delegatecall 和 call 是 Solidity 中用于合约间调用的两个重要底层函数,理解它们的区别对于实现代理合约和可升级合约至关重要。
1. call 的基本用法
call 是最常用的低级调用函数,在目标合约的上下文中执行代码。
soliditycontract CallExample { // 使用 call 发送 ETH function sendEther(address payable recipient) public payable { (bool success, ) = recipient.call{value: msg.value}(""); require(success, "Transfer failed"); } // 使用 call 调用函数 function callFunction(address target, uint256 value) public returns (bool) { // 编码函数调用:function setValue(uint256) bytes memory data = abi.encodeWithSignature("setValue(uint256)", value); (bool success, bytes memory returnData) = target.call(data); require(success, "Call failed"); return success; } // 使用 call 调用并获取返回值 function callAndGetResult(address target) public view returns (uint256) { bytes memory data = abi.encodeWithSignature("getValue()"); (bool success, bytes memory returnData) = target.staticcall(data); require(success, "Call failed"); return abi.decode(returnData, (uint256)); } }
2. delegatecall 的基本用法
delegatecall 在调用者的上下文中执行目标合约的代码,保持 msg.sender 和 msg.value 不变。
soliditycontract DelegatecallExample { uint256 public value; // 这个变量会被目标合约代码修改 // 使用 delegatecall 执行逻辑合约代码 function executeLogic(address logicContract, uint256 newValue) public { bytes memory data = abi.encodeWithSignature("setValue(uint256)", newValue); (bool success, ) = logicContract.delegatecall(data); require(success, "Delegatecall failed"); } } // 逻辑合约 contract LogicContract { uint256 public value; // 注意:这个变量布局必须与代理合约一致 function setValue(uint256 newValue) public { value = newValue; // 修改的是调用者(代理合约)的 storage } function getValue() public view returns (uint256) { return value; } }
3. call 与 delegatecall 对比
| 特性 | call | delegatecall |
|---|---|---|
| 执行上下文 | 目标合约 | 调用者合约 |
| msg.sender | 当前合约地址 | 原始调用者地址 |
| msg.value | 传递的值 | 原始调用值 |
| storage 访问 | 目标合约的 storage | 调用者合约的 storage |
| 代码执行位置 | 目标合约地址 | 目标合约地址 |
| 使用场景 | 普通调用、转账 | 代理合约、库合约 |
4. 代理合约实现
基础代理合约( EIP-1967 )
soliditycontract Proxy { // EIP-1967 标准存储槽 // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; // bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) bytes32 private constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; constructor(address _implementation) { _setImplementation(_implementation); _setAdmin(msg.sender); } modifier onlyAdmin() { require(msg.sender == _getAdmin(), "Not admin"); _; } // 获取实现合约地址 function _getImplementation() internal view returns (address impl) { assembly { impl := sload(IMPLEMENTATION_SLOT) } } // 设置实现合约地址 function _setImplementation(address _implementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, _implementation) } } // 获取管理员地址 function _getAdmin() internal view returns (address adm) { assembly { adm := sload(ADMIN_SLOT) } } // 设置管理员地址 function _setAdmin(address _admin) internal { assembly { sstore(ADMIN_SLOT, _admin) } } // 升级实现合约 function upgradeTo(address _newImplementation) public onlyAdmin { _setImplementation(_newImplementation); } // 获取当前实现地址(便于查询) function implementation() public view returns (address) { return _getImplementation(); } // 回退函数:将所有调用委托给实现合约 fallback() external payable { _delegate(_getImplementation()); } receive() external payable { _delegate(_getImplementation()); } // 核心委托逻辑 function _delegate(address _implementation) internal { assembly { // 复制 msg.data calldatacopy(0, 0, calldatasize()) // 执行 delegatecall let result := delegatecall( gas(), _implementation, 0, calldatasize(), 0, 0 ) // 复制返回数据 returndatacopy(0, 0, returndatasize()) // 根据结果返回或回滚 switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } }
逻辑合约示例
solidity// 逻辑合约 V1 contract LogicV1 { uint256 public value; address public implementation; // 与代理合约存储布局一致 address public admin; function setValue(uint256 _value) public { value = _value; } function getValue() public view returns (uint256) { return value; } } // 逻辑合约 V2(升级版本) contract LogicV2 { uint256 public value; address public implementation; address public admin; // 新增功能 function setValue(uint256 _value) public { value = _value * 2; // 新逻辑:值翻倍 } function getValue() public view returns (uint256) { return value; } // 新增函数 function increment() public { value++; } }
5. 透明代理模式
透明代理解决了代理合约和实现合约函数选择器冲突的问题。
soliditycontract TransparentProxy { bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; bytes32 private constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; constructor(address _implementation, address _admin) { _setImplementation(_implementation); _setAdmin(_admin); } modifier ifAdmin() { if (msg.sender == _getAdmin()) { _; } else { _fallback(); } } // 管理员函数(只有管理员可以调用) function upgradeTo(address _newImplementation) external ifAdmin { _setImplementation(_newImplementation); } function admin() external ifAdmin returns (address) { return _getAdmin(); } function implementation() external ifAdmin returns (address) { return _getImplementation(); } // 修改管理员 function changeAdmin(address _newAdmin) external ifAdmin { _setAdmin(_newAdmin); } // 非管理员调用会执行到这里 function _fallback() internal { _delegate(_getImplementation()); } fallback() external payable { _fallback(); } receive() external payable { _fallback(); } // 存储操作函数 function _getImplementation() internal view returns (address impl) { assembly { impl := sload(IMPLEMENTATION_SLOT) } } function _setImplementation(address _implementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, _implementation) } } function _getAdmin() internal view returns (address adm) { assembly { adm := sload(ADMIN_SLOT) } } function _setAdmin(address _admin) internal { assembly { sstore(ADMIN_SLOT, _admin) } } function _delegate(address _implementation) internal { assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } }
6. UUPS 代理模式
UUPS(Universal Upgradeable Proxy Standard)将升级逻辑放在实现合约中。
solidity// UUPS 代理 contract UUPSProxy { bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; constructor(address _implementation) { _setImplementation(_implementation); } function _getImplementation() internal view returns (address impl) { assembly { impl := sload(IMPLEMENTATION_SLOT) } } function _setImplementation(address _implementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, _implementation) } } fallback() external payable { _delegate(_getImplementation()); } receive() external payable { _delegate(_getImplementation()); } function _delegate(address _implementation) internal { assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } } // UUPS 实现合约接口 interface IUUPS { function upgradeTo(address newImplementation) external; function proxiableUUID() external view returns (bytes32); } // UUPS 实现合约 contract UUPSImplementation is IUUPS { bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; uint256 public value; address public implementation; address public owner; modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } constructor() { owner = msg.sender; } function upgradeTo(address newImplementation) external onlyOwner { _authorizeUpgrade(newImplementation); _upgradeTo(newImplementation); } function proxiableUUID() external pure returns (bytes32) { return IMPLEMENTATION_SLOT; } function _authorizeUpgrade(address newImplementation) internal virtual { // 可以在这里添加额外的升级验证逻辑 } function _upgradeTo(address newImplementation) internal { assembly { sstore(IMPLEMENTATION_SLOT, newImplementation) } } function setValue(uint256 _value) public { value = _value; } }
7. 存储布局注意事项
solidity// 错误的存储布局示例 contract WrongLayoutV1 { uint256 public value; // slot 0 address public owner; // slot 1 } contract WrongLayoutV2 { address public owner; // slot 0 - 错误!应该是 slot 1 uint256 public value; // slot 1 - 错误!应该是 slot 0 uint256 public newValue; // slot 2 } // 正确的存储布局 contract CorrectLayoutV1 { uint256 public value; // slot 0 address public owner; // slot 1 } contract CorrectLayoutV2 { uint256 public value; // slot 0 - 保持相同 address public owner; // slot 1 - 保持相同 uint256 public newValue; // slot 2 - 新增变量放在最后 }
8. 最佳实践
- 使用标准存储槽:遵循 EIP-1967 标准避免存储冲突
- 保持存储布局一致:升级时不能改变已有变量的顺序
- 使用 OpenZeppelin:使用经过审计的标准实现
- 测试升级流程:在测试网上充分测试升级过程
- 考虑钻石模式:对于超大型合约,考虑使用 EIP-2535 钻石标准
solidity// 使用 OpenZeppelin 的代理 import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; // 推荐:使用 UUPS 模式 import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract MyContract is UUPSUpgradeable, Ownable { function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} }