The full article source code is available on GitHub.
In case you missed it, start by going through Running TON OS on Windows.
We start from the smart contract discussed in, Designing an Unbounded List in Solidity. Despite being rather simple, the contract required quite a few changes.
Important: Since the initial contract was designed for Ethereum, the result of this conversion is certainly not optimal! Design considerations that apply to Ethereum do not necessarily directly apply to TON.
Hence, I recommend caution before reusing the TON Solidity contract included with this article.
For those eager to look at the code, check these links:
The Compilation Process
To begin, we need a Solidity to TON Virtual Machine (TVM) compiler. A few options are available for running the compiler. On Windows, I will run this as a docker container.
If you are following along, grab the smart contract and copy it to the machine where TON OS is installed.
Start TON OS with:
> tondev start
Next, start the compilation using:
> tondev sol ListContract.sol -l js -L deploy
Once again, I am using the tondev
command line tool, which we introduced in Running TON OS on Windows. Here, the first two parameters are selecting solidity compilation and identifying the source file. The remaining parameters instruct tondev
to generate a JavaScript wrapper for deploying and running the smart contract.
On compiling for the first time, the command pulls the compiler docker image:
Pulling [tonlabs/compilers:latest]...
Docker Desktop also prompts for file access, which I grant:
With all this out of the way, compilation completes generating three files:
ListContract.tvc
ListContract.abi.json
ListContractContract.js
Contract Wrapper Class
ListContractContract.js
contains a wrapper that facilitates deploying and running the smart contract in Node.js. You should generate ListContractContract.js
yourself. The lazier ones can look at the wrapper code that was generated by my compilation.
At the very top of this file, we find the smart contract interface definition:
const abi = ...
Next, we have the package that groups together the ABI and the base64 encoded compilation result:
const pkg = ...
Finally, we have the wrapper class that encapsulates the functions for deploying and running the contract. For each public/external smart contract function the wrapper includes 2 functions. For example the Solidity function:
function add(address addr) external
...has the two wrapper functions:
add(params)
addLocal(params)
These provide us with 2 alternative methods for calling the function:
add
wraps a call that is executed against a blockchain node incurring a gas fee.
addLocal
wraps a call that is executed on a local VM without incurring a gas fee.
Local calls are useful for reading the current contract state and for emulating transactions before these are run on the blockchain. Using addLocal
we can detect errors before sending the message to a node.
More about Gas
The discussion on wrapper functions is a good way to start digging into gas and transaction fees. But before going any further let me thank the TON community, especially Boris Ivanovsky for helping me better understand how gas works in Free TON.
Let's look at the solidity code starting from the constructor:
constructor() public {
tvm.accept();
. . .
This starts with a call to accept
from the tvm namespace
. With this call, the constructor is accepting to pay for the fees incurred on deployment.
Indeed, TON contracts can pay for their own gas. By default, the caller is expected to pay for gas, but the contract may choose to absorb this cost itself.
This is true for all public/external functions. Indeed, going back to the solidity code, note the modifier preceding the constructor:
modifier alwaysAccept {
tvm.accept();
_;
}
This is being applied to the add
, remove
, and update
solidity functions. Whether you would really want to do this, is of course application specific.
Another interesting difference between Ethereum and TON is the gas consumption of pure
and view
functions. These do not incur gas in Ethereum. However, in TON this works a little bit differently.
As already explained, in TON we have a choice between running functions locally or on the blockchain. If a pure/view
function is run on the blockchain, gas is incurred. To avoid paying such fees these calls must be made locally.
Let us see this at work in our test.js code. Here functions that change the contract state are run on the blockchain, but view
functions are run locally. For example this is what the list remove call looks like:
await list.remove({id: id})
. . . and here is an example call to a view function:
let first = await list.firstItemLocal()
The Giver Contract and Contract Deployment
As already discussed, the constructor is paying the gas fees for its own deployment. This is achieved by pre-loading the contract with credit before it is deployed. This is achieved as follows:
- Pre-compute the address for the contract that is about to be deployed
- Transfer credit to this address
- Deploy the contract
On a live blockchain, before going through this procedure, one would first need to somehow purchase or earn some TON Crystals. In our TON OS test environment credit is free! The installation is initialized with the Giver contract to which we can simply ask for credit.
Our node.js code includes the giver.js helper. This code is largely copied from the TON OS docs. To see the complete procedure in action, look at the load_credit
function:
async function load_credit(client, package, constructorParams, keyPair, credit) {
//Determine the contract address
const futureAddress = (await client.contracts.createDeployMessage({
package,
constructorParams,
keyPair,
})).address
//Load contract with credit
await get_grams_from_giver(client, futureAddress, credit)
return futureAddress
}
Mappings, Memory and Storage
The TON mapping type is more feature rich than its Ethereum counterpart. Amongst other things, it includes the ability to iterate over mapping entries and to check for key existence.
This meant that we could now use the TON fetch
function to verify item inclusion:
function remove(uint256 id) external alwaysAccept {
(bool exists, ListElement elem) = items.fetch(id);
require(exists && (elem.addr != address(0x0)), ...
Another difference that had to be accounted for, is the fact that memory/storage access modifiers have no effect in TON. As this compiler warning pointed out:
"Warning: Memory access modifiers have no effect in TON."
For example, our Ethereum add
function included this code:
ListElement storage prevElem = items[lastItem()];
...
prevElem.next = nextItem;
With this code, changes to prevElem
are persisted to storage in Ethereum, but not in TON. So the code was replaced by:
items[lastItem()].next = nextItem;
If you refer to the GitHub file change history, you will see that all changes related to mappings were dictated by these 2 considerations.
Node.js Test
The Node.js test is quite simple. It relies on one external package, ton-client-node-js. To run this code:
Save all files under FreeTONList to the machine running TON OS.
Compile the smart contract as shown earlier, to generate the ListContractContract.js
wrapper. Alternatively, copy this from GitHub to the same directory where the solidity file is located (/FreeTONList).
Move to the directory /FreeTONList/test and restore the node packages with:
> npm install
Run the test with:
> node test
Contract Address before deployment: 0:a5046a2e1f3653c3ad35ecf3c5384486d4bdccb8f7007ed12c6231001b51e0a0
Contract Deployed: 0:a5046a2e1f3653c3ad35ecf3c5384486d4bdccb8f7007ed12c6231001b51e0a0
First Item Id 0x1
Last Item Id 0x5
First Item Id 0x2
Last Item Id 0x4
Next Read Pos: 0x0
Addr List Out: 0:0000000000000000000000000000000000000000000000000000000000000002, 0:0000000000000000000000000000000000000000000000000000000000000004
The test starts by deploying a new contract instance, loads it with credit, adds 5 list entries and removes 3 of them. In between, it logs the ids of the first and last items. Finally, it dumps the items remaining in the list.
Concluding Remarks
This concludes my first Free TON Solidity contract. Converting this Ethereum contract to run on TON forced me to take my first dive. However, my learning journey has only just started. Post a comment if you enjoyed reading about this and I will keep you posted as I dig deeper into the blockchain of the free...
References
Running TON OS on Windows
Source code for this article
TON Solidity API
RunLocal Method
Run get method