news 2026/9/11 21:58:15

用 Sway 实现智能合约版 FizzBuzz:从 ABI 设计到链上调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 Sway 实现智能合约版 FizzBuzz:从 ABI 设计到链上调用

用 Sway 实现智能合约版 FizzBuzz:从 ABI 设计到链上调用

【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway

Sway 语言官方书籍的 FizzBuzz 示例 展示了一种与经典编程题目不同的玩法:它不是跑在本地终端上的控制台程序,而是一个部署在 Fuel 区块链上的智能合约——脚本通过 ABI 调用合约的fizzbuzz方法,传入u64数值,合约返回一个表示结果的枚举(enum)。读完本文,你将掌握如何用 Sway 的contract程序类型、abi声明与impl实现来组织一个最小可用的链上逻辑,理解枚举与结构体如何自动进入 ABI JSON 供链下代码解码,并能够结合forc构建、部署与调用这一示例。

FizzBuzz 题目的智能合约化改造

经典 FizzBuzz 的规则众所周知:从 1 数到 N,遇到 3 的倍数输出Fizz,遇到 5 的倍数输出Buzz,同时是 3 和 5 的倍数输出FizzBuzz,其余输出数字本身。原文档明确指出,本例不是传统意义上的 FizzBuzz,而是它的智能合约版本

This example is not the traditional FizzBuzz; instead it is the smart contract version! A script can call thefizzbuzzABI method of this contract with someu64value and receive back the result as anenum.

二者的核心差异在于交互模型:传统程序把结果打印到标准输出,而这里的合约把结果编码为返回值返回给调用方。这意味着“打印”这个动作被替换成了“返回一个可被链下代码解码的类型化值”,而“循环计数”则被压缩为对单次输入的判定。整个示例浓缩了 Sway 智能合约开发的完整骨架:

  1. contract;声明程序类型;
  2. enum定义返回的数据结构;
  3. abi声明对外接口;
  4. impl <ABI> for Contract实现接口逻辑。

完整源码位于 examples/fizzbuzz/src/main.sw,项目清单文件位于 examples/fizzbuzz/Forc.toml。

完整示例源码与逐行拆解

程序类型声明

contract;

Sway 中的每个程序文件都必须以程序类型声明开头。Sway 程序共分为四类:contract(合约)、script(脚本)、predicate(谓词)和library(库),前三种可部署到链上,而库仅用于代码复用、不会被直接部署(参见 程序类型概览)。本例声明为contract,意味着这是一个可被调用、可持有状态的链上程序。

用枚举建模结果

enum FizzBuzzResult { Fizz: (), Buzz: (), FizzBuzz: (), Other: u64, }

FizzBuzzResult是一个包含四种变体(variant)的枚举,即求和类型(sum type)。前三个变体FizzBuzzFizzBuzz的类型是单元类型(),表示“只有该变体本身、不附带额外数据”;Other: u64则携带原始输入数值,用于“既不是 3 的倍数也不是 5 的倍数”的情况。

从内存布局看,枚举需要一定的额外开销:为了区分当前保存的是哪个变体,Sway 会为枚举存储一个 8 字节(一个 word)的 tag,其后预留的空间等于最大变体的大小。在本例中,最大变体是Other: u64(8 字节),因此每个FizzBuzzResult值在内存中占用约 16 字节(8 字节 tag + 8 字节数据)。更多细节参见书籍的 结构体、元组与枚举 章节。

ABI 声明:定义对外接口

abi FizzBuzz { fn fizzbuzz(input: u64) -> FizzBuzzResult; }

abi声明定义了一个名为FizzBuzz的 Application Binary Interface(应用二进制接口),其中只有一个方法fizzbuzz:接收一个u64参数input,返回FizzBuzzResult。注意 ABI 声明里只有函数签名、没有函数体,这与 trait 声明 类似。对于使用 ABI 的合约,它必须定义或导入 ABI 声明并实现它(参见 什么是智能合约)。

实现 ABI:落实判定逻辑

impl FizzBuzz for Contract { fn fizzbuzz(input: u64) -> FizzBuzzResult { if input % 15 == 0 { FizzBuzzResult::FizzBuzz } else if input % 3 == 0 { FizzBuzzResult::Fizz } else if input % 5 == 0 { FizzBuzzResult::Buzz } else { FizzBuzzResult::Other(input) } } }

