首页
/ WTF-Solidity 第21讲:在 Solidity 中调用其他合约——四种交互方式与 ETH 转账实战

WTF-Solidity 第21讲:在 Solidity 中调用其他合约——四种交互方式与 ETH 转账实战

2026-09-15 18:02:55作者:仰钰奇

在区块链上,合约之间相互调用不仅让代码具备跨合约的可复用性,也是整个 Ethereum DApp 生态得以运转的基础——许多 Web3 应用(例如 yield farming 协议)都依赖调用另一个合约来完成核心逻辑。本篇文章基于 WTF-Solidity 极简入门教程第 21 讲,完整讲解如何在已知目标合约地址与源码(或 ABI)的前提下,从 Solidity 中创建合约引用并调用其函数,并通过四种典型写法演示传地址、传合约变量、创建合约变量以及调用时附带转账 ETH 的完整流程,读完即可在 Remix 中独立复现。

调用已部署合约的基本思路

在 Solidity 中,一个合约调用另一个合约的函数,核心前提是知道目标合约的地址它的代码(或接口/ABI)。有了这两样,就可以在调用方合约中创建一个"引用",进而调用目标合约暴露出来的任何函数。

创建引用的语法非常直观:

_Name(_Address)
  • _Name:目标合约名,必须与目标合约源码(或 ABI)中声明的合约名保持一致;
  • _Address:目标合约的部署地址。

拿到引用后,用点号调用目标函数即可:

_Name(_Address).f()

其中 f() 就是要调用的目标函数。

目标合约 OtherContract 解析

为了方便演示,先准备一个简单的、专门用于被其他合约调用的目标合约 OtherContract。该合约完整实现在仓库源码 21_CallContract/CallContract.sol(西语版源码位于 Languages/es/21_LlamarContrato_es/CallContract.sol),内容如下:

contract OtherContract {
    uint256 private _x = 0; // 状态变量 _x

    // 收到 ETH 时触发的事件,记录转入金额和剩余 gas
    event Log(uint amount, uint gas);

    // 返回合约的 ETH 余额
    function getBalance() view public returns(uint) {
        return address(this).balance;
    }

    // 可设置状态变量 _x,并且可接收 ETH(payable)
    function setX(uint256 x) external payable{
        _x = x;
        // 如果转入 ETH,则释放 Log 事件
        if(msg.value > 0){
            emit Log(msg.value, gasleft());
        }
    }

    // 读取 _x 的值
    function getX() external view returns(uint x){
        x = _x;
    }
}

这个合约包含一个状态变量 _x、一个在收到 ETH 时触发的事件 Log,以及三个函数:

  • getBalance()view public 函数,通过 address(this).balance 返回合约当前的 ETH 余额;
  • setX(uint256 x)external payable 函数,既能把状态变量 _x 设置为传入的 x,还能顺便接收转入的 ETH;当 msg.value > 0 时触发 Log 事件,记录转入金额与 gasleft()
  • getX()external view 函数,读取 _x 的当前值。

从源码结构可以看出,OtherContract 故意同时提供了"读"(getXgetBalance)与"写/收币"(setX)两种函数,覆盖了调用方在交互时可能遇到的绝大多数场景:读取状态、写入状态、附带转账。

调用方合约 CallContract 与四种交互方式

接下来在 Remix 中同时编译并部署 OtherContractCallContract 两个合约,其中 CallContract 包含四种典型的调用写法,全部实现在 21_CallContract/CallContract.sol

contract CallContract{
    function callSetX(address _Address, uint256 x) external{
        OtherContract(_Address).setX(x);
    }

    function callGetX(OtherContract _Address) external view returns(uint x){
        x = _Address.getX();
    }

    function callGetX2(address _Address) external view returns(uint x){
        OtherContract oc = OtherContract(_Address);
        x = oc.getX();
    }

    function setXTransferETH(address otherContract, uint256 x) payable external{
        OtherContract(otherContract).setX{value: msg.value}(x);
    }
}

部署阶段在 Remix 中依次选择 OtherContractCallContract 合约进行 Deploy 操作,得到两个合约实例地址:

在 Remix 中编译并部署 OtherContract 与 CallContract 合约

方式一:传入合约地址,即时创建引用

这是最直接的方式:在函数参数中接收目标合约的 address,在函数体内用 OtherContract(_Address) 创建引用并立刻调用目标函数。以调用 OtherContract.setX 为例:

function callSetX(address _Address, uint256 x) external{
    OtherContract(_Address).setX(x);
}

实操步骤:

  1. 复制已部署的 OtherContract 合约地址,作为 callSetX 的第一个参数 _Address 填入;
  2. 第二个参数 x 填入希望写入的值,例如 123
  3. 点击 transact 发起交易,交易成功后调用 OtherContract 自己的 getX 函数,可以看到返回值变为 123,证明调用生效。

在 Remix 中调用 callSetX,传入 OtherContract 地址并将 x 设为 123

方式二:直接传入合约变量

方式一要求参数类型是 address;而 Solidity 允许直接把参数类型声明为合约名 OtherContract,把"合约引用"作为函数参数传递。下面例子演示了通过这种方式调用目标合约的 getX()

