` 与 UTXO 模型)
Sway 资产转账指南深入理解 FuelVM 的transfer()与 UTXO 模型【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/swaySway 智能合约基于 FuelVM 的 UTXO 模型合约铸造的资产可以被转出合约而标准库std::asset模块提供的transfer()函数正是实现资产跨实体流转的核心入口。本文以官方参考文档 transfer/index.md 为主干结合标准库 asset.sw 的源码实现与官方示例完整讲解如何向地址Address与合约ContractId转账资产、函数底层的工作机制、回退Revert条件及实操注意事项读完即可在 Sway 合约中安全、正确地编写资产转账逻辑。背景为什么 Sway 需要transfer()Sway 运行在 FuelVM 之上其资产管理采用UTXOUnspent Transaction Output模型参考 asset/index.md 的说明这与以太坊等基于账户Account based的模型不同在账户模型下余额集中记录在每个账户的状态中在 UTXO 模型下资产以未花费输出coin的形式存在资产可以转出创建它的合约因此合约外的资产分布信息并不集中在一个地方。Sway 通过标准库内置的资产操作在合约层面部分复刻账户模型的使用体验。完整的资产生命周期包括三类操作Mint铸造/创建见 mint/index.mdBurn销毁见 burn.mdTransfer转账/发送即本文主题见 transfer/index.md同时还可通过 balance.md 查询合约余额。transfer()解决的核心问题当合约持有某类资产例如用户存入的本金、合约自己铸造的 token需要将其发送给任意实体外部地址或另一个合约时如何正确操作 FuelVM 的底层转账指令。transfer()函数总览一个函数两类目标标准库std::asset模块源码位于 asset.sw提供了一个面向任意实体的转账函数transfer(to: Identity, asset_id: AssetId, amount: u64)其签名设计非常统一向谁转to、转什么asset_id、转多少amount。to参数类型为Identity枚举因此一个函数即可覆盖两种目标目标实体Identity变体底层实际调用外部地址Identity::Address(Address)transfer_to_address()私有函数智能合约Identity::ContractId(ContractId)force_transfer_to_contract()私有函数从源码asset.sw可以看到transfer()的分发逻辑pub fn transfer(to: Identity, asset_id: AssetId, amount: u64) { match to { Identity::Address(addr) transfer_to_address(addr, asset_id, amount), Identity::ContractId(id) force_transfer_to_contract(id, asset_id, amount), }; }官方文档中对两种场景的详细说明分别位于transfer/address.md转账到地址transfer/contract.md转账到合约transfer/address-or-contract.md使用Identity统一处理两者的通用写法。通用写法使用 Identity 统一转账导入函数后在官方代码示例anchortransfer中演示了面向任意实体的写法use std::asset::transfer; fn transferring_to() { let amount 10; let address 0x0000000000000000000000000000000000000000000000000000000000000001; let asset AssetId::base(); let user Identity::Address(Address::from(address)); let pool Identity::ContractId(ContractId::from(address)); transfer(user, asset, amount); // 转账给地址 transfer(pool, asset, amount); // 转账给合约 }转账到地址Address官方文档 transfer/address.md 与代码 anchortransfer_to_address见 lib.sw给出的完整示例use std::asset::transfer; fn transferring_to_address() { let amount 10; let address 0x0000000000000000000000000000000000000000000000000000000000000001; let asset AssetId::base(); let user Address::from(address); transfer(Identity::Address(user), asset, amount); }底层机制复用未使用的变量输出转账到地址与转账到合约在底层实现上完全不同。标准库中的transfer_to_address()asset.sw不会直接创建新输出而是先扫描当前交易的所有输出outputs寻找一个未使用的Output::Variable变量输出fn transfer_to_address(to: Address, asset_id: AssetId, amount: u64) { // maintain a manual index as we only have while loops in sway atm: let mut index 0; // If an output of type OutputVariable is found, check if its amount is // zero. As one cannot transfer zero coins to an output without a panic, a // variable output with a value of zero is by definition unused. let number_of_outputs output_count().as_u64(); while index number_of_outputs { if let Some(Output::Variable) output_type(index) { if let Some(0) output_amount(index) { asm(r1: to.bits(), r2: index, r3: amount, r4: asset_id) { tro r1 r2 r3 r4; }; return; } } index 1; } revert(FAILED_TRANSFER_TO_ADDRESS_SIGNAL); }实现要点通过 outputs.sw 提供的output_count()、output_type()、output_amount()遍历交易输出找到类型为Output::Variable且金额为 0的输出——由于向输出转账 0 金额必然 panic因此金额为 0 的变量输出被认定为未使用通过内联汇编执行tro指令Transfer Output将amount数量的asset_id资产写入该输出接收方为to若找不到可用的变量输出则调用revert(FAILED_TRANSFER_TO_ADDRESS_SIGNAL)回退交易。其中FAILED_TRANSFER_TO_ADDRESS_SIGNAL定义于 error_signals.swpub const FAILED_TRANSFER_TO_ADDRESS_SIGNAL 0xffff_ffff_ffff_0001;这也意味着调用转账到地址的函数前调用方外部脚本或钱包必须在交易中预留一个可复用的变量输出variable output否则标准库会直接回退。转账到地址的回退条件根据 asset.sw 的文档注释transfer_to_address在以下情况回退amount大于合约在该asset_id上的余额amount等于 0交易中没有空闲的变量输出即FAILED_TRANSFER_TO_ADDRESS_SIGNAL场景。转账到合约ContractId官方文档 transfer/contract.md 与代码 anchorforce_transfer_to_contract见 lib.sw给出的完整示例use std::asset::transfer; fn transferring_to_contract() { let amount 10; let address 0x0000000000000000000000000000000000000000000000000000000000000001; let asset AssetId::base(); let pool ContractId::from(address); transfer(Identity::ContractId(pool), asset, amount); }底层机制tr指令直传force_transfer_to_contract()asset.sw的实现比地址转账简单直接直接使用trTransfer指令fn force_transfer_to_contract(to: ContractId, asset_id: AssetId, amount: u64) { asm(r1: amount, r2: asset_id, r3: to.bits()) { tr r3 r1 r2; } }tr指令在 FuelVM 层面直接完成当前合约 → 目标合约的余额转移无需依赖交易的输出结构因此不要求预留变量输出。⚠️ 必须警惕永久丢失资产的风险函数名中的force与文档注释明确警告asset.sw这是无条件转账即使接收合约没有任何可取回这些资产的机制例如没有提供取款函数资产也会被转入若不加小心可能造成资产的永久损失PERMANENT LOSS OF COINS。因此向合约转账前务必确认接收合约具备对应的取款/回调机制或在业务设计上接受该资产被锁定的结果。转账到合约的回退条件根据 asset.swamount大于合约在该asset_id上的余额amount等于 0。参数详解Identity、AssetId 与 amounttransfer()的三个参数含义与构造方式如下to: Identity接收方标识两个变体Identity::Address(Address)通过Address::from(b256 值)构造Identity::ContractId(ContractId)通过ContractId::from(b256 值)构造。asset_id: AssetId要转账的资产标识。常见构造方式参见官方示例 native_asset/src/main.sw// 基础资产燃料币 let base_asset: AssetId AssetId::base(); // 由合约 ID 子标识合成唯一资产 let my_contract_id: ContractId ContractId::from(0x1000...); let my_sub_id: SubId 0x2000...; let asset_id: AssetId AssetId::new(my_contract_id, my_sub_id);amount: u64转账数量。注意两个限制不能为 0且不能超过当前合约在该资产上的余额可用 balance.md 中介绍的方式查询。官方实战示例一个完整的原生资产合约官方示例 native_asset/src/main.sw 将transfer()封装进 ABI 方法transfer_coins是生产合约中最典型的用法contract; use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*}; abi NativeAsset { fn mint_coins(mint_amount: u64); fn burn_coins(burn_amount: u64); fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity); #[payable] fn deposit(); fn get_balance(target: ContractId, asset_id: AssetId) - u64; // ... } impl NativeAsset for Contract { /// Transfer coins to a target contract. fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) { // ANCHOR: transfer_asset transfer(target, asset_id, coins); // ANCHOR_END: transfer_asset } // ... }该示例同时展示了与转账配套的完整资产操作链mint(DEFAULT_SUB_ID, mint_amount)铸造资产到合约自身mint_to(target_identity, DEFAULT_SUB_ID, mint_amount)铸造并直接发送给目标——其实现正是 asset.sw 中先mint再transfer的组合pub fn mint_to(to: Identity, sub_id: SubId, amount: u64) { mint(sub_id, amount); transfer(to, AssetId::new(ContractId::this(), sub_id), amount); }burn(DEFAULT_SUB_ID, burn_amount)销毁资产#[payable] fn deposit()配合msg_amount()/msg_asset_id()接收资产回存为转出 → 收回闭环提供基础。其他使用transfer()的官方示例还包括 liquidity_pool/src/main.sw、wallet_smart_contract/src/main.sw 与 wallet_smart_contract_self_impl/src/main.sw可作为多合约交互场景下的参考实现。实用检查清单在合约中调用transfer()时建议按以下清单自查确认导入use std::asset::transfer;所有场景的导入方式见 transfer/index.md 与各子页面确认余额充足amount不得大于合约在asset_id上的余额否则回退确认amount 0零金额转账必然回退目标为地址时预留变量输出调用方交易需包含可复用的Output::Variable否则触发FAILED_TRANSFER_TO_ADDRESS_SIGNAL0xffff_ffff_ffff_0001回退目标为合约时确认可赎回接收合约若没有取款逻辑资产将永久锁定务必在业务层规避。小结transfer()是 Sway 标准库std::asset模块中最核心的资产流出接口它通过Identity统一了转给地址与转给合约两类场景前者依赖交易中的空闲变量输出并执行tro指令后者通过tr指令无条件直转。理解其底层指令、回退条件与永久丢失风险是编写可靠 Sway 资产合约如钱包、流动性池、原生 token的必备基础。更多资产操作铸造、销毁、余额查询可继续阅读 asset/index.md 下的相关章节。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考