impl FizzBuzz for Contract把 ABI 实现到当前合约上。for Contract语法专用于为合约实现 ABI;如果是为结构体实现方法则应使用impl Foo语法。判定逻辑本身就是一个多分支if表达式:

  • input % 15 == 0:同时被 3 和 5 整除,返回FizzBuzz(先判断这一项,避免被更具体的分支提前拦截);
  • 否则input % 3 == 0,返回Fizz
  • 否则input % 5 == 0,返回Buzz
  • 否则把原始输入包进Other变体返回:FizzBuzzResult::Other(input)

这段逻辑依赖 Sway 的if表达式分支能力。Sway 中if是表达式而非语句,可以用在let绑定的右侧;同时所有分支必须返回同一类型,本例各分支返回的正是同一个FizzBuzzResult类型(参见 控制流)。最终每个分支都返回一个,这也与“枚举携带数据”的特性自然契合——空变体直接构造,带数据变体用枚举名::变体名(值)语法构造。

ABI JSON:链下代码如何理解链上返回值

原文档特别强调了一个关键事实:

The format for custom structs and enums such asFizzBuzzResultwill be automatically included in the ABI JSON so that off-chain code can handle the encoded form of the returned data.

也就是说,FizzBuzzResult这样的自定义枚举(以及结构体)的类型格式会自动被写入 ABI JSONforc build生成 ABI JSON 时,会自动序列化合约接口以及接口中出现的全部自定义类型定义。链下代码(如 Rust SDK、TypeScript SDK 或任何能解析 JSON 的客户端)读取该 JSON 后,即可知道返回值FizzBuzzResult有哪几个变体、每个变体的负载类型是什么(()u64),从而正确解码链上返回的编码字节,还原出是FizzBuzzFizzBuzz还是Other(u64)

这正是 Sway 合约“接口即类型系统”的体现:合约方法与链下客户端通过 ABI JSON 达成类型层面的契约,枚举/结构体格式被自动包含,调用方无需手工维护解码表。ABI 的声明、实现与调用方式可进一步参考 什么是智能合约 章节中关于 ABI 声明与impl Self的说明。

脚本调用合约:把结果拿回链下

合约部署后,需要一个调用方。原文档指明“脚本(script)可以调用该合约的fizzbuzzABI 方法”。在 Sway 中,脚本是只执行一次的可运行字节码,不持有资源、也不能被合约调用,但可以调用合约并根据返回值继续执行(参见 脚本)。

脚本通过ABI 转换(abi cast)来调用合约,核心写法如下(示例见 examples/wallet_contract_caller_script/src/main.sw):

