深入解析:Solidity中call与delegatecall的核心区别——谁在修改谁的存储?

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 地址

核心规则总结

  1. call()
    • address(this) = 被调用合约的地址
    • msg.sender = 调用合约的地址
    • 修改被调用合约的存储
  2. 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);
    }
}
相关推荐
Sui_Network1 天前
回顾 2025,Sui 技术栈的落地之年
大数据·人工智能·web3·去中心化·区块链
hopsky1 天前
区块链以太坊-基础
区块链
无限大.1 天前
为什么“Web3“是下一代互联网?——从中心化到去中心化的转变
web3·去中心化·区块链
小明的小名叫小明1 天前
5.Uniswap 技术架构详解
架构·区块链
lsrsyx1 天前
SUNX交易所技术优势与用户信任体验全景解析
区块链
小明的小名叫小明1 天前
4.Uniswap核心业务逻辑详解
区块链
木西2 天前
30秒搞懂ERC-2981:NFT版税的终极解决方案!
web3·智能合约·solidity
Yyyyy123jsjs2 天前
XAUUSD API 周末无推送,会影响回测与实盘一致性吗?
大数据·区块链
qq_381454992 天前
区块链:从底层原理到产业落地的全景解析
区块链
千匠网络2 天前
千匠大宗电商系统:赋能煤炭能源行业产业升级
大数据·人工智能·区块链·大宗电商·大宗电商系统