fuels-rs 合约调用参数详解:用 CallParameters 向 Fuel 合约转发资产与 Gas
本文基于 fuels-rs 官方文档 Call parameters 展开,系统讲解 Fuel Network Rust SDK 中合约调用参数(Call Parameters)的三个核心字段——金额、资产 ID、转发 Gas——的含义、默认值与底层实现;读完你可以掌握如何通过 CallParameters 在发起合约调用时向合约转发任意资产、精确控制单次调用的 Gas 上限,并理解 SDK 对非 #[payable] 方法的防护机制。
合约调用参数是什么
在 fuels-rs 中,每次向合约发起调用时都可以附带一组「调用参数」,用于向合约转发 coins。文档将其归纳为三个字段:
- Amount(金额):本次调用随交易转发给合约的资产数量;
- Asset ID(资产 ID):转发的资产种类;
- Gas forwarded(转发 Gas):转发给合约实际执行的 Gas 额度。
这三个字段在 SDK 中被封装为 CallParameters 结构体,定义在 contract_call.rs 中:
#[derive(Debug, Clone)]
pub struct CallParameters {
amount: u64,
asset_id: Option<AssetId>,
gas_forwarded: Option<u64>,
}
从源码结构看,asset_id 与 gas_forwarded 都是 Option 类型,意味着两者均可缺省;而 amount 是裸的 u64。当 asset_id 缺省时,SDK 会自动回退到链上的 base asset——这一行为发生在 ContractCall::data() 中,即 contract_call.rs 的如下映射逻辑:
Ok(ContractCallData {
amount: self.call_parameters.amount(),
asset_id: self.call_parameters.asset_id().unwrap_or(base_asset_id), // 未指定则回退 base asset
contract_id: self.contract_id,
fn_selector_encoded: self.encoded_selector.clone(),
encoded_args,
gas_forwarded: self.call_parameters.gas_forwarded,
})
构造与配置:new 与链式 builder
CallParameters 提供两种构造方式,均定义在 contract_call.rs:
CallParameters::new(amount, asset_id, gas_forwarded):一次性显式指定全部三个字段;CallParameters::default()配合链式 builder 方法with_amount/with_asset_id/with_gas_forwarded,按需覆盖单个字段,其余保持默认。
默认值由 constants.rs 中的常量决定:
// ANCHOR: default_call_parameters
pub const DEFAULT_CALL_PARAMS_AMOUNT: u64 = 0;
// ANCHOR_END: default_call_parameters
结合 contract_call.rs 的 Default 实现,可以确认 CallParameters::default() 的语义为:金额 0、资产 ID 为 base asset(None 回退)、Gas 转发量不限制(None)。
向合约转发资产:完整示例
先看一个典型的 Sway 合约示例。仓库中的 contract_test 合约 使用 Sway 标准库的 msg_amount() 把交易带入的金额原样返回,这正是文档选用的演示合约:
// ANCHOR: msg_amount
#[payable]
fn get_msg_amount() -> u64 {
msg_amount()
}
// ANCHOR_END: msg_amount
注意方法上的 #[payable] 标注——它是接收资产转发的前提(详见后文 payable 校验一节)。
部署该合约后,在 Rust 侧通过 call_params 链式方法配置转发金额,完整示例见 examples/contracts/src/lib.rs:
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?; // This is an async call, `.await` it.
// ANCHOR_END: call_parameters
示例中转发 1_000_000 单位的 base asset(因为未调用 with_asset_id,按上文逻辑回退为链上 base asset)。合约方法 get_msg_amount 返回的 response.value 即该金额,读者可据此验证转发是否生效。
从调用链看,call_params 是 CallHandler 上的 builder 方法,实现位于 call_handler.rs。它把参数写入内部 ContractCall.call_parameters 字段,后续在构建交易时经由 ContractCall::data() 落到 ContractCallData,最终编码进合约调用的 context 中。
为什么 call_params 返回 Result:payable 校验
call_params 与链上其他 builder 方法不同,它的签名是 pub fn call_params(mut self, params: CallParameters) -> Result<Self>,返回 Result 是为了防止把资产转发给非 payable 方法。源码中的校验逻辑只有一行,但语义明确(call_handler.rs):
pub fn call_params(mut self, params: CallParameters) -> Result<Self> {
if !self.is_payable() && params.amount() > 0 {
return Err(error!(Other, "assets forwarded to non-payable method"));
}
self.call.call_parameters = params;
Ok(self)
}
其中 is_payable 标志由 abigen! 宏从合约 ABI 中解析 #[payable] 注解读取。以仓库的 payable_annotation 测试合约 为例,它同时定义了带标注与不带标注的两个方法:
abi TestContract {
#[payable]
fn payable() -> u64;
fn non_payable() -> u64;
}
对应的 e2e 测试 contracts.rs 验证了校验行为:向 non_payable 转发 100 单位资产会直接返回编译期之外的运行时错误:
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
注意(原文档强调):向合约调用转发 Gas 始终是被允许的,即使目标方法没有
#[payable]标注。上面测试的后半段(contracts.rs)也验证了这一点:non_payable().call_params(CallParameters::default().with_gas_forwarded(20_000))可以正常执行并成功返回。
默认行为:不设置参数会转发什么
如果既不调用 call_params 也不显式传入 CallParameters::default(),SDK 的默认行为是:转发整笔交易的 gas limit,而不是转发给单次合约调用。换言之,gas_forwarded 缺省(None)等价于「把交易层 Gas 上限全部作为本次调用的可用 Gas」。
若你只想显式走一遍默认路径,可以像 examples/contracts/src/lib.rs 这样写:
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
注意即使在这里 call_params 也返回 Result,链式调用中必须用 ? 处理。
gas_forwarded:为单次调用设定 Gas 上限
gas_forwarded 参数定义的是本次合约调用本身的 Gas 上限,区别于 TxPolicies 中配置的整笔交易的 script_gas_limit。文档给出的约束关系是:
- 调用级 Gas 上限受交易级 Gas 上限约束;
- 如果把
gas_forwarded设置得比交易实际可用的 Gas 还大,则实际转发的就是全部可用 Gas(而不是报错或取你设置的值)。
官方示例完整展示了两个层面的配合,见 examples/contracts/src/lib.rs:
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
这里交易层允许消耗最多 1_000_000 Gas,而真正转发给合约执行的只有 4300 Gas。这种分层控制在多调用(multi-call)场景中尤为有用:你可以让单笔交易里的某个调用只拿到有限的 Gas,从而隔离资源消耗。
速查表与小结
| 字段 | 类型 | 默认值 | 设置方法 | 语义 |
|---|---|---|---|---|
amount |
u64 |
0(见 constants.rs) |
with_amount |
转发资产的单位数量 |
asset_id |
Option<AssetId> |
None,回退 base asset |
with_asset_id |
转发的资产种类 |
gas_forwarded |
Option<u64> |
None,转发交易 gas limit |
with_gas_forwarded |
本次合约调用的 Gas 上限,受交易 gas limit 约束 |
小结:在 fuels-rs 中,CallParameters 是「交易级策略」(TxPolicies)之外、针对单次合约调用的资源控制手段。核心要点有四:
- 通过
CallParameters::default().with_amount(...)等 builder 方法配置,并以.call_params(params)?链入调用; asset_id缺省时自动使用链上 base asset(见 ContractCall::data 的回退逻辑);call_params返回Result,向非#[payable]方法转发非零金额会被 SDK 在本地拦截并报assets forwarded to non-payable method(见 call_handler.rs);- 转发 Gas 不受 payable 限制,而
gas_forwarded与交易script_gas_limit是「调用级 vs 交易级」两层独立的 Gas 预算。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00