delegatecall
delegatecall 解决的问题、使用方式和注意事项。
发布于 2026年5月28日0 views
delegatecall 的核心是:用别的合约代码,修改自己的合约状态。
普通 call 是“去目标合约里执行目标合约的逻辑,修改目标合约的状态”。delegatecall 则是“借用目标合约的代码,但在当前合约的上下文中执行”。
解决什么问题
delegatecall 主要用于:
- 代理合约升级。
- 逻辑合约复用。
- 模块化合约架构。
最常见场景是 Proxy:
- Proxy 合约保存状态。
- Implementation 合约保存逻辑。
- Proxy 通过
delegatecall调用 Implementation。 - 升级时只换 Implementation 地址,Proxy 状态不变。
基本例子
调用合约:
contract Caller {
uint256 public value;
address public logic;
constructor(address _logic) {
logic = _logic;
}
function setValue(uint256 _value) external {
(bool success, ) = logic.delegatecall(
abi.encodeWithSignature("setValue(uint256)", _value)
);
require(success, "delegatecall failed");
}
}逻辑合约:
contract Logic {
uint256 public value;
function setValue(uint256 _value) external {
value = _value;
}
}当 Caller 使用 delegatecall 调用 Logic.setValue 时,真正被修改的是 Caller.value,不是 Logic.value。
和 call 的区别
call = 在目标合约上下文执行,修改目标合约状态
delegatecall = 在当前合约上下文执行,修改当前合约状态delegatecall 执行时:
msg.sender保持原调用者。msg.value保持原值。- 读写的是当前合约 storage。
- 使用的是目标合约代码。
存储布局
delegatecall 最大的风险是存储布局。
如果 Proxy 和 Logic 的状态变量顺序不一致,就可能写错 slot。
contract Proxy {
uint256 public value; // slot 0
}
contract Logic {
uint256 public value; // slot 0
}这里布局一致,Logic.value 对应 Proxy.value。
如果 Logic 第一位换成别的变量,就可能写坏 Proxy 状态。
常见用途
Proxy 升级
升级合约时,Proxy 保留状态,只替换逻辑合约地址。
常见模式:
- Transparent Proxy。
- UUPS Proxy。
- Beacon Proxy。
Diamond
Diamond Standard 会把功能拆成多个 Facet,通过 delegatecall 路由到不同模块。
适合大型协议,但复杂度也更高。
注意事项
- 使用
delegatecall必须非常谨慎。 - Proxy 和 Logic 的 storage layout 必须保持兼容。
- 不要 delegatecall 到不可信合约。
- 升级合约前要测试存储布局。
delegatecall失败时要正确 revert。
总结
delegatecall 是一种“借代码、用自己状态”的调用方式。它让代理升级和模块化合约成为可能,但最大风险是存储布局错乱和调用不可信逻辑。