深入解析: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);
    }
}
相关推荐
Max_uuc9 小时前
【C++ 硬核】利用链接器魔法 (Linker Sections) 实现“去中心化”的自动初始化与插件系统
去中心化·区块链
devmoon9 小时前
在 Polkadot 上部署独立区块链Paseo 测试网实战部署指南
开发语言·安全·区块链·polkadot·erc-20·测试网·独立链
傻小胖9 小时前
22.ETH-智能合约-北大肖臻老师客堂笔记
笔记·区块链·智能合约
傻小胖1 天前
21.ETH-权益证明-北大肖臻老师客堂笔记
笔记·区块链
硅基流动1 天前
硅基流动 × ValueCell:8K+Star,去中心化金融智能体加速投资决策
金融·去中心化·区块链
devmoon1 天前
使用 Hardhat 在 Polkadot Hub 测试网部署基础 Solidity 合约(完整实战指南)
web3·区块链·智能合约·波卡·hardhat
威胁猎人1 天前
【黑产大数据】2025年全球KYC攻击风险研究报告
大数据·区块链
焦点链创研究所1 天前
去中心化实体基础设施网络的崛起:比较分析
网络·去中心化·区块链
MicroTech20251 天前
微算法科技(NASDAQ :MLGO)量子测量区块链共识机制:保障数字资产安全高效存储与交易
科技·安全·区块链
区块链蓝海2 天前
Ardor v2.6.0 正式发布:Nxt迁移完成,Ardor迈入多链协同新阶段
人工智能·区块链