Introduction To Fift

Article image

Using the Fift language is not required for programming TON smart contracts, but if you want to get acquainted with Fift, we have prepared this article for you.

What is Fift?

Fift is a stack-based general-purpose programming language designed specifically for interacting with the TON Virtual Machine and the TON Blockchain.

What is a stack-based language?

Stack-oriented programming is a programming paradigm in which parameters are passed using a stack machine model. Stack-oriented languages work with one or more stacks, each of which can have a particular purpose. Some stack-oriented languages, such as Fift, use postfix or reverse Polish notation. Any command arguments or parameters are listed before the command. Postfix notation, for example for multiply(2, 3), would be expressed as 2, 3, multiply.

Setup

To use Fift, you'll need to have the Fift compiler binaries first. You can either compile from the sources or simply download from TON Autobuilds according to your platform.

You should be able to use Fift interpreter just by typing fift in your shell. If you encounter the following error:

[ 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.

You should either set the FIFTPATH env variable to fift libs directory or pass directory path by -I flag:

fift -I /path/to/fift/libs

Types

These are some basic data types used in Fift:

TypeDescriptionUsual Stack Notation
IntegerA Signed 257-bit integerx, y, or z
CellTVM Cellc, c', or c2
Slicepartial view of Cells
Builderpartially built Cellb
Nullnull value
TupleOrdered collection of values
StringA UTF-8 StringS
BytesSequence of bytes, representing binary dataB

Stack Notation

As mentioned before, Fift uses postfix or reverse Polish notation. Fift parser processes the code line-by-line. As it reads the line, If it encounters a value, pushes it onto the stack and If it encounters a word, executes it. Words can be assumed as function/code definitions and primitives and functions are implemented as those. All words are stored in Fift's global dictionary.

Primitives are the word that are already defined by the interpreter (in the implementation), other words are defined in Fift.fif standard library. Users can define words and extend the dictionary later.

Every time the interpreter finishes interpreting the line, it prints ok to standard output.

As an example, let's see the following code:

2 3 + .

First, 2 will be pushed to the stack then 3, + primitive will be executed which will pop two values from the stack and push their sum into the stack, at last . primitive will print the integer to standard output. So the standard output after executing the code will be:

5 ok

To understand the effect of words easily, a notation is used that shows the top of the stack before and after the word's execution. It's in word-name (before — after) format and here are some examples:

Article image

Primitives and standard library words are documented in the Fift Documentation, along with their notation and a description of what they do.

List of useful words

Here is the table of some useful words that are predefined:

WordNotationDescription
+(x y — x + y)Replaces two Integers x and y passed at the top of the stack with their sum x + y
-(x y — x - y)Computes the difference x − y of two Integers x and y
*(x y — xy)Computes the product xy of two Integers x and y
/(x y — q := floor(x/y))Computes the floor-rounded quotient of two Integers x and y
<(x y — ?)Checks whether x < y (i.e., pushes −1 if x < y, 0 otherwise)
>, =, <>, <=, >=(x y — ?)Compare x and y and push −1 or 0 depending on the result of the comparison.
"<string>"( — S)Pushes a String literal into the stack
."<string>"( — )Prints a constant string into the standard output
type(S — )Prints a String S taken from the top of the stack into the standard output
cr( — )Outputs a carriage return (or a newline character) into the standard output
b{<binary-data>}( – s)Creates a Slice s that contains no references and up to 1023 data bits specified in <binary-data>, which must be a string consisting only of the characters ‘0’ and ‘1’
x{<hex-data>}( – s)Creates a Slice s that contains no references and up to 1023 data bits specified in <hex-data>
<b( – b)Creates a new empty Builder
b>(b – c)Transforms a Builder b into a new Cell c containing the same data as b
i,(b x y – b')Appends binary representation of signed y-bit integer x (0 <= y <= 257) to Builder b
u,(b x y – b')Appends binary representation of unsigned y-bit integer x (0 <= y <= 256) to Builder b
ref,(b c – b')Appends a reference to Cell c to Builder b
s,(b s – b')Appends data bits and references taken from Slice s to Builder b
$,(b S – b')Appends String S to Builder b
B>boc(B – c)Deserializes a “standard” bag of cells represented by Bytes B, and returns the root Cell c
boc+>B(c x – B)Creates and serializes a “standard” bag of cells, consisting one root Cell c along with all its descendants. x represents flags for the additional options
B{<hex-digits>} ( – B)Pushes a Bytes literal containing data represented by an even number of hexadecimal digits.
Bx.(B – )Prints the hexadecimal representation of a Bytes value
file>B(S – B)Reads the (binary) file with the name specified in String S and returns its contents as a Bytes value
B>file(B S – )Creates a new (binary) file with the name specified in String S and writes data from Bytes B into the new file.
smca>$(x y z – S)Packs a standard TON smart-contract address with workchain x (a signed 32-bit Integer) and in-workchain address y (an unsigned 256-bit Integer) into a 48-character string S (the human-readable representation of the address) according to flags z. Possible individual flags in z are: +1 for non-bounceable addresses, +2 for testnet-only addresses, and +4 for base64url output instead of base64.
$>smca(S – x y z -1 or 0)Unpacks a standard TON smart-contract address from its human-readable string representation S.

Creating messages with Fift

To interact with smart contracts on TON Network we mostly need to create and send messages to them. There are several ways to do that, but Fift is the main and preferred way of creating messages.

Let's say we have a contract that requires an input body in the below format:

Internal message has the following structure:
      op_code:uint32 query_id:uint64 amount:(VarUInteger 16)
      destination:MsgAddress = InternalMsgBody;

To construct this with 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

What it does is:

  • Imports Asm.fif and TonUtil.fif libraries
  • Constructs an empty builder
  • Appends 24 as op_code (32-bit unsigned integer)
  • Appends 0 as query_id (64-bit unsigned integer)
  • Appends 1 nanoton as amount
  • Parses address EQA3azUJcnicaZYflOBy_rQhhwg79_bOy9mhXt5priuc1EuX and stores it to builder as destination using x,y (drops z and result) by invoking Addr, word that is defined in TonUtil.fif
  • Constructs cell from the builder
  • Writes the bag of cells to Bytes
  • Prints hex representation of the result and leaves the result at top of the stack.

Here is the execution of it with fift interpreter:

"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: An Alternative Way

TonWeb is a JavaScript SDK for The Open Network. It comes with classes that ease interacting with TON ecosystem. One of those is Cell class which can be used to create the bag of cells.

For example, the above example could be done in JS this way:

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")) });

If we execute the script above, we get:

$ node message.js x{0000001800000000000000001018006ED66A12E4F138D32C3F29C0E5FD68430E1077EFED9D97B342BDBCD35C5739A9_} b5ee9c7241010101003100005d0000001800000000000000001018006ed66a12e4f138d32c3f29c0e5fd68430e1077efed9d97b342bdbcd35c5739a9ef4bc35f

Creating Full Messages

What we discussed in two previous sections were just only message bodies, If we want to create complete messages, we should pick the appropriate type of the message and construct it according to the TL-B Schema. Messages have two types:

  • Internal Messages: messages that contracts send to each other on the blockchain
  • External Messages: messages that come from outside the blockchain (Messages from nowhere or Inbound External Messages) or go outside the blockchain (Messages to nowhere or Outbound External Messages)

Here is the TL-B Schema for 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;

To construct a full internal message we will have:

"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

With 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")) });

The result will be:

b5ee9c724101010100620000bf62001bb59a84b93c4e34cb0fca70397f5a10c3841dfbfb6765ecd0af6f34d715ce6a0808000000000000000000000000000000001800000000000000001018006ed66a12e4f138d32c3f29c0e5fd68430e1077efed9d97b342bdbcd35c5739a97d492a80

The message we just created is an internal message and only can be sent by the contracts. If we want to send messages from outside to the blockchain, we should create an inbound external message and send it to the blockchain.

For example, we can create an external message containing an internal message to our wallet's smart contract, so the smart contract will read our internal message and send it to the other contract.

Here's an example of an inbound external message which contains the internal message we just created as the body assuming the recipient contract will read the body and send it as a full raw message (with 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

with 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")) });

The result will be:

b5ee9c7241010201008800014689001a0e11f3dee4b527ad74cfb941482bd8b890cb25b5ceb7937bc51a499b3e95dc010100bf62001bb59a84b93c4e34cb0fca70397f5a10c3841dfbfb6765ecd0af6f34d715ce6a0808000000000000000000000000000000001800000000000000001018006ed66a12e4f138d32c3f29c0e5fd68430e1077efed9d97b342bdbcd35c5739a9a3cca5b3

These messages were just simple demonstrations and messages in real-life apps can get quite complicated. For example, external messages should be signed and sent along with the signature so the smart contract can verify it's from the authorized sender and execute it. Also, more complex control flows can be used in Fift, like conditions, loops, and ... . As an example we could determine if a cell will be able to get serialized in-place and act accordingly automatically.

Leveraging these capabilities helps to generalize our Fift scripts. Much more complex messages and also Fift control flows will be covered in the next sections.