深入解析: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);
    }
}
相关推荐
币圈菜头4 小时前
GAEA:情感AI如何改变我们的生活?12月空投前瞻
人工智能·web3·区块链·生活
MicroTech20255 小时前
微算法科技(NASDAQ:MLGO)优化区块链身份证明(PoI)技术:构建可信网络的基石
网络·科技·区块链
谈笑也风生9 小时前
浅谈:被称为新基建的区块链(二)
区块链
阿恩.7701 天前
2026年1月最新计算机、人工智能、经济管理国际会议:选对会议 = 论文成功率翻倍
人工智能·经验分享·笔记·计算机网络·金融·区块链
百***35481 天前
区块链在金融中的Polygon
金融·区块链
软件工程小施同学1 天前
区块链论文速读 CCF A--FAST 2025 优化区块链存储的超额收费
区块链
TechubNews1 天前
十年转折,务实为新:从 Devconnect 2025 解析以太坊的「新思路」
区块链
MicroTech20251 天前
微算法科技(NASDAQ:MLGO)以区块链技术重塑信任生态,驱动数字化变革
科技·区块链