script; use wallet_abi::Wallet; fn main() { let contract_address = 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b; let caller = abi(Wallet, contract_address); // ... caller.send_funds { gas: 10000, coins: 0, asset_id: b256::zero() }(amount_to_send, recipient_address); }

abi(AbiName, contract_address)返回一个ContractCaller,ABI 的方法成为其可用方法。调用时可以指定三个可选的特殊参数:gas(转发给合约的 gas,默认取上下文 gas 即$cgas寄存器)、coins(随调用转发的币数,默认 0)、asset_id(转发资产的 ID,默认b256::zero())。对 FizzBuzz 合约来说,脚本只需abi(FizzBuzz, address)构造调用者,然后调用caller.fizzbuzz { gas: ..., coins: 0, asset_id: b256::zero() }(15)之类的表达式,即可拿到FizzBuzzResult枚举值,再按其变体分支处理。forc会把脚本与合约一起编译进交易,脚本在交易执行时完成这次链上调用。

工程配置:Forc.toml中的关键信息

示例的项目清单 examples/fizzbuzz/Forc.toml 内容如下:

[project] authors = ["Fuel Labs <contact@fuel.sh>"] entry = "main.sw" license = "Apache-2.0" name = "fizzbuzz" [dependencies] std = { path = "../../sway-lib-std" }
  • entry = "main.sw":指明入口文件为src/main.sw(默认入口为main.sw,此处显式声明);
  • name = "fizzbuzz":包名,合约构建产物(字节码与 ABI JSON)都会以此命名;
  • std依赖通过相对路径指向仓库根目录的 sway-lib-std 标准库,这是因为本示例位于 Sway 主仓库内部、直接复用同一源码树的 std;普通独立项目则通常写成std = { git = "..." }std = { version = "..." }形式。

examples目录下的全部示例由顶层的 examples/Forc.toml 以 workspace 方式统一组织管理,其中fizzbuzz是默认启用的成员之一。构建时在examples/fizzbuzz目录下执行forc build即可生成合约字节码与 ABI JSON;也可以从仓库根目录对整个 workspace 执行forc build(构建方式参考 forc 构建命令)。

从源码看该示例在仓库中的双重存在

在 Sway 仓库中,FizzBuzz 逻辑实际上出现了两次,用途不同:

1. 书籍示例(contract 版本):即本主题文档讲解的对象,位于 examples/fizzbuzz/src/main.sw,被 docs/book/src/examples/fizzbuzz.md 通过{{#include}}指令直接嵌入书籍页面,强调“枚举类型自动进入 ABI JSON”这一链上契约特性。

2. 语言参考手册示例(library 版本):位于 docs/reference/src/code/examples/fizzbuzz/src/lib.sw,是一个library;类型的程序,把判定逻辑封装成普通函数fn fizzbuzz(input: u64) -> State,枚举命名为State(变体Fizz/Buzz/FizzBuzz/Other(u64),与合约版一致),对应文档 docs/reference/src/documentation/examples/fizzbuzz.md。两处共享完全相同的判定规则:

  • 能被 3 整除返回Fizz
  • 能被 5 整除返回Buzz
  • 同时能被 3 和 5 整除返回FizzBuzz
  • 其余情况原样返回输入

将同一算法分别以“库函数”和“合约 ABI 方法”两种形态呈现,恰好对比出 Sway 程序类型的使用差异:库用于链下/链上通用的代码复用,合约则把逻辑暴露为可被外部调用的链上接口。

小结与延伸

本文围绕 Sway 官方书籍的 FizzBuzz 示例,完整拆解了智能合约版 FizzBuzz 的四个组成部分——contract;程序类型声明、带数据的枚举返回值、abi接口声明与impl for Contract实现,并说明了自定义枚举如何自动进入 ABI JSON 供链下客户端解码,以及脚本如何通过abi转换调用合约。若想继续深入,可以:

  • 阅读 Counter 示例 了解带持久化存储的合约;
  • 阅读 Wallet 智能合约示例 了解多方法 ABI 与资金转移场景;
  • 结合 控制流 与 结构体、元组与枚举 加深对if表达式与求和类型的理解。

【免费下载链接】sway🌴 Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 21:57:39

从RAR到Piano Roll:MIDI钢琴数据集清洗与标准化实战

简介&#xff1a;面向米哈游音乐二创爱好者和音乐信息检索、生成方向研究者的精选钢琴二创数据集&#xff0c;数据源自《原神》《崩坏&#xff1a;星穹铁道》等米哈游旗下游戏的标志性旋律再创作。整理方在原始网络乐谱基础上&#xff0c;补充了游戏内地区名与结构信息作为关键…

作者头像 李华
网站建设 2026/9/11 21:57:35

双关节机械臂自适应模糊反演控制理论与MATLAB仿真实现

简介&#xff1a;面向机械臂智能控制方向的高校本科生与硕士生&#xff0c;该压缩包围绕双关节机械臂的自适应模糊反演控制方法&#xff0c;提供了一套可直接运行的Matlab仿真实现&#xff0c;适用于智能控制、自适应控制及机器人技术等方向的课题研究。包内共9个文件&#xff…

作者头像 李华
网站建设 2026/9/11 21:54:56

为什么财务人的脾气,都这么大?

做财务久了&#xff0c;会发现一个挺有意思的现象&#xff1a; 很多业务同事对财务最大的印象&#xff0c;不是专业&#xff0c;而是“脾气大”。 单据晚交&#xff0c;财务催。 合同信息对不上&#xff0c;财务问。 月底关账&#xff0c;财务更是一天到晚盯着所有人。 站…

作者头像 李华
网站建设 2026/9/11 21:54:51

实时数仓到底怎么建?从Kafka采集到指标计算、BI展示全链路拆解

很多企业一提实时数仓&#xff0c;第一反应往往是&#xff1a; Kafka怎么搭&#xff1f; Flink要不要上&#xff1f; 延迟能不能做到秒级&#xff1f; 但真正做过项目以后会发现&#xff0c;技术组件反而不是最难的。 真正难的是&#xff1a; 数据从哪里来、怎样持续进来…

作者头像 李华
网站建设 2026/9/11 21:54:17

AI短剧六步工作流:一个人从梗概做到成片

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华