合约交互 #
一、合约交互概述 #
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