基础代理模式带来的问题
在基础代理模式中,通过delegatecall和相同的成员变量存储布局实现代理调用,但是也出现一些问题、
由于结构化的模式,proxy合约与Logic合约使用了相同的成员变量的堆叠,但是proxy中的这些变量的唯一作用就是占位符。这使得proxy合约并不纯粹,
非结构化代理
Proxy合约使用空白存储,把存储的"解释权"完全交付给Logic合约,数据与逻辑相分离;
在基础代理中,数据的解释权是完全由Proxy来决定的,Logic中的成员变量必须与Proxy成员变量storage layout保持一致。非结构化代理模式选择将数据的解释权交给Logic合约,由Logic合约来决定如何进行成员变量的storage layout。想办法让Proxy自身的逻辑不参与成员变量的堆叠,也就不必在logic中出现占位符变量;
Logic成为纯粹的业务逻辑处理合约,Proxy成为纯粹的逻辑执行控制合约。Proxy下派任务,Logic执行任务。 Proxy使用空白存储,那使用delegatecall时,就可以随意操作存储,比如Logic合约count占slot0,那在使用delegatecall时,就会反过来操作Proxy的slot0,因为Proxy的存储布局是空白的,所以并不会出现布局冲突。
通过代码理解
// 逻辑合约
contract Logic {
uint public count;
function inc() external {
count += 1;
}
}
// 非结构化代理合约 slot:存储槽 专栏存储布局中有提到
contract UnstructuredProxy {
// 定义逻辑合约地址的存储位置,注意这里使用的是constant;constant变量并不参与成员变量的堆叠,
//或者说并不会占用slot 存储槽。
//简单解释一下这里为什么要设置这个变量;
//在基础代理模式中,Proxy会声明一个logicAddress的成员变量保存,所以会占用存储布局,那么Logic合约就会出现占位符 logicAddress,以保证存储布局一致;
//我们在这设置一个hash作为setLogic函数中汇编sstore(position, newLogic),将这个地址存到slot hash中,所以这里的logicPosition要足够远,确保不会参与堆叠
bytes32 private constant logicPosition = keccak256("org.zeppelinos.proxy.implementation");
// 升级到新的逻辑合约地址
function upgradeTo(address newLogic) public {
setLogic(newLogic);
}
// 获取当前的逻辑合约地址
function logic() public view returns(address impl) {
bytes32 position = logicPosition;
assembly {
impl := sload(position) //读取slot position中的数据
}
}
// 内部函数设置新的逻辑合约地址
function setLogic(address newLogic) internal {
bytes32 position = logicPosition;
assembly {
sstore(position, newLogic) //将newLogic 地址 存储到position位置
}
}
// 使用内联汇编委托调用
function _delegate(address _logic) internal virtual {
assembly {
//这里简单解释,不能保证正确;
//calldatacopy是针对calldata的函数,他会直接找到calldata,根据参数决定copy多长的字节
//calldatasize()针对calldata,取到calldata的size 字节大小
//(t, f, calldatasize())
//t是合约的内存空间起点,f是调用数据(`calldata`)的起点。
//注意这里是memory内存,所以并不会存在storage里,只存活于函数指定期间
calldatacopy(0, 0, calldatasize())
//对_logic合约地址使用delegatecall
let result := delegatecall(gas(), _logic, 0, calldatasize(), 0, 0)
//这里就跟上边的差不多了 returndatacopy 返回结果result
returndatacopy(0, 0, returndatasize())
//check result
switch result
/
case 0 {
// 0 就直接回滚
revert(0, returndatasize())
}
default {
// 默认返回 返回数据给外部调用者
return(0, returndatasize())
}
}
}
// 当调用不匹配任何函数时触发
fallback() external {
_delegate(logic());
}
}