首页
/ fuels-rs 合约调用全指南:从 ABI 绑定、方法调用到结果解析与参数配置

fuels-rs 合约调用全指南:从 ABI 绑定、方法调用到结果解析与参数配置

2026-09-08 11:28:08作者:董宙帆

本篇技术指南聚焦 Fuel Network Rust SDK(fuels-rs)中"如何调用已部署合约"这一完整链路:从为合约生成类型安全的方法绑定、构建与执行一次合约调用,到读取返回值与日志,再到通过 CallParametersTxPolicies 精细化控制转账金额、资产与 Gas,以及将"提交交易"与"读取结果"分离的异步编程范式。读完本文,你将能够基于 SDK 编写出可运行的合约调用代码,并理解其背后 CallHandlerCallResponse 等核心类型的工作方式。本文以 docs/src/calling-contracts/index.md 为核心骨架,全部示例均可在本仓库源码中找到对应实现。

部署之后,你会想对合约做什么

当你参考 部署章节 完成合约部署后,通常会立刻面对四类需求:

  1. 调用合约中的方法(Read / Write);
  2. 配置调用参数与交易策略(如 Gas、费用、资产转账);
  3. 在调用中携带或转发 coins 与 gas;
  4. 读取并解读合约返回的值与日志。

本仓库对"调用合约"这一主题维护了一个完整的文档子章节,位于 docs/src/calling-contracts 目录下,包含基础调用、调用参数、交易策略、返回响应、日志、多合约调用、模拟执行、费用估算、自定义资产转移与自定义输入输出、低层级调用、变量输出、跨钱包调用等专题。本文先打通主线,再逐层展开这些可配置面。

一次最简单的合约调用

假设你在 Sway 侧编写了一个合约,其 ABI 中包含两个方法 initialize_counter(u64)increment_counter(u64)。仓库中的 e2e/sway/contracts/contract_test/src/main.sw 正是这样一个典型合约:它通过 storage 持久化一个 counterinitialize_counter 负责写入初值,increment_counter 读取旧值并累加后写回,最后都返回当前的计数值。

在 Rust 侧,一次典型的调用流程如下(完整示例见 examples/contracts/src/lib.rs):

// 通过 abigen! 宏读取合约的 ABI JSON,
// 把合约的方法以类型安全的方式生成到 MyContract 上
abigen!(Contract(
    name = "MyContract",
    abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));

// 用合约 ID 与签名钱包创建合约实例,之后即可调用链上方法
let contract_instance = MyContract::new(contract_id_2, wallet);

let response = contract_instance
    .methods()
    .initialize_counter(42) // 构建一次 ABI 调用
    .call()                 // 执行网络调用
    .await?;                // 合约调用是异步的

assert_eq!(42, response.value);

let response = contract_instance
    .methods()
    .increment_counter(10)
    .call()
    .await?;

assert_eq!(52, response.value);

这一小段代码体现了 fuels-rs 合约调用的核心心智模型:abigen! 生成绑定 → MyContract::new 创建实例 → .methods().方法名(参数) 构建调用 → .call().await 执行并拿到 CallResponse。其中 contract_idwallet 来自部署环节,例如通过 Contract::load_from(...).deploy(&wallet, TxPolicies::default()).await?.contract_id 获取(见 examples/contracts/src/lib.rs)。上述示例全程使用默认配置,足以覆盖大多数只读或纯逻辑调用。

为什么到处都在链式 .call().await?

细心的话你会发现 SDK 示例中总是出现 .call().await?(或 unwrap())连写的调用链,这并非巧合,而是由 fuels-rs 的异步 + Result 设计决定的(详见 call-response.md):

  1. 调用执行前需要二选一.call()(真实提交交易)还是 .simulate()(模拟执行,不产生状态变更,见 simulation.md);
  2. 合约调用是异步的,你可以选择就地 .await,也可以并发发起多个任务,充分利用 Rust 的 async 生态;
  3. 合约调用返回的是 Result<CallResponse, Error>,因此通常需要 ?.unwrap() 取出来。

CallResponse 的组成

Result 被解开后,你拿到的是一个 CallResponse,其定义位于 packages/fuels-programs/src/responses/call.rs,主要包含四个字段:

  • value:合约方法返回的真实值,其 Rust 类型由 ABI 精确推导。例如 Sway 的 u64 对应 Rust 的 u64;Sway 元组 (u8, bool) 对应 Rust 的 (u8, bool);若合约返回自定义结构体 MyStruct { u64, b256 }abigen! 会在编译期生成同名的 Rust struct,其中 b256 对应 [u8; 32]
  • receipts:该次合约调用产生的全部 receipts
  • gas_used:本次合约调用消耗的 Gas 量;
  • tx_id:对应已提交交易的 ID。

Result 的判错与解错

对合约调用返回的 Result,可以直接用 is_ok / is_err 判断成功与否:

let is_ok = response.is_ok();
let is_error = response.is_err();

is_err() 为真时,可用 unwrap_err 取出错误信息:

if response.is_err() {
    let err = response.unwrap_err();
    println!("ERROR: {:?}", err);
};

将"提交"与"取值"分离:.submit() 模式

默认的 .call() 会在一次 await 内同时完成交易提交与结果轮询。但某些场景(例如需要精确控制等待时机、或想在后台异步运行交易)要求把提交交易获取返回值拆开。此时可以使用 .submit()(见 examples/contracts/src/lib.rs):

// submit() 只负责把交易提交到链上,返回提交结果
let response = contract_instance
    .methods()
    .initialize_counter(42)
    .submit()
    .await?;

// 之后在需要取值时再调用 response() 等待执行完成并取回 value
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;

assert_eq!(42, value);