function callGetX(OtherContract _Address) external view returns(uint x){
    x = _Address.getX();
}

注意一个底层细节:参数 OtherContract _Address 的底层类型仍然是 address。也就是说,在生成的 ABI 中、以及在调用 callGetX 时传入的参数,全部都是 address 类型——合约类型参数只是编译期层面的语法糖,链上交互层面传递的依然是 20 字节地址。

实操步骤:复制 OtherContract 合约地址填入 callGetX 参数,调用后即可成功读取到 x 的值(此函数声明为 view,不会产生状态变更,只做链上读取)。

方式三:创建合约变量,再通过变量调用

除了在参数位置临时构造引用,还可以先把引用保存到合约类型的局部变量里,再通过该变量调用目标函数。示例将 OtherContract 的引用存入变量 oc

function callGetX2(address _Address) external view returns(uint x){
    OtherContract oc = OtherContract(_Address);
    x = oc.getX();
}

这种方式与方式一的区别在于:方式一是 OtherContract(_Address).f() 的"一行流"写法,方式三则把引用赋值给显式变量 oc,适合需要对同一个目标合约连续调用多个函数、或需要把引用传入其他内部逻辑的场合,可读性和复用性更好。

方式四:调用合约并附带转账 ETH

当目标函数声明为 payable 时,调用方可以在调用同时向目标合约转账 ETH,语法为:

_Name(_Address).f{value: _Value}()
  • _Name:目标合约名;
  • _Address:目标合约地址;
  • f:目标函数名;
  • _Value:要转出的 ETH 数额,单位是 wei

OtherContract.setX 恰好是 payable 函数,因此下面这个 setXTransferETH 就通过调用 setX 实现了"设置状态 + 转账"两步合一:

function setXTransferETH(address otherContract, uint256 x) payable external{
    OtherContract(otherContract).setX{value: msg.value}(x);
}

这里把调用方收到的 msg.value 原封不动地通过 {value: msg.value} 转发给目标合约。

实操步骤:

  1. 复制 OtherContract 合约地址,填入 setXTransferETH 的参数 otherContract
  2. 参数 x 填入目标状态值;
  3. 在 Remix 的 VALUE 输入框中填入 10(单位选择 ETH),再点击 transact,即会在调用 setX 的同时向 OtherContract 转入 10 ETH。

在 Remix 中调用 setXTransferETH,同时向目标合约转入 10 ETH

转账完成后,验证余额变化有两条途径:

  • 事件验证OtherContract.setX 在收到 ETH 时会触发 Log(msg.value, gasleft()),在 Remix 的事件日志区(Logs)可以查到转入金额与剩余 gas;
  • 函数验证:调用 OtherContractgetBalance(),返回值为 10000000000000000000(即 10 ETH,单位 wei),与转入金额吻合。

通过 getBalance 返回值与 Log 事件日志验证目标合约 ETH 余额变化

调用交互中的几个关键细节

结合上面四种写法和源码,有几个值得注意的工程细节:

  • 地址必须与 ABI 匹配_Name(_Address) 中的 _Name 决定 Solidity 如何解析函数签名,如果实际部署在 _Address 上的合约与声明的 ABI 不一致(例如函数签名对不上),调用会回滚或产生不可预期的行为;
  • 底层仍是 address:如方式二所强调,合约类型参数在 ABI 层面对应 address,理解这一点有助于阅读生成的 ABI 或与其他工具链交互;
  • payable 是转账的前提:只有目标函数声明为 payable{value: ...} 才能生效;对非 payable 函数附带 value 会导致调用失败;
  • msg.valuegasleft()msg.value 是本次调用实际携带的 ETH 数额(wei),gasleft() 返回调用点剩余的 gas,二者被组合进 Log 事件用于链上审计;
  • 异常会向上传播:被调用合约执行失败时,整个调用链会一并回滚,调用方合约应在上层设计好防护(例如 try/catch 或前置校验);
  • 交易与读取的区分callSetXsetXTransferETH 修改目标合约状态,需要发送交易;callGetXcallGetX2view 函数,直接本地读取即可,无需消耗 gas 发起交易。

总结

本讲围绕"已知合约代码(或 ABI)和地址,如何调用已部署合约"这一主题,介绍了创建合约引用的核心语法 _Name(_Address),并通过 CallContract 中的四个函数展示了四种交互写法:传入地址即时调用(callSetX)、传入合约类型参数(callGetX)、创建合约变量后调用(callGetX2),以及使用 {value: ...} 在调用同时转账 ETH(setXTransferETH)。相关完整代码可在仓库 21_CallContract/CallContract.sol 中查看,西语版对应 Languages/es/21_LlamarContrato_es/CallContract.sol,建议在 Remix 中配合上述截图步骤亲手部署验证一遍,以加深对合约间调用机制的理解。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
34
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.21 K
2.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
952
1.87 K
docsdocs
暂无描述
Markdown
906
5.84 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
537
614
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
864
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
4.29 K
1.04 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.4 K
1.48 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
550
402
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.19 K
348