Solidity中call与delegatecall的核心区别
对比表格
| 特性 | A.call(B) |
A.delegatecall(B) |
|---|---|---|
| 执行的代码 | 合约B的代码 | 合约B的代码 |
| 存储修改 | 合约B的存储 | 合约A的存储 |
address(this) |
合约B地址 | 合约A地址 |
msg.sender |
合约A地址 | 原始调用者地址 |
msg.value |
可传递ETH | 被忽略 |
详细示例
示例代码
// 合约B - 被调用者
contract B {
address public contractAddress;
address public sender;
uint public value;
function setValues() public payable {
contractAddress = address(this); // 注意这里!
sender = msg.sender;
value = msg.value;
}
}
// 合约A - 调用者
contract A {
address public contractAddress;
address public sender;
uint public value;
// 方式1:使用 call()
function callB(address bAddress) public payable {
(bool success, ) = bAddress.call{value: msg.value}(
abi.encodeWithSignature("setValues()")
);
require(success, "call failed");
}
// 方式2:使用 delegatecall()
function delegatecallB(address bAddress) public {
(bool success, ) = bAddress.delegatecall(
abi.encodeWithSignature("setValues()")
);
require(success, "delegatecall failed");
}
}
执行结果对比
场景1:A.callB(B地址)
// 用户调用:A.callB(B地址) 并发送 1 ETH
合约A的状态:
contractAddress: ❌ 未改变sender: ❌ 未改变value: ❌ 未改变
合约B的状态:
contractAddress: B的地址 ✅(因为address(this)= B)sender: A的地址 ✅(因为msg.sender= A)value: 1 ETH ✅(因为msg.value= 1 ETH)
场景2:A.delegatecallB(B地址)
// 用户调用:A.delegatecallB(B地址) 并发送 1 ETH
合约A的状态:
contractAddress: A的地址 ✅(因为address(this)= A)sender: 用户的地址 ✅(因为msg.sender= 用户)value: 0 ✅(因为delegatecall忽略msg.value)
合约B的状态:
contractAddress: ❌ 未改变sender: ❌ 未改变value: ❌ 未改变
可视化理解
call() 的情况
用户 → 合约A.call() → 合约B.setValues()
│ │
└─ msg.sender ├─ address(this) = 合约B
└─ msg.value └─ msg.sender = 合约A
修改的是合约B的存储
delegatecall() 的情况
用户 → 合约A.delegatecall() → 合约B.setValues()的代码
│ │
├─ 提供存储 │
├─ address(this) = 合约A ←─┘
└─ msg.sender = 用户
修改的是合约A的存储
内存地址分析
contract Debug {
function testCall(address b) public payable {
// 在合约A中执行
address aAddress = address(this); // 合约A的地址
// call:切换到合约B的上下文
b.call{value: 1 ether}(
abi.encodeWithSignature("debug()")
);
}
function testDelegateCall(address b) public {
// 在合约A中执行
address aAddress = address(this); // 合约A的地址
// delegatecall:保持合约A的上下文
b.delegatecall(
abi.encodeWithSignature("debug()")
);
}
}
contract Target {
function debug() public payable {
// 当被 call 调用时:
// address(this) = Target 地址
// msg.sender = Debug 地址
// 当被 delegatecall 调用时:
// address(this) = Debug 地址
// msg.sender = 原始用户地址
}
}
代理模式的实际应用
// 逻辑合约
contract Logic {
address public implementation; // 这个变量实际上属于代理合约!
function upgrade(address newImplementation) public {
// 这个函数修改的是代理合约的存储
implementation = newImplementation;
}
}
// 代理合约
contract Proxy {
address public implementation; // 与 Logic 中的变量在相同槽位
constructor(address _logic) {
implementation = _logic;
}
fallback() external {
// 关键:使用 delegatecall
implementation.delegatecall(msg.data);
}
}
// 使用
Proxy proxy = new Proxy(logicAddress);
// 当调用 proxy.upgrade(newLogic) 时:
// 1. 调用 proxy.fallback()
// 2. delegatecall 到 logic.upgrade()
// 3. logic.upgrade() 的代码执行
// 4. 但修改的是 proxy.implementation
// 5. address(this) 在 upgrade() 函数中是 proxy 地址
核心规则总结
call():address(this)= 被调用合约的地址msg.sender= 调用合约的地址- 修改被调用合约的存储
delegatecall():address(this)= 调用合约的地址 ✅msg.sender= 原始用户的地址- 修改调用合约的存储
安全提醒
// ⚠️ 危险示例
contract Malicious {
address public owner;
function takeOwnership() public {
// 如果被 delegatecall 调用,这会修改调用合约的 owner!
owner = msg.sender;
}
}
// 安全的代理合约应该:
contract SafeProxy {
address public implementation;
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function upgrade(address newImplementation) public onlyOwner {
// 1. 验证新合约
require(newImplementation != address(0), "Invalid address");
// 2. 防止重入
address oldImplementation = implementation;
implementation = newImplementation;
// 3. 发出事件
emit Upgraded(oldImplementation, newImplementation);
}
}