从 SDK 的调用抽象看,.call().submit() 分别对应不同的"完成度":.call() 内部会替你完成提交并解析响应;.submit() 则把解析动作推迟到你显式调用 response.response() 时。相关的提交与响应载体分别位于 packages/fuels-programs/src/responses/submit.rspackages/fuels-programs/src/responses/call.rs

配置合约调用的各类参数

一次合约调用可以被精细配置。fuels-rs 将其拆分为两类截然不同的配置对象:

交易策略 TxPolicies

TxPolicies 描述的是承载这次调用的交易整体策略,包括(定义见 transaction.rs 中的 tx_policies_struct):

  1. Tip:支付给区块生产者的优先费,用于提升交易被打包的优先级;
  2. Witness Limit:交易允许携带的最大 witness 数据量;
  3. Maturity:在此之前交易无法被打包进块的"成熟块高";
  4. Expiration:在此之后交易无法被打包进块的"过期块高";
  5. Max Fee:本交易可支付的最高费用;
  6. Script Gas Limit:交易执行脚本代码可消耗的 Gas 上限。

Script Gas Limit 未显式设置时,SDK 会在后台估算执行所需 Gas 并自动设置为上限;当 Witness Limit 未设置时,SDK 会按交易构建器中所有 witness 与签名的大小自动补齐。

通过链式方法 with_tx_policies 传入自定义策略(见 examples/contracts/src/lib.rs):

let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();

let tx_policies = TxPolicies::default()
    .with_tip(1)
    .with_script_gas_limit(1_000_000)
    .with_maturity(0)
    .with_expiration(10_000);

let response = contract_methods
    .initialize_counter(42)          // 选择合约方法
    .with_tx_policies(tx_policies)   // 链式挂上交易策略
    .call()                          // 执行调用
    .await?;                         // 异步等待

同样地,TxPolicies 也适用于部署合约、转账资产等场景——凡接受它的方法都可传入同一份策略(详见 tx-policies.md)。

调用参数 CallParameters

CallParameters 描述的则是这一次具体调用要携带与转发的资源,共三个字段:

  1. Amount:转发的 token 数量;
  2. Asset ID:转发的资产 ID(默认基础资产 base asset);
  3. Gas forwarded:转发给被调用合约的 Gas 上限。

这三个参数常配合 Sway 侧上下文读取使用。例如 contract_test 合约中标注了 #[payable]get_msg_amount(),通过 Sway 标准库的 msg_amount() 返回本次调用携带的金额(见 main.sw):

#[payable]
fn get_msg_amount() -> u64 {
    msg_amount()
}

在 Rust 侧,创建 CallParameters 实例并用链式方法 call_params 传入(见 examples/contracts/src/lib.rs):

let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();

let tx_policies = TxPolicies::default();

// 转发 1_000_000 数量的基础资产给合约
let call_params = CallParameters::default().with_amount(1_000_000);

let response = contract_methods
    .get_msg_amount()                    // 选择合约方法
    .with_tx_policies(tx_policies)       // 挂交易策略
    .call_params(call_params)?           // 挂调用参数
    .call()                              // 执行调用
    .await?;

关于 payable 的校验call_params 之所以返回 Result,是为了防止你向未标注 #[payable] 的合约方法转发资产。若将 Amount 设为非 0 值却调用非 payable 方法(如 non_payable),SDK 会直接报错。相应的端到端测试见 e2e/tests/contracts.rs 中的 non_payable_params。需要注意:向合约调用转发 Gas 始终被允许,与目标方法是否 payable 无关。

使用默认值

无论 TxPolicies 还是 CallParameters,都可以直接使用 ::default() 采用默认值(默认 CallParameters 常量定义见 constants.rsdefault_call_parameters):

let response = contract_methods
    .initialize_counter(42)
    .call_params(CallParameters::default())?
    .call()
    .await?;

gas_forwarded 与默认 Gas 行为

CallParameters 中的 gas_forwarded 语义需要特别注意(详见 call-params.md):

  • 它约束的是单次合约调用本身的 Gas 上限,而非整笔交易的 Gas 上限,因此会受交易级 Gas 限制的约束;
  • 若它被设置成大于可用 Gas 的值,SDK 会把当前所有可用 Gas 全部转发过去;
  • 若不设置调用参数,或使用 CallParameters::default(),则默认行为是把交易的 Gas limit 整体转发给该次调用。

扩展阅读:合约调用能力全景

index.md 指出"接下来我们看看如何进一步配置合约调用中众多不同的参数"。在深入源码前,以下专题子文档可帮助你按需组合这些能力:

从源码结构看调用链的实现

如果希望进一步理解 .methods() 之后发生了什么,可以顺着以下源码路径阅读:

从这些文件的分工可以推断,fuels-rs 把"构建 ABI 调用 → 组装交易(注入 TxPolicies 与 CallParameters)→ 提交/模拟 → 解析 receipts 得到返回值"拆成了职责单一的多层组件:CallHandler 负责把一次 .methods().foo(args) 攒成可执行的调用,最终由交易层统一提交给 Provider 与 Fuel 节点。

小结

合约调用是 fuels-rs 日常开发中使用频率最高的能力。其核心心法可以浓缩为一条调用链:abigen! 生成 ABI 绑定 → Contract::new(contract_id, wallet) 得到实例 → .methods().foo(args) 选方法 → .with_tx_policies(...) 配交易策略 → .call_params(...) 配转发资源 → .call() / .submit() / .simulate() 决定执行方式 → .await 后从 CallResponse.value 取返回值。掌握了这条主链,再结合 index.md 导航下 docs/src/calling-contracts 各专题对多合约调用、日志、费用估算、模拟执行等能力的扩展,你就能在 fuels-rs 中自由编排各种复杂链上交互。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389