Debugging Free TON Smart Contracts with the ‘ft’ Multi-Account Wallet
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 --help
to get the full list of sub-commands, and ft [COMMAND] --help
to 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!