# call、staticcall、delegatecall  使用

By [Bob](https://paragraph.com/@bob-25) · 2024-11-21

---

在 Solidity 中，`call`、`staticcall` 和 `delegatecall` 是强大的低级操作，用于与其他合约交互或在当前上下文中执行外部合约的逻辑。掌握它们的用法和区别对于智能合约开发非常重要。

本文将详细介绍它们的功能、适用场景、代码示例，以及使用时需要注意的潜在问题。

**1.** `call`
-------------

### **功能**

`call` 是一种通用方法，用于调用另一个合约的函数或发送 ETH。它可以调用目标合约的任意函数，包括不存在的函数（在这种情况下不会抛出错误）。

### **特点**

*   可读写目标合约的状态。
    
*   支持附带 ETH 发送。
    
*   返回两个值：
    
    *   调用是否成功 (`bool`)。
        
    *   调用返回的数据 (`bytes memory`)。
        

### **使用场景**

*   调用外部合约的任意函数。
    
*   向合约或外部账户发送 ETH。
    

### **代码示例**

    pragma solidity ^0.8.0;
    
    contract CallExample {
        function callFunction(address target, uint256 value) external returns (bool, bytes memory) {
            // 通过 call 调用目标合约的 setValue(uint)
            (bool success, bytes memory data) = target.call(
                abi.encodeWithSignature("setValue(uint256)", value)
            );
            require(success, "Call failed");
            return (success, data);
        }
    
        function sendEth(address payable target) external payable {
            // 使用 call 发送 ETH
            (bool success, ) = target.call{value: msg.value}("");
            require(success, "Send ETH failed");
        }
    }
    

**2.** `staticcall`
-------------------

### **功能**

`staticcall` 是一种只读调用方法，用于调用目标合约的视图或纯函数。它不允许改变状态，因此更安全且节省 Gas。

### **特点**

*   只能调用 `view` 或 `pure` 修饰的函数。
    
*   无法修改状态或发送 ETH。
    
*   返回两个值：
    
    *   调用是否成功 (`bool`)。
        
    *   调用返回的数据 (`bytes memory`)。
        

### **使用场景**

*   查询目标合约的状态。
    
*   调用只读逻辑以避免状态改变。
    

### **代码示例**

    pragma solidity ^0.8.0;
    
    contract StaticCallExample {
        function staticCallFunction(address target) external view returns (uint256) {
            // 使用 staticcall 调用目标合约的 storedValue()
            (bool success, bytes memory data) = target.staticcall(
                abi.encodeWithSignature("storedValue()")
            );
            require(success, "Staticcall failed");
            return abi.decode(data, (uint256));
        }
    }
    

**3.** `delegatecall`
---------------------

### **功能**

`delegatecall` 是一种在当前合约上下文中执行目标合约逻辑的方法。它会以调用合约的存储布局为准，执行目标合约中的代码。

### **特点**

*   使用调用合约的存储。
    
*   使用调用合约的 `msg.sender` 和 `msg.value`。
    
*   返回两个值：
    
    *   调用是否成功 (`bool`)。
        
    *   调用返回的数据 (`bytes memory`)。
        

### **使用场景**

*   实现合约代理（如升级逻辑）。
    
*   在共享存储布局的上下文中执行外部逻辑。
    

### **代码示例**

    pragma solidity ^0.8.0;
    
    contract DelegateCallExample {
        uint256 public storedValue;
    
        function delegateCallFunction(address target, uint256 value) external {
            // 使用 delegatecall 调用目标合约的 setValue(uint)
            (bool success, ) = target.delegatecall(
                abi.encodeWithSignature("setValue(uint256)", value)
            );
            require(success, "Delegatecall failed");
        }
    }
    
    // 被调用合约 (Library)
    contract Target {
        uint256 public storedValue;
    
        function setValue(uint256 value) external {
            storedValue = value; // 实际更新的是调用合约的存储
        }
    }
    

**4.** `call` vs `staticcall` vs `delegatecall`
-----------------------------------------------

![](https://storage.googleapis.com/papyrus_images/2b6f0b2102d71709d25e5f890163076e434fcc92653a0f444181ca86a9c7a575.png)

5\. 使用注意事项
----------

### call 的安全性

*   使用 call 调用外部合约时，目标合约可能会执行恶意代码。需要额外检查返回值并限制权限。
    
*   不要轻易使用 call 调用不可信的合约。
    

### delegatecall 的存储风险

*   调用目标合约时，目标合约必须与调用合约共享相同的存储布局，否则可能导致存储冲突。
    
*   使用 delegatecall 需要确保调用的是受信任的逻辑。
    

### Gas 消耗与返回值处理

*   注意低级调用的 Gas 使用，避免由于 Gas 不足导致调用失败。
    
*   低级调用（call、staticcall 和 delegatecall）不会自动抛出异常，需要手动处理返回值。
    

### 不要使用简写类型

      无论是使用abi.encodeWithSelect 还是 abi.encodeWithSignature ，参数中方法名后的参数一定不要使用简写类型。

    // 错误写法
    (bool success, ) = target.call(abi.encodeWithSignature("setValue(uint)", value));
    

     应为在编译的过程中，编译器会将简写类型（如：uint）转换成uint256，这将导致，你的签名或选择器不匹配，导致执行失败。

**6\. 实践案例：实现代理合约**
-------------------

以下是使用 `delegatecall` 的代理合约的示例，用于实现合约逻辑的动态升级。

### **代码示例**

    pragma solidity ^0.8.0;
    
    // 逻辑合约
    contract LogicContract {
        uint256 public storedValue;
    
        function setValue(uint256 value) external {
            storedValue = value;
        }
    }
    
    // 代理合约
    contract ProxyContract {
        address public logicContract;
    
        constructor(address _logicContract) {
            logicContract = _logicContract;
        }
    
        fallback() external payable {
            (bool success, ) = logicContract.delegatecall(msg.data);
            require(success, "Delegatecall failed");
        }
    }
    

### **运行过程**

1.  部署 `LogicContract`。
    
2.  部署 `ProxyContract`，将 `LogicContract` 的地址传递给其构造函数。
    
3.  通过代理合约调用 `setValue` 方法，`storedValue` 实际存储在 `ProxyContract` 中
    

**7\. 结论**
----------

`call`、`staticcall` 和 `delegatecall` 是 Solidity 中的基础工具，可以用来实现灵活的合约交互和逻辑扩展。在使用这些低级调用时，需要仔细处理返回值、安全性以及存储一致性问题。

通过合理使用这些工具，您可以设计功能强大且可扩展的智能合约体系，同时避免潜在的安全漏洞。

---

*Originally published on [Bob](https://paragraph.com/@bob-25/call-staticcall-delegatecall)*
