欢迎订阅专栏:3分钟Solidity--智能合约--Web3区块链技术必学
如需获取本内容的最新版本,请参见 Cyfrin.io 上的Upgradeable Proxy(代码示例)
可升级代理合约示例。切勿在生产环境中使用。
此示例展示了:
- 如何在调用
fallback时使用delegatecall并返回数据。 - 如何将
admin和implementation的地址存储在特定的存储槽中。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
// 透明可升级代理模式
contract CounterV1 {
uint256 public count;
function inc() external {
count += 1;
}
}
contract CounterV2 {
uint256 public count;
function inc() external {
count += 1;
}
function dec() external {
count -= 1;
}
}
contract BuggyProxy {
address public implementation;
address public admin;
constructor() {
admin = msg.sender;
}
function _delegate() private {
(bool ok,) = implementation.delegatecall(msg.data);
require(ok, "delegatecall 失败");
}
fallback() external payable {
_delegate();
}
receive() external payable {
_delegate();
}
function upgradeTo(address _implementation) external {
require(msg.sender == admin, "未经授权");
implementation = _implementation;
}
}
contract Dev {
function selectors() external view returns (bytes4, bytes4, bytes4) {
return (
Proxy.admin.selector,
Proxy.implementation.selector,
Proxy.upgradeTo.selector
);
}
}
contract Proxy {
// 所有函数/变量应为私有,将所有调用转发至回退函数
// -1 表示未知原像
// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
bytes32 private constant IMPLEMENTATION_SLOT =
bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
// 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
bytes32 private constant ADMIN_SLOT =
bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1);
constructor() {
_setAdmin(msg.sender);
}
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
function _getAdmin() private view returns (address) {
return StorageSlot.getAddressSlot(ADMIN_SLOT).value;
}
function _setAdmin(address _admin) private {
require(_admin != address(0), "admin = zero address");
StorageSlot.getAddressSlot(ADMIN_SLOT).value = _admin;
}
function _getImplementation() private view returns (address) {
return StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value;
}
function _setImplementation(address _implementation) private {
require(
_implementation.code.length > 0, "实现不是一个合约"
);
StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = _implementation;
}
// 管理员接口 //
function changeAdmin(address _admin) external ifAdmin {
_setAdmin(_admin);
}
// 0x3659cfe6
function upgradeTo(address _implementation) external ifAdmin {
_setImplementation(_implementation);
}
// 0xf851a440
function admin() external ifAdmin returns (address) {
return _getAdmin();
}
// 0x5c60da1b
function implementation() external ifAdmin returns (address) {
return _getImplementation();
}
// 用户接口 //
function _delegate(address _implementation) internal virtual {
assembly {
// 复制 msg.data。我们在这个内联汇编块中完全控制内存,因为它不会返回到 Solidity 代码。
// 我们覆盖了内存位置 0 处的 Solidity 暂存区。
// calldatacopy(t, f, s) - 从调用数据的位置f复制s个字节到内存的位置t
// calldatasize() - 调用数据的字节大小
calldatacopy(0, 0, calldatasize())
// 调用实现。
// out和outsize为0,因为我们还不知道大小。
// delegatecall(g, a, in, insize, out, outsize) -
// - 调用地址a的合约
// - 输入 mem[in…(in+insize))
// - 提供 g gas
// - 输出区域 mem[out…(out+outsize))
// - 错误时返回0(例如,gas耗尽),成功时返回1
let result :=
delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
// 复制返回的数据。
// returndatacopy(t, f, s) - 将返回数据中从位置f开始的s个字节复制到内存位置t
// returndatasize() - 最后返回数据的大小
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall 在出错时返回 0。
case 0 {
// revert(p, s) - 终止执行,回滚状态更改,返回内存数据 mem[p…(p+s))
revert(0, returndatasize())
}
default {
// 返回(p, s) - 结束执行,返回数据内存[p…(p+s))
return(0, returndatasize())
}
}
}
function _fallback() private {
_delegate(_getImplementation());
}
fallback() external payable {
_fallback();
}
receive() external payable {
_fallback();
}
}
contract ProxyAdmin {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
function getProxyAdmin(address proxy) external view returns (address) {
(bool ok, bytes memory res) =
proxy.staticcall(abi.encodeCall(Proxy.admin, ()));
require(ok, "call failed");
return abi.decode(res, (address));
}
function getProxyImplementation(address proxy)
external
view
returns (address)
{
(bool ok, bytes memory res) =
proxy.staticcall(abi.encodeCall(Proxy.implementation, ()));
require(ok, "call failed");
return abi.decode(res, (address));
}
function changeProxyAdmin(address payable proxy, address admin)
external
onlyOwner
{
Proxy(proxy).changeAdmin(admin);
}
function upgrade(address payable proxy, address implementation)
external
onlyOwner
{
Proxy(proxy).upgradeTo(implementation);
}
}
library StorageSlot {
struct AddressSlot {
address value;
}
function getAddressSlot(bytes32 slot)
internal
pure
returns (AddressSlot storage r)
{
assembly {
r.slot := slot
}
}
}
contract TestSlot {
bytes32 public constant slot = keccak256("TEST_SLOT");
function getSlot() external view returns (address) {
return StorageSlot.getAddressSlot(slot).value;
}
function writeSlot(address _addr) external {
StorageSlot.getAddressSlot(slot).value = _addr;
}
}
Remix Lite 尝试一下