编写 TON 智能合约不需要使用 Fift 语言,但如果您想熟悉 Fift,我们为您准备了这篇文章。
什么是 Fift?
Fift 是一种基于堆栈的通用编程语言,专为与 TON 虚拟机和 TON 区块链交互而设计。
什么是基于堆栈的语言?
面向堆栈的编程是一种使用堆栈机器模型传递参数的编程范例。面向堆栈的语言使用一个或多个堆栈,每个堆栈都有特定的用途。一些面向堆栈的语言,例如 Fift,使用后缀或逆波兰表示法。任何命令自变量或参数都列在命令之前。后缀表示法,例如multiply(2, 3)
,将表示为2, 3, multiply
。
设置
要使用 Fift,您需要首先拥有 Fift 编译器二进制文件。您可以根据您的平台从源代码进行编译,也可以直接从TON Autobuilds下载。
您只需fift
在 shell 中输入即可使用 Fift 解释器。如果您遇到以下错误:
[ 1][t 0][2022-05-08 14:36:43.170977500][fift-main.cpp:180] Error interpreting standard preamble file `Fift.fif`: cannot locate file `Fift.fif`
Check that correct include path is set by -I or by FIFTPATH environment variable, or disable standard preamble by -n.
您应该将FIFTPATH
env 变量设置为 fift libs 目录或通过标志传递目录路径-I
:
fift -I /path/to/fift/libs
类型
这些是 Fift 中使用的一些基本数据类型:
类型 | 描述 | 常用的堆栈表示法 |
整数 | 有符号 257 位整数 | x, y, or z |
细胞 | TVM细胞 | c, c', or c 2 |
片 | Cell 的部分视图 | s |
建设者 | 部分构建的单元 | b |
无效的 | null 价值 | ⟂ |
元组 | 值的有序集合 | |
细绳 | UTF-8 字符串 | S |
字节 | 字节序列,表示二进制数据 | B |
堆栈表示法
如前所述,Fift 使用后缀或逆波兰表示法。Fift 解析器逐行处理代码。当它读取该行时,如果遇到一个值,则将其压入堆栈,如果遇到一个单词,则执行它。单词可以被假定为函数/代码定义,原语和函数也被实现为这些。所有单词都存储在 Fift 的全球词典中。
Fift.fif
原语是解释器(在实现中)已经定义的单词,其他单词在标准库中定义。用户可以定义单词并稍后扩展词典。
每次解释器完成对该行的解释时,都会打印ok
到标准输出。
作为示例,让我们看一下以下代码:
2 3 + .
首先,2
将被推入堆栈,然后3
,+
将执行原语,从堆栈中弹出两个值并将它们的和推入堆栈,最后.
原语将整数打印到标准输出。所以执行代码后的标准输出将是:
5 ok
为了轻松理解单词的效果,使用了一种表示法来显示单词执行之前和之后的堆栈顶部。它是word-name (before — after)
格式的,这里有一些示例:
基元和标准库单词以及它们的符号和它们的作用的描述都记录在Fift 文档中。
有用的单词列表
下面是一些预定义的有用单词表:
单词 | 符号 | 描述 |
+ | (x y — x + y) | 替换两个整数 x 并y 在堆栈顶部传递它们的和x + y |
- | (x y — x - y) | x − y 计算两个整数的差x 和y |
* | (x y — xy) | xy 计算两个整数的乘积x 和y |
/ | (x y — q := floor(x/y)) | 计算两个整数的四舍五入商 x 并y |
< | (x y — ?) | 检查是否 x < y (即,如果 则推入 -1x < y ,否则推入 0) |
>, =, <>, <=, >= | (x y — ?) | 比较 x andy 并根据比较结果推入 -1 或 0。 |
"<string>" | ( — S) | 将字符串文字推入堆栈 |
."<string>" | ( — ) | 将常量字符串打印到标准输出中 |
type | (S — ) | S 将从堆栈顶部获取的字符串打印到标准输出中 |
cr | ( — ) | 将回车符(或换行符)输出到标准输出中 |
b{<binary-data>} | ( – s) | 创建一个 s 不包含引用且最多包含 1023 个数据位的切片<binary-data> ,该切片必须是仅由字符“0”和“1”组成的字符串 |
x{<hex-data>} | ( – s) | 创建一个 s 不包含引用且最多 1023 个数据位的切片<hex-data> |
<b | ( – b) | 创建一个新的空构建器 |
b> | (b – c) | 将 Builder 转换为包含与以下内容相同的数据的 b 新 Cellc b |
i, | (b x y – b') | y 将有符号位整数x (0 <= y <= 257)的二进制表示形式附加到 Builderb |
u, | (b x y – b') | y 将无符号位整数x (0 <= y <= 256)的二进制表示形式附加到 Builderb |
ref, | (b c – b') | 将对 Cell 的引用附加 c 到 Builderb |
s, | (b s – b') | 将从 Slice 获取的数据位和引用附加 s 到 Builderb |
$, | (b S – b') | 将字符串附加 S 到生成器b |
B>boc | (B – c) | 反序列化由 Bytes 表示的“标准”单元包 B ,并返回根 Cellc |
boc+>B | (c x – B) | 创建并序列化一个“标准”单元包,其中包含一个根单元 c 及其所有后代。x 代表附加选项的标志 |
B{<hex-digits>} | ( – B) | 推送包含偶数个十六进制数字表示的数据的字节文字。 |
Bx. | (B – ) | 打印 Bytes 值的十六进制表示形式 |
file>B | (S – B) | 读取具有在 String 中指定的名称的(二进制)文件 S ,并将其内容作为 Bytes 值返回 |
B>file | (B S – ) | 使用 String 中指定的名称创建一个新(二进制)文件 S ,并将 Bytes 中的数据B 写入新文件中。 |
smca>$ | (x y z – S) | 根据标志将标准 TON 智能合约地址与工作链 x (带符号的 32 位整数)和工作链内地址y (无符号 256 位整数)打包成 48 个字符的字符串(地址的人类可读表示) 。可能的单独标志是:+1 表示不可退回地址,+2 表示仅测试网地址,+4 表示 base64url 输出而不是 base64。S z z |
$>smca | (S – x y z -1 or 0) | 从人类可读的字符串表示中解压标准 TON 智能合约地址 S 。 |
使用 Five 创建消息
为了与 TON 网络上的智能合约进行交互,我们主要需要创建消息并向它们发送消息。有多种方法可以做到这一点,但 Fift 是创建消息的主要且首选的方法。
假设我们有一个合约,需要以下格式的输入主体:
Internal message has the following structure:
op_code:uint32 query_id:uint64 amount:(VarUInteger 16)
destination:MsgAddress = InternalMsgBody;
用 Fift 构建这个:
"Asm.fif" include
"TonUtil.fif" include
<b 24 32 u, 0 64 u, 1 Gram,
"EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX" $>smca 2drop Addr,
b>
2 boc+>B dup Bx. cr
它的作用是:
- 导入
Asm.fif
和TonUtil.fif
库 - 构造一个空的构建器
- 附加
24
为op_code
(32 位无符号整数) - 附加
0
为query_id
(64 位无符号整数) - 附加
1 nanoton
为amount
- 通过调用定义在中的单词来解析地址
EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX
并将其存储到构建器中destination
(x,y
丢弃z
和结果)Addr,TonUtil.fif
- 从构建器构建单元格
- 将单元格写入字节
- 打印结果的十六进制表示并将结果保留在堆栈顶部。
这是用第五个解释器执行的:
"Asm.fif" include
ok
"TonUtil.fif" include
ok
<b 24 32 u, 0 64 u, 1 Gram,
ok
"EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX" $>smca 2drop Addr,
ok
b>
ok
2 boc+>B dup Bx. cr
B5EE9C7241010101003100005D0000001800000000000000001018006ED66A12E4F138D32C3F29C0E5FD68430E1077EFED9D97B342BDBCD35C5739A9EF4BC35F
ok
.s
BYTES:B5EE9C7241010101003100005D0000001800000000000000001018006ED66A12E4F138D32C3F29C0E5FD68430E1077EFED9D97B342BDBCD35C5739A9EF4BC35F
ok
TonWeb:另一种方式
TonWeb是 The Open Network 的 JavaScript SDK。它附带了可轻松与 TON 生态系统交互的类。其中之一是Cell
可用于创建细胞袋的类。
例如,上面的例子可以用 JS 来完成:
const TonWeb = require("tonweb");
const Cell = TonWeb.boc.Cell;
const Address = TonWeb.utils.Address;
let cell = new Cell();
cell.bits.writeUint(24, 32);// op_code
cell.bits.writeUint(0, 64);// query_id
cell.bits.writeCoins(1);// amount
let address = new Address("EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX");// destination
cell.bits.writeAddress(address);
console.log(cell.print());// print cell data like Fift
let bocBytes = cell.toBoc(false);
bocBytes.then((res) => {
console.log(Buffer.from(res).toString("hex"))
});
如果我们执行上面的脚本,我们会得到:
$ node message.js
x{0000001800000000000000001018006ED66A12E4F138D32C3F29C0E5FD68430E1077EFED9D97B342BDBCD35C5739A9_}
b5ee9c7241010101003100005d0000001800000000000000001018006ed66a12e4f138d32c3f29c0e5fd68430e1077efed9d97b342bdbcd35c5739a9ef4bc35f
创建完整消息
我们在前两节中讨论的只是消息体,如果我们想创建完整的消息,我们应该选择适当的消息类型并根据TL-B Schema
. 消息有两种类型:
- 内部消息:合约在区块链上相互发送的消息
- 外部消息:来自区块链外部的消息(来自无处的消息或入站外部消息)或到达区块链之外的消息(无处发送的消息或出站外部消息)
这TL-B Schema
是Message
:
message$_ {X:Type} info:CommonMsgInfo
init:(Maybe (Either StateInit ^StateInit))
body:(Either X ^X) = Message X;
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddressInt dest:MsgAddressInt
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
import_fee:Grams = CommonMsgInfo;
ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
为了构建完整的内部消息,我们将有:
"Asm.fif" include
"TonUtil.fif" include
// message body:
<b 24 32 u, 0 64 u, 1 Gram,
"EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX" $>smca 2drop Addr,
b> constant msg_body // we define the message body and store it in a constant named msg_body, so we can later use it
<b
// First we define CommonMsgInfo, we're gonna pick int_msg_info$0 so first bit will be => '0'
// as schema follows, want to disable ihr, allow bounces and is not bounced itself so we'll have => '011'
// next is source address which we'll use addr_none so => '00' (this will be overwritten when sent to correct address)
// concating all we'll get final bitstring => '011000' that we can append to builder
// Note: one can use 0x18 and append it as an 6-bit uint which will result in same outcome
b{011000} s,
// Now we parse and append the destination address
"EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX" $>smca 2drop Addr,
// Appending the amount
1 Gram,
// We now have extra_currencies which we'll have no, so a null dictionary will be needed => '0'
// ihr_fee and fwd_fee are zero which we'll encode as `VarUInteger 16`, it will be overwritten later
// created_lt and create_at fields will be zero so it could be overwritten later, they're 64-bit and 32-bit
// Now we go through message body:
// we append a zero bit to indicate there is no init field => '0'
// later we append another zero bit which indicates in-place serialization of the body
// All of these values are zeros so we sum the length (let's say its m) and write a m-bit 0 uint to builder
0 1 4 4 64 32 1 1 + + + + + + u,
// Now we parse msg_body cell to slice and append it to builder
msg_body <s s,
b>
// to be accessible by other contracts
dup constant internal_msg
2 boc+>B dup Bx. cr
使用 TonWeb:
const TonWeb = require("tonweb");
const Cell = TonWeb.boc.Cell;
const Address = TonWeb.utils.Address;
// message body
const body = new Cell();
body.bits.writeUint(24, 32);// op_code
body.bits.writeUint(0, 64);// query_id
body.bits.writeCoins(1);// amount
let address = new Address("EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX");// destination
body.bits.writeAddress(address);
// message itself:
const message = new Cell();
message.bits.writeUint(0x18, 6);// 0x18 = 0b011000
message.bits.writeAddress(address);
message.bits.writeCoins(1);
message.bits.writeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1);
message.writeCell(body);
const bocBytes = message.toBoc(false);
bocBytes.then((res) => {
console.log(Buffer.from(res).toString("hex"))
});
结果将是:
b5ee9c724101010100620000bf62001bb59a84b93c4e34cb0fca70397f5a10c3841dfbfb6765ecd0af6f34d715ce6a0808000000000000000000000000000000001800000000000000001018006ed66a12e4f138d32c3f29c0e5fd68430e1077efed9d97b342bdbcd35c5739a97d492a80
我们刚刚创建的消息是内部消息,只能由合约发送。如果我们想从外部向区块链发送消息,我们应该创建一条入站外部消息并将其发送到区块链。
例如,我们可以创建一条包含内部消息的外部消息发送给我们钱包的智能合约,这样智能合约就会读取我们的内部消息并将其发送到另一个合约。
这是入站外部消息的示例,其中包含我们刚刚创建的内部消息作为正文,假设收件人合约将读取正文并将其作为完整的原始消息发送(带有 )send_raw_message
:
"Asm.fif" include
"TonUtil.fif" include
// this will import the internal message that we just created
// (it will be as internal_msg constant)
"internal.fif" include
<b
// Again we'll define CommonMsgInfo, we're gonna pick ext_in_msg_info$10 this time, so first two bits will be => '10'
// next is source address which we'll use addr_none so => '00' (from nowhere)
// now we need to append destination which should be MsgAddressInt, we'll use:
// addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt;
// and it will not be anycast so we'll get => '100'
// after concating all of them: '1000100'
b{1000100} s,
// Now we parse and append the destination address (workchain_id:int8 address:bits256)
"EQBoOEfPe5LUnrXTPuUFIK9i4kMsltc63k3vFGkmbPpXcGo5" $>smca 2drop Addr,
// Then append import_fee
0 Gram,
// Now we go through message body:
// we append a zero bit to indicate there is no init field => '0'
// later we append a 1 bit which indicates body is a cell ref (otherwise the whoule message won't fit in 1023-bits) => '1'
b{01} s,
// Now we append internal_msg as a cell reference
internal_msg ref,
b>
2 boc+>B dup Bx. cr
与 TonWeb:
const TonWeb = require("tonweb");
const Cell = TonWeb.boc.Cell;
const Address = TonWeb.utils.Address;
// message body
const body = new Cell();
body.bits.writeUint(24, 32);// op_code
body.bits.writeUint(0, 64);// query_id
body.bits.writeCoins(1);// amount
let address = new Address("EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX");// destination
body.bits.writeAddress(address);
// message itself:
const message = new Cell();
message.bits.writeUint(0x18, 6);// 0x18 = 0b011000
message.bits.writeAddress(address);
message.bits.writeCoins(1);
message.bits.writeUint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1);
message.writeCell(body);
const ext = new Cell();
ext.bits.writeUint(0x44, 7);// 0x44 = 0b1000100
let naddr = new Address("EQBoOEfPe5LUnrXTPuUFIK9i4kMsltc63k3vFGkmbPpXcGo5");// destination
ext.bits.writeAddress(naddr);
ext.bits.writeCoins(0);
ext.bits.writeUint(1, 2);// 1 = 0b01
ext.refs.push(message);
const bocBytes = ext.toBoc(false);
bocBytes.then((res) => {
console.log(Buffer.from(res).toString("hex"))
});
结果将是:
b5ee9c7241010201008800014689001a0e11f3dee4b527ad74cfb941482bd8b890cb25b5ceb7937bc51a499b3e95dc010100bf62001bb59a84b93c4e34cb0fca70397f5a10c3841dfbfb6765ecd0af6f34d715ce6a0808000000000000000000000000000000001800000000000000001018006ed66a12e4f138d32c3f29c0e5fd68430e1077efed9d97b342bdbcd35c5739a9a3cca5b3
这些消息只是简单的演示,现实应用程序中的消息可能会变得相当复杂。例如,外部消息应该被签名并与签名一起发送,以便智能合约可以验证它来自授权的发送者并执行它。此外,Fift 中还可以使用更复杂的控制流,例如条件、循环等。作为一个例子,我们可以确定一个单元是否能够就地序列化并自动采取相应的行动。
利用这些功能有助于概括我们的 Fift 脚本。下一节将介绍更复杂的消息以及 Fift 控制流。
本站所提供的所有资讯均仅供读者参考。这些资讯不代表任何投资建议、提供、邀请或推荐。读者在使用这些资讯时,应当考虑自己的个人需求、投资目标和财务状况。所有投资都伴随着一定的风险,在做出任何投资决策之前请多加留意。