
delegatecall,在代理合约中使用 delegatecall 调用逻辑合约的函数,使得代码在代理的存储上下文中执行。(bool success, bytes memory data) = implementation.delegatecall(msg.data);delegatecall 会在当前合约的存储和上下文中执行目标合约的代码。upgradeTo 函数完成。风险点 | 说明 | 解决方案 |
|---|---|---|
存储布局冲突 | 升级后逻辑合约的变量顺序、类型不一致,导致数据错位 | 遵循固定的变量追加规则,避免删除或更改类型 |
初始化漏洞 | 新逻辑合约的构造函数不会被代理调用 | 使用 |
delegatecall 风险 | 调用外部不可信合约可能破坏存储 | 严格控制升级权限,禁止不可信代码执行 delegatecall |
权限丢失 | 升级过程中可能被替换成恶意逻辑 | 使用多签或 Timelock 控制升级 |
在我们的测试用例中,实现思路如下:
Proxy 只保存 implementation 和 adminStorage 合约Logic 通过固定的 slot 读取数据这个也是OpenZeppelin UUPS/Transparent Proxy 的核心思路
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Storage {
uint256 private _value;
function setValue(uint256 value) public {
_value = value;
}
function getValue() public view returns (uint256) {
return _value;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Storage.sol";
contract LogicV1 {
Storage public store;
constructor(address _store) {
store = Storage(_store);
}
function setValue(uint256 _value) public {
store.setValue(_value);
}
function getValue() public view returns (uint256) {
return store.getValue();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Storage.sol";
import "./LogicV1.sol";
contract LogicV2 is LogicV1 {
constructor(address _store) LogicV1(_store) {}
function increment() public {
uint256 current = store.getValue();
store.setValue(current + 1); // 使用 getter + setter
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Proxy {
address public implementation;
address public admin;
constructor(address _impl) {
implementation = _impl;
admin = msg.sender;
}
function upgradeTo(address _newImpl) public {
require(msg.sender == admin, "Not admin");
implementation = _newImpl;
}
fallback() external payable {
address impl = implementation;
require(impl != address(0), "No implementation");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
receive() external payable {}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/LogicV1.sol";
import "../src/LogicV2.sol";
import "../src/Proxy.sol";
import "../src/Storage.sol";
contract UpgradeTest is Test {
LogicV1 logicV1;
Proxy proxy;
Storage store;
function setUp() public {
store = new Storage();
logicV1 = new LogicV1(address(store));
proxy = new Proxy(address(logicV1));
}
function testUpgrade() public {
LogicV1 proxyAsV1 = LogicV1(address(proxy));
proxyAsV1.setValue(42);
assertEq(proxyAsV1.getValue(), 42);
LogicV2 logicV2 = new LogicV2(address(store));
proxy.upgradeTo(address(logicV2));
LogicV2 proxyAsV2 = LogicV2(address(proxy));
proxyAsV2.increment();
assertEq(proxyAsV2.getValue(), 43);
}
}执行测试命令:
➜ counter git:(main) ✗ forge test --match-path test/UpgradeTest.t.sol -vvv
[⠊] Compiling...
[⠢] Compiling 5 files with Solc 0.8.29
[⠰] Solc 0.8.29 finished in 1.21s
Compiler run successful!
Ran 1 test for test/UpgradeTest.t.sol:UpgradeTest
[PASS] testUpgrade() (gas: 376528)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.86ms (3.15ms CPU time)
Ran 1 test suite in 355.58ms (10.86ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)delegatecall 操作的是 代理合约的 storage slot,所以逻辑合约和代理合约的变量 slot 不能冲突,否则会覆盖状态。简单来说,slot 就是状态变量在合约存储中的编号位置。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。