这个案例是代理合约的实际操作,代理合约实现了逻辑和数据的分离,就可以实现在生产环境中,轻松升级合约,这就是一个如何实际升级合约的案例。
实现一个简单的可升级合约,它包含3
个合约:代理合约,旧的逻辑合约,和新的逻辑合约
代理合约
我们没有在它的fallback()
函数中使用内联汇编
,而仅仅用了implementation.delegatecall(msg.data);
。因此,回调函数没有返回值,但足够教学使用了。
它包含3
个变量:
implementation
:逻辑合约地址。admin
:admin地址。words
:字符串,可以通过逻辑合约的函数改变。
它包含3
个函数
- 构造函数:初始化admin和逻辑合约地址。
fallback()
:回调函数,将调用委托给逻辑合约。upgrade()
:升级函数,改变逻辑合约地址,只能由admin
调用。
// SPDX-License-Identifier: MIT
// wtf.academy
pragma solidity ^0.8.4;// 简单的可升级合约,管理员可以通过升级函数更改逻辑合约地址,从而改变合约的逻辑。
// 教学演示用,不要用在生产环境
contract SimpleUpgrade {address public implementation; // 逻辑合约地址address public admin; // admin地址string public words; // 字符串,可以通过逻辑合约的函数改变// 构造函数,初始化admin和逻辑合约地址constructor(address _implementation){admin = msg.sender;implementation = _implementation;}// fallback函数,将调用委托给逻辑合约fallback() external payable {(bool success, bytes memory data) = implementation.delegatecall(msg.data);}// 升级函数,改变逻辑合约地址,只能由admin调用function upgrade(address newImplementation) external {require(msg.sender == admin);implementation = newImplementation;}
}
旧逻辑合约
这个逻辑合约包含3
个状态变量,与保持代理合约一致,防止插槽冲突。它只有一个函数foo()
,将代理合约中的words
的值改为"old"
。
// 逻辑合约1
contract Logic1 {// 状态变量和proxy合约一致,防止插槽冲突address public implementation; address public admin; string public words; // 字符串,可以通过逻辑合约的函数改变// 改变proxy中状态变量,选择器: 0xc2985578function foo() public{words = "old";}
}
新逻辑合约
这个逻辑合约包含3
个状态变量,与保持代理合约一致,防止插槽冲突。它只有一个函数foo()
,将代理合约中的words
的值改为"new"
。
// 逻辑合约2
contract Logic2 {// 状态变量和proxy合约一致,防止插槽冲突address public implementation; address public admin; string public words; // 字符串,可以通过逻辑合约的函数改变// 改变proxy中状态变量,选择器:0xc2985578function foo() public{words = "new";}
}
Remix
实现
完整代码:
// SPDX-License-Identifier: MIT
// wtf.academy
pragma solidity ^0.8.4;contract SimpleUpgrade{address public implementation;//逻辑合约地址address public admin;//合约管理人地址string public words;//字符串,通过逻辑合约的函数改变//构造函数,初始化admin和逻辑合约地址constructor(address _implementation) {implementation = _implementation;admin = msg.sender;}//通过fallback函数,将调用委托给逻辑合约fallback() external payable {(bool success,bytes memory data) = implementation.delegatecall(msg.data);}//升级函数,改变逻辑合约的地址,就可以升级到新的逻辑合约,而且只能管理员才能调用function upgrade(address newImplementation) external {require(msg.sender == admin,'not owner');implementation = newImplementation;}
}contract OldLogic{address public implementation;address public admin;string public words;function foo() public {words = 'old';}
}contract NewLogic{address public implementation;address public admin;string public words;function foo() public {words = 'new';}
}
1、部署新旧逻辑合约Logic1
和Logic2
。
2、部署可升级合约SimpleUpgrade
,将implementation
地址指向把旧逻辑合约。
3、利用选择器0xc2985578
,在代理合约中调用旧逻辑合约Logic1
的foo()
函数,将words
的值改为"old"
。
4、调用upgrade()
,将implementation
地址指向新逻辑合约Logic2
。
5、 利用选择器0xc2985578
,在代理合约中调用新逻辑合约Logic2
的foo()
函数,将words
的值改为"new"
。
它是一个可以改变逻辑合约的代理合约,给不可更改的智能合约增加了升级功能。但是,这个合约有选择器冲突
的问题,存在安全隐患。之后我们会介绍解决这一隐患的可升级合约标准:透明代理和UUPS
。