合约交互 #

一、合约交互概述 #

Solidity合约可以通过多种方式与其他合约交互:

方式 说明 上下文
直接调用 通过接口调用 目标合约
call 低级调用 目标合约
delegatecall 委托调用 调用者合约
staticcall 静态调用 目标合约

二、直接调用 #

2.1 通过接口调用 #

solidity
interface IToken {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address owner) external view returns (uint256);
}

contract TokenUser {
    IToken public token;
    
    constructor(address tokenAddress) {
        token = IToken(tokenAddress);
    }
    
    function transferTokens(address to, uint256 amount) public returns (bool) {
        return token.transfer(to, amount);
    }
    
    function getBalance(address owner) public view returns (uint256) {
        return token.balanceOf(owner);
    }
}

2.2 通过合约实例调用 #

solidity
contract Counter {
    uint256 public count;
    
    function increment() public {
        count++;
    }
    
    function getCount() public view returns (uint256) {
        return count;
    }
}

contract CounterUser {
    Counter public counter;
    
    constructor(address counterAddress) {
        counter = Counter(counterAddress);
    }
    
    function increment() public {
        counter.increment();
    }
    
    function getCount() public view returns (uint256) {
        return counter.count();
    }
}

三、call调用 #

3.1 基本用法 #

solidity
contract CallDemo {
    event CallResult(bool success, bytes data);
    
    function callFunction(
        address target,
        bytes memory data
    ) public returns (bool, bytes memory) {
        (bool success, bytes memory result) = target.call(data);
        emit CallResult(success, result);
        return (success, result);
    }
    
    function callWithSelector(
        address target,
        bytes4 selector,
        uint256 value
    ) public returns (bool, bytes memory) {
        bytes memory data = abi.encodeWithSelector(selector, value);
        return target.call(data);
    }
}

3.2 发送ETH #

solidity
contract SendETH {
    event Sent(address to, uint256 amount, bool success);
    
    function sendViaCall(address payable to) public payable {
        (bool success, ) = to.call{value: msg.value}("");
        require(success, "Transfer failed");
        emit Sent(to, msg.value, success);
    }
    
    function sendWithGas(address payable to, uint256 gasAmount) public payable {
        (bool success, ) = to.call{value: msg.value, gas: gasAmount}("");
        require(success, "Transfer failed");
    }
}

3.3 调用函数 #

solidity
contract CallFunction {
    function callTransfer(
        address token,
        address to,
        uint256 amount
    ) public returns (bool) {
        bytes memory data = abi.encodeWithSignature(
            "transfer(address,uint256)",
            to,
            amount
        );
        
        (bool success, bytes memory result) = token.call(data);
        require(success, "Call failed");
        
        return abi.decode(result, (bool));
    }
}

四、delegatecall #

4.1 基本概念 #

delegatecall在调用者的上下文中执行目标合约代码,保持msg.sender和msg.value不变。

solidity
contract Implementation {
    uint256 public value;
    
    function setValue(uint256 _value) public {
        value = _value;
    }
}

contract Proxy {
    address public implementation;
    uint256 public value;
    
    constructor(address _implementation) {
        implementation = _implementation;
    }
    
    function delegateSetValue(uint256 _value) public {
        (bool success, ) = implementation.delegatecall(
            abi.encodeWithSignature("setValue(uint256)", _value)
        );
        require(success, "Delegatecall failed");
    }
}

4.2 代理模式 #

solidity
contract Proxy {
    address public implementation;
    address public admin;
    
    constructor(address _implementation) {
        implementation = _implementation;
        admin = msg.sender;
    }
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }
    
    function upgrade(address _newImplementation) public onlyAdmin {
        implementation = _newImplementation;
    }
    
    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
    
    receive() external payable {}
}

五、staticcall #

5.1 只读调用 #

solidity
contract StaticCallDemo {
    function staticCallBalance(
        address token,
        address owner
    ) public view returns (uint256) {
        bytes memory data = abi.encodeWithSignature(
            "balanceOf(address)",
            owner
        );
        
        (bool success, bytes memory result) = token.staticcall(data);
        require(success, "Staticcall failed");
        
        return abi.decode(result, (uint256));
    }
}

六、安全注意事项 #

6.1 检查返回值 #

solidity
contract SafeCall {
    function safeCall(address target, bytes memory data) public returns (bytes memory) {
        (bool success, bytes memory result) = target.call(data);
        require(success, "Call failed");
        return result;
    }
}

6.2 重入保护 #

solidity
contract ReentrancyGuard {
    bool private _locked;
    
    modifier nonReentrant() {
        require(!_locked, "Reentrant call");
        _locked = true;
        _;
        _locked = false;
    }
    
    mapping(address => uint256) public balances;
    
    function withdraw() public nonReentrant {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "No balance");
        
        balances[msg.sender] = 0;
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "Transfer failed");
    }
}

七、总结 #

合约交互要点:

方式 上下文 使用场景
直接调用 目标合约 普通调用
call 目标合约 低级调用
delegatecall 调用者 代理模式
staticcall 目标合约 只读调用

使用建议:

  • 优先使用直接调用
  • 检查call返回值
  • 使用重入保护
  • delegatecall用于代理模式

接下来,让我们学习创建合约!

最后更新:2026-03-27