Debugging Free TON Smart Contracts with the ‘ft’ Multi-Account Wallet

Fabrice Le Fessant
OCamlPro
Published in
6 min readMay 18, 2021

Free TON is one of most efficient blockchains in terms of performance. We have been working with it during the last months. ‘ft’ is a tool that we developed to help manage multiple accounts. While we were developing more and more smart contracts on Free TON, we have kept extending ‘ft’ with many features to help developers of smart contracts in their daily tasks. In this article, we present a simple session with ‘ft’ and how it can be used together with TONOS SE to test a smart contract.

Installation

ft is an open-source tool available on Github:

Since it is based on our OCaml binding on TON-SDK, the easiest way to install it is to use Opam, the OCaml package manager (you must have a recent version of Rust installed, do not use system packages but download it from https://rustup.rs/):

$ sh <(curl -sL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)
$ opam init
$ opam switch create free-ton 4.10.0
$ opam repo add ocp git+https://github.com/OCamlPro/ocp-opam-repository --set-default --all
$ opam install ft
$ cp $HOME/.opam/free-ton/bin/ft SOMEWHERE_IN_YOUR_PATH/

Since Opam is a source package manager, it may take a while to download, compile and install the package and all its dependencies. If you already have a previous version installed, upgrading will be much faster:

$ opam update
$ opam upgrade ft
$ cp $HOME/.opam/4.10.0/bin/ft SOMEWHERE_IN_YOUR_PATH/
$ ft --help
$ ft account --help

This article was written with version 0.4.0, on May 17, 2021.

Configuration

ft is preconfigured with the knowledge of a few networks on Free TON:

$ ft switch                                                           
* testnet
- tonlabs (current if network was selected)
url: https://net.ton.dev
* mainnet (current)
- tonlabs (current)
url: https://main.ton.dev
* rustnet
- tonlabs (current if network was selected)
url: https://rustnet.ton.dev
* fldnet
- tonlabs (current if network was selected)
url: https://fld.ton.dev

We can easily switch between them with:

$ ft switch testnet
Switched to network "testnet"

Since we are going to use ft for development, we should install some standard tools for Free TON: tonos-cli, solc and tvm-linker. This is easily done using:

$ ft init

This command will checkout the latest sources of all these tools, build them and install them in $HOME/.ft/bin/ (you will need a recent version of Rust).

Playing with Smart Contracts

Let’s take a very simple example of smart contract. Our HelloWorld.sol contract looks like this :

pragma ton-solidity >= 0.35.0;
pragma AbiHeader expire;
contract HelloWorld {
string g_s ;
uint128 g_cost ;
function set( string s ) public {
require( msg.value > g_cost, 100 );
g_s = s; g_cost = msg.value ;
}
function flush(address dest) public view {
require( tvm.pubkey() == msg.pubkey(), 101 );
tvm.accept();
dest.transfer( 0, false, 128 );
}
function get() public view returns ( string s, uint128 cost )
{ s = g_s; cost = g_cost; }
}

Let’s start by building it:

$ ft contract --build HelloWorld.sol -f

This command will call the solc compiler, and stores the result in the database of contracts known by ft . We can check that it appears in the following list:

$ ft contract --list

We can also check its ABI:

$ ft contract --show-abi HelloWorld

Deploying a smart contract on a local network

If we want to play with this contract, we may want to use TONOS SE to execute the contract locally. Let’s do that!

We first need to create a “switch” in ‘ft’ for the new network:

$ ft switch --create sandbox1
$ ft node --start

Switches called “sandboxN”, with N being a number, have a special meaning in ‘ft’: they are used to create TONOS SE networks with predefined accounts. Let’s list them:

$ ft account 
Account "giver": 4_999_999_999.975_350 TONs (Active)
Account "user9": not yet created
Account "user8": not yet created
Account "user7": not yet created
Account "user6": not yet created
Account "user5": not yet created
Account "user4": not yet created
Account "user3": not yet created
Account "user2": not yet created
Account "user1": not yet created
Account "user0": not yet created

We can create and give some tokens, and deploy the corresponding contracts:

$ ft node --give user1:10000
$ ft account user1
Account "user1": 9_999.944_105_999 TONs (Active)

The account ‘user1’ is a bit special, as ‘ft’ is configured to use it as the deployer for new contracts, i.e. the account that will credit the address to make the deployment possible.

We can now deploy our contract:

$ ft contract --deploy HelloWorld --create hello
$ ft account hello

The second command shows that the account hello was created, credited of 1 TON (coming from the deployer user1 ) and the contract deployed to it.

We can check its state using a local call to the get method:

$ ft call hello get --local
{
"s": "",
"cost": "0"
}
$ ft call hello get --local --subst '@string = "%{res:s}"'
string = ""

Now, let’s try to change the string using the set method:

$ ft call hello set '{ "s": "%{hex:string:Hello World}" }'
call: 0:25ba[...]ea0b
method: set
params: { "s": "48656c6c6f20576f726c64" }
signed: hello
MessageId: 486f[...]080ba
fatal exception Wait for transaction failed: {
code:414.000000
message:Contract execution was terminated with error: compute phase isn't succeeded, exit code: 100. For more information about exit code check the contract source code or ask the contract developer
data:{ ... }

Here, we used the %{hex:string:...} substitution to translate the string to hexa on the fly (we could also have used %{hex:file:FILENAME} to read from a file). Indeed, ft has a powerful substitution language, that can be very useful to write scripts without hardcoding addresses (for example, using %{account:address:user1} instead of user1’s address ).

The call returned an error, with exit_code = 100. Looking at the code of the smart contract, we see thatmsg.value is 0, so the first requirement fails. We need to transfer tokens in the call !

Let’s use the account user2 for that:

$ ft node --give user2
$ ft multisig -a user2 --transfer 1 --to hello set '{ "s": "%{hex:string:Hello World}" }'
$ ft call hello get --local --subst '@string = "%{of-hex:string:%{res:s}}"'
string = "Hello World"

The call succeeded ! Again, you can see how ft substitutions can be used to extract the result %{res:s} and translate it from hexa using %{of-hex:string:STRING} . We could also have directly saved it to a file using the option --output FILE.

Inspecting the Blockchain

If we want to better understand what happened, we can inspect the transactions on different accounts:

$ ft inspect --past hello
$ ft inspect --past user2

These 2 commands will show all the transactions that happened on hello and user2 in the past. We may want to inspect a specific transaction in more details:

$ ft inspect --transaction f9dc86826da90da7a72f7b020b1ab4c08767c4c18d3d5dbf59e0e80b11af6a64 -3

If you try this example, you should pick your own transaction id. The -3 argument tells ft to give details at level 3 (max is 4). We could also inspect accounts, messages and blocks with ft inspect .

We can also see what happens in real time: in two different terminals, we can call the following commands in parallel:

$ ft watch hello

and then:

$ ft call hello flush '{ "dest": "%{account:address:user3}" }'
$ ft account hello user3

Transactions on the hello account are displayed as they happen. This feature is particularly useful if you want to watch several contracts at the same time.

Conclusion

This ends our short tour of the ft tool. There are of course many other features in ft, you can use ft --helpto get the full list of sub-commands, and ft [COMMAND] --helpto get the specific help of a sub-command.

We hope it was interesting and that ft will be as useful for you as it was for us!

--

--