作者:深圳职业技术学院高旭亮
前面章节介绍了delegatecall、slot等知识点,本章节会通过讲解代码的方式来帮助大家开发出可升级结构的智能合约,为了演示的清晰,文章中合约代码没有使用fallback函数完成合约调用,而是通过手动编写请求字节的方式来调用合约,带领读者感受升级合约的过程。
BaseStructure合约继承Owner合约,为逻辑合约与存储合约的基础,后续在升级结构的时候都是从该合约下手。
contract Owner {
// 逻辑合约所有者
address public owner;
// 存储合约所有者
address public proxyOwner;
event OwnerSet(address indexed oldOwner, address indexed newOwner);
modifier isOwner() {
require(tx.origin == owner || owner == address(0) , "Caller is not owner");
_;
}
modifier isProxyOwner() {
require(msg.sender == proxyOwner || proxyOwner == address(0), "Caller is not proxyOwner");
_;
}
function changeOwner(address newOwner) public isOwner {
emit OwnerSet(owner, newOwner);
owner = newOwner;
}
function changeProxyOwner(address newOwner) public isProxyOwner {
emit OwnerSet(proxyOwner, newOwner);
proxyOwner = newOwner;
}
function getOwner() external view returns (address) {
return owner;
}
function getProxyOwner() external view returns (address) {
return proxyOwner;
}
}
pragma solidity ^0.5.1;
import "./Owner.sol";
contract BaseStructure is Owner{
// 逻辑合约地址
address public implement;
// 业务变量
string public name;
}
逻辑合约继承基础结构合约
pragma solidity ^0.5.1;
import "./BaseStructure.sol";
contract Logic is BaseStructure{
function setName(string memory _name) public {
name = _name;
}
function getName() public view returns (string memory){
return name;
}
}
存储合约继承基础结构合约
pragma solidity ^0.5.1;
import "./BaseStructure.sol";
import "./Logic.sol";
contract Proxy is BaseStructure{
function callSetName(string memory _name) public returns (bool) {
bytes4 setName = Logic(implement).setName.selector;
bytes memory data = abi.encodeWithSelector(setName, _name);
(bool success, ) = implement.delegatecall(data);
return success;
}
function callGetName() public returns (string memory) {
bytes4 getName = Logic(implement).getName.selector;
bytes memory data = abi.encodeWithSelector(getName);
(bool success, bytes memory result) = implement.delegatecall(data);
return abi.decode(result, (string));
}
function setImplement(address addr) public isProxyOwner{
implement = addr;
}
function getImplement() public isProxyOwner view returns (address){
return implement;
}
}
-
代码中的delegatecalle等知识不再介绍,可以看前面的章节进行学习。
-
定义基础结构合约的原因:保证逻辑合约和存储合约的状态变量顺序不变,在新增、修改状态变量时不易出错。
-
升级结构的方式:直接修改基础结构合约后部署逻辑合约,更改implement即可,下面会演示。
-
前言提到的fallback作用:如果不使用fallback,是不可能达到逻辑升级的,当逻辑合约中函数变动时,如果没采用fallback,我们就得手动写一个call,这个是比较不现实的。
function() external { (bool success, ) = getImplement().delegatecall(msg.data); assembly { let free_mem_ptr := mload(0x40) returndatacopy(free_mem_ptr, 0, returndatasize()) switch success case 0 { revert(free_mem_ptr, returndatasize()) } default { return(free_mem_ptr, returndatasize()) } } }
请使用fallback的调用方式进行测试,在升级基础结构合约和逻辑合约后,不需要重新部署存储合约。
pragma solidity ^0.5.1;
import "./Owner.sol";
contract BaseStructure is Owner{
address public implement;
string public name;
uint public age;
}
pragma solidity ^0.5.1;
import "./BaseStructure.sol";
contract Logic is BaseStructure{
function setName(string memory _name) public {
name = _name;
}
function getName() public view returns (string memory){
return name;
}
function setAge(uint256 _age) public {
age = _age;
}
function getAge() public view returns (uint256) {
return age;
}
}
本章节通过代码演示了可升级结构的合约,后续会介绍非结构化存储合约、推荐可升级合约库和升级结构的一些经验,铺垫这么多知识,是因为作者之前需要编写可升级合约,一上来就看到成品的可升级合约库,但那时候是一头雾水的,查阅了许多资料才知道如何合理使用,所以觉得还是得有个循序渐进的过程,希望文章能够帮助到有需要的朋友。