This is the second article in our series on integrating payment channels on the Telegram Open Network. In the first part, we presented the network, detailed our experience of the competition and explained how synchronous and asynchronous smart contracts work. As the next addition to the series, this article explains how we created a synchronous payment channel over the network during the TON competition in September. Here, we will only talk about Fift (TON general purpose programming language) and FunC (TON programming language for writing smart contracts).
The TON white paper provides more detailed information on payment channels, but we will explain them again briefly.
Related: Behind the Scenes of TON: Lessons Learned on Deploying Smart Contracts, Part 1
A synchronous payment channel allows transactions between two off-chain users to be sent using chain assets. In our case – GRAMs. It is impossible for one party to deceive the other off-chain, and transactions are carried out much faster than the execution of layer one blockchain transactions, since only user devices are used to complete them without having to write in the blockchain. There are two basic operations: deposit and withdraw. Withdrawal is the most difficult to implement.
To make a correct withdrawal, users must provide the latest information on the status of their channel. The report consists of steps and digital signatures of each participant, which means that it is not possible to provide a correct report with data which has not been approved by both parties.
To deploy a smart contract, you must write a deployment script in Fift and compile it in a .boc (cell bag). This operation creates several cells which will be linked to each other. The GRAMs must then be sent to the address received during the execution of the deployment script. Once the GRAM at the address, send it .boc file to the network and the contract will be deployed.
To make a function call, write a script that will send an external message to the deployed smart contract.
Basically everything about TON is a cell with a few references. A cell bag is a data structure designed by the Telegram team. He is an actor model. More details are in TON’s white paper: “Everything is a bag of cells.” You build a cell that will interact with another cell during its deployment.
Each peer-to-peer payment channel is a unique smart contract. Let’s take a look at the segments of a smart contract.
Related: What To Expect From The Telegram Open Network: A Developer’s Perspective
A serialized Fift script is used to deploy a contract. It is saved in a .boc file and sent to the network via TON Cli, the network thin client.
The last cell in the stack is the result of executing the above Fift script.
Typical segments of a Fift deployment script include (but are not limited to):
- Smart contract code as a single cell (usually written in FunC, then compiled in ASM Fift code and included in the main .FIF file using path-to-compiled asm.fif).
- Initial storage of the smart contract (see below).
- New smart contract address (the hash from the initial state of the smart contract which also includes the smart contract code cell and the initial storage cell).
- Arguments of the first appeal of the recv_external (the number of arguments and the type depend on the contract).
- An external message cell for initialization, which will be serialized in bytes and compressed in the .boc file, which includes all the data from points 1 to 4 and some others which still lack documentation.
When the .boc is compiled, a specific number of GRAMs must be sent to the address of the smart contract. The .boc file must be sent to the network to initialize the smart contract. The quantity of GRAM depends on the size and volume of the calculations of the external message cell of the deployed smart contract (not just its code). The price of gas × gas is taken from the balance of the smart contract deployed. This amount is the minimum necessary to pay for gas during deployment.
A representation of the storage:
- seqno 32 bit
- contrat_état 4 bits
- first_user_pubkey. Public key of the first part 256 bit
- second_user_pubkey. Public key of the second part 256 bit
- time_to_send. Shipping time after submission of the first actual report 32 bit (valid until 2038)
- depositSum. The sum deposited of two participants up to 121 bit
- state_num 64 bit. The current number of states occurring
A cell contains up to 1023 bits and four references to other cells. We were able to adapt all of the storage on a cell without a single reference. Our storage can take up to 765 bits.
All smart contract states
0x0 – Deployment status
0x1 – Chain open and ready for deposit
0x2 – User deposit 1
0x3 – User deposit 2
0x4 – The deposit is blocked. It is possible to give a status to the smart contract
0x5 – User 1 provided the status
0x6 – User 2 provided the status
0x7 – The chain is closed
The deposit function receives a message from a simple wallet (transfer) with an additional body payload.
Place GRAMs on the chain:
- The user generates an additional body payload which includes a message (for example, 1 bit) and his signature in another .FIF file.
- The body’s payload is compiled into one .boc file.
- The body’s payload is charged from this .boc file in a .fif file as a “transfer” reference from .FIF is responsible for transferring the GRAMs from the portfolio).
- the recv_external The function is called with arguments (the deposit amount and the destination address of the channel) when the compiled .FIF the file is sent to the network.
- the send_raw_message is executed. The GRAMs deposited and the body’s additional payload are sent to a P2P channel smart contract destination address.
- the recv_internal The P2P channel smart contract function is called. GRAMs are received by channel contracts.
The deposit function can be called if the P2P channel smart contract status is 0x1 or 0x2 or 0x3.
FunC code that checks the status:
Only the owners of public keys (written in the initial storage) are authorized to make a deposit. The smart contract checks the signature of each internal message that will be received via the recv_internal a function. If the message is signed by one of the owners of the public key, the contract status changes to 0x2 or 0x3 (0x2 if it is public key 1 and 0x3 if it is a public key 2). If all users have made a deposit, the contract status changes to 0x4 on the same function call.
The FunC code responsible for changing the contract status:
Funds can be returned if a counterparty has not made a deposit on time.
To do this, a user must provide his address and signature via an external message. The funds will be reimbursed if the signature provided belongs to public key 1 or public key 2 (people who have made a deposit) and the contract status is 0x2 or 0x3.
FunC code responsible for verifying the refund request:
Each person must provide an exit status, the signature of this state and the signature of the body message.
- Smart contract address (to exclude the possibility of entering the correct state of the previous P2P channel with the same participants).
- The final balance of the first participant.
- The final balance of the second participant.
- State number.
The signature of the body message is stored in the main section, the state is stored in a separate reference and the state signatures are stored as references to a “signatures” reference to avoid cell overflow.
Check the signature of the body message and determine the participant.
Check that it is the participant’s turn or that 24 hours have passed since the last entered state. Write the tour of the current participant (0x5 or 0x6) to the contract status.
An example of correct signature of the body message for the owner of first_user_pubkey:
We must then verify that the address of the smart contract written to the State is the real address of the contract:
Then we need to check the signatures under the state:
After that, there are two statements:
- The amount deposited from the warehouse must be equal to the sum of the total balances of the participants.
- The new status number entered must be greater than or equal to the previous one.
In case of new_state_num> state_num we have to store new_state_num with the new time_to_send equal to now () + 86401 (24 hours from the current time), and also write the actual contract status (0x5 if the first participant made a call, otherwise 0x6).
In another case, if new_state_num == state_num we must add two additional references to the “signatures” reference with the addresses of each participant and the signatures below their addresses.
If the signatures are correct, the GRAMs are removed from a single address and placed in the owner’s address.
Whenever a call is successful, we must store all the storage data even if it does not change.
The assumption is that the first user has deployed the contract and the participants have agreed on the commissions. The commission agreement in our case comes off the chain.
We have not yet figured out how to calculate the total commission, taking into account that players can write an irrelevant state and save real states after that. Keep in mind that we have to pay the P2P channel smart contract fees each time we successfully call recv_internal or recv_external the functions.
As mentioned earlier, we need to add a certain amount of GRAM to a future non-reboundable smart contract address in order to initialize it.
On the last day of the contest, the developers of TON committed to stdlib.fc library with a new function which allows to obtain the real balance of the smart contract.
Suggestions for possible solutions to this problem are welcome!
FunC and Fift allow any developer to access the low-level world of software engineering, opening up new opportunities and features for blockchain developers who are already used to Ethereum or any other smart contract platform. It is important that TON is a fragmented blockchain, so it is more difficult to implement smart contracts. For example, Ethereum contracts run synchronously and do not require handling situations such as waiting for a response from another contract.
The asynchronous method of intelligent contractual communication is the only option to make it scalable, and TON has these options. Our solution has proven to be more difficult to implement than Solidity, but there is always a compromise. It is certainly possible to build an advanced smart contract on TON, and the way the TON team managed it is very impressive. We look forward to seeing more libraries and tools that will help deploy and build FunC contracts.
We really appreciated all the tasks and wish to have more time to implement them. However, we won two awards in the TON competition: first place for the best synchronous payment channel and third place for the best asynchronous payment channel.
We will share our own personal comments in the third part.
The views, thoughts and opinions expressed here are the sole authors and do not necessarily reflect or represent the views and opinions of Cointelegraph.