TON Blockchain and Solidity
If you’ve worked with smart contracts before, you’ll probably know about Ethereum’s Solidity language and the EVM. As you start looking into TON development, it’s important to understand the different design choices that lead to different behaviours compared to what you might expect from Ethereum. This post aims to outline some of these key differences and provide insights into the rationale behind them.
Transitioning from Data to Big Data
The key to understanding TON is recognizing that it was engineered to bring blockchain technology to billions of people worldwide. This requires an immense scale, with the potential for billions of users conducting billions of transactions daily.
Consider this shift as moving from managing standard data to handling big data. For example, storing a restaurant’s menu in an SQL database is effective because it allows for powerful and flexible queries with all the data easily accessible. However, when it comes to storing every Facebook post made by billions of users globally, an SQL database is no longer a viable option. In this context of “big data,” aggressive sharding is necessary, which in turn limits the flexibility of the queries you can perform. It’s a matter of making different trade-offs to meet different objectives.
Here are six distinctive features of the TON blockchain that might surprise Solidity developers:
- Smart Contracts Must Pay Rent and Charge Users
The idea of using blockchain as a permanent, unchangeable storage solution is appealing in theory but proves challenging when scaled. Ethereum’s fee model is akin to that of a bank: when you send money, you pay a transaction fee, and the user who initiates the transaction covers this cost. Similarly, when you deploy a new smart contract on Ethereum, the sender of the deployment transaction pays a one-time fee. However, since the data on the blockchain is intended to be permanent, miners bear the ongoing infrastructure costs to maintain it indefinitely. This economic model becomes unsustainable at scale, especially when considering billions of users.
- From a Bank Model to an Instant Messenger Approach
TON takes a different approach to fees, drawing inspiration from web apps like instant messengers rather than banks. For example, when you send a message on Facebook Messenger, you don’t directly pay a fee. Instead, the app developer (Facebook Inc., now Meta) covers the cost, and it’s up to them to recoup these expenses and sustain the platform.
Similarly, in TON, the dapp itself must cover its resource costs. Each smart contract holds a balance of TON tokens, which it uses to pay for rent. If the smart contract runs out of funds, it will eventually be removed from the blockchain (though recovery is possible). Unlike Ethereum, where storage fees are paid only once, TON’s model requires continuous rent payments. This means that the longer data is stored, the more it costs, aligning better with the ongoing expenses miners incur, and making it more scalable.
- Flexible Funding Options for Developers
Just as Facebook Inc. has various ways to fund its operations, developers on TON have significant flexibility in financing their smart contracts. They can choose to subsidize the contract out of their own TON tokens or charge users gas fees for specific actions, using those fees to cover future rent payments. This flexibility allows developers to tailor their economic models according to their dapp’s needs and ensures a more sustainable operation over time.
- Smart Contract Interactions in TON Are Asynchronous and Non-Atomic
One of Ethereum’s greatest strengths in fostering a robust DeFi ecosystem is the smooth composability of smart contracts. Within a single transaction, you can perform complex operations such as using WBTC as collateral in a Compound contract, borrowing USDC, trading it on Uniswap for more WBTC, and leveraging your position—all in one go. This entire process is atomic, meaning that if any step fails, the entire transaction is rolled back as if it never occurred.
In Ethereum, when a smart contract calls a function on another smart contract, the call is executed immediately within the same transaction. This synchronous execution is akin to running your entire backend on a single server, where every component can directly and instantly interact with every other component. This approach is straightforward to manage and reason about but has a significant limitation: scalability. The system can only expand as long as everything remains within that single environment.
In contrast, TON handles smart contract interactions differently. Calls between smart contracts are asynchronous and non-atomic, meaning that when one contract calls another, the call doesn’t happen immediately within the same transaction. Instead, the process is broken up, and each step occurs independently. If something goes wrong in one step, the other steps don’t automatically roll back. This design is more scalable but requires developers to approach contract interactions with a different mindset, considering the potential delays and non-reversibility in the process.
Transitioning from a Single Server to a Microservices Cluster
If you think of Ethereum as a monolithic system running on a single server, TON is more akin to a cluster of microservices. In this setup, each smart contract might be running on a different machine. When two smart contracts need to communicate, they do so by sending messages across the network, much like microservices in a distributed system. Because these messages take time to travel, communication between contracts becomes asynchronous. This means that when your smart contract calls a method on another contract, the call is processed after the transaction is complete, in a subsequent block.
This asynchronous model introduces complexities that are not present in Ethereum’s synchronous environment. For instance, what if the conditions change between the time a message is sent and when it is received? Imagine a scenario where the balance of the calling contract has altered by the time the second contract processes the call. Ensuring consistency becomes more challenging, and the likelihood of bugs increases. Additionally, atomicity—where a series of operations either all succeed or all fail—no longer happens automatically. If you chain multiple calls and the last one fails, you’ll need to manually roll back the previous changes, adding another layer of complexity to smart contract development on TON.
- Your Smart Contract Can’t Directly Query Other Contracts
This limitation is a direct consequence of the asynchronous nature of contract interactions in TON. In Ethereum, where contract calls are synchronous, reading data from another smart contract is straightforward. For example, if your contract needs to know its balance of USDC, it simply calls the getBalance method on the USDC contract.
Returning to the analogy of a monolithic system running on a single server, one of the advantages is that every service can instantly access the state of any other service. This makes querying data between contracts in Ethereum easy and efficient. However, in TON, where contracts operate more like separate microservices, this direct access isn’t possible. Instead, retrieving data from another contract requires sending a message and waiting for a response, which complicates the process and introduces delays.
Shifting from a Single Server to a Microservices Cluster
In a system where separate microservices run on different machines, direct access to state memory across services becomes impossible. On TON, smart contracts can only interact by sending asynchronous messages. If your contract needs to query data from another contract and requires an immediate response, you’re out of luck.
The situation gets even more peculiar. If you come across getter methods on a TON smart contract, such as getBalance, these methods aren’t accessible from other smart contracts. Instead, getter methods are designed to be called only by off-chain clients, much like how an Ethereum wallet might use a service like Infura to query the state of any smart contract.
- Smart Contract Code on TON Is Not Immutable and Can Be Easily Modified
The concept of dapps on Ethereum draws inspiration from legal contracts, where the terms are set in stone once agreed upon—hence the term “smart contracts.” In this analogy, the developer writes the terms of the contract as code, and that code becomes the unchangeable law. In the real world, when two parties sign a contract, the agreement is fixed, and any changes require drafting a new contract.
Following this philosophy, Ethereum’s smart contract code was designed to be immutable, meaning it cannot be altered once deployed. However, over time, developers have found ways to work around this limitation by employing complex patterns, such as using proxy contracts that delegate calls to another contract, thereby allowing for upgrades without changing the original contract’s code.
In contrast, TON takes a different approach. The code of smart contracts on TON is not inherently immutable and can be modified more easily. This flexibility allows developers to update and improve their contracts without resorting to convoluted workarounds, making the process of upgrading smart contracts more straightforward on the TON blockchain.
Shifting from Legal Contracts to Software Engineering
Unlike lawyers, who assume that contracts are set in stone, software engineers understand that every piece of code is prone to bugs, and even if the code were flawless, requirements often evolve, necessitating updates and modifications.
In TON, the notion that smart contracts must be immutable is entirely abandoned. Smart contracts on TON can freely modify their own code, just as they would any other state variable. If a contract writes to its code variable, it becomes mutable; if it doesn’t, it remains immutable. This approach simplifies the process, eliminating the need for complex proxy patterns and making it easier to update and maintain smart contracts.
- Avoid Unbounded Data Structures in Your Contract State
This concept can be challenging to grasp at first, but it’s essential for understanding why certain smart contracts on TON are designed in specific ways.
Unbounded data structures in smart contracts are state variables that can grow without limit. Take, for example, an ERC20 contract like the one implementing the USDC token. This contract needs to maintain a mapping of user addresses to their respective balances. Since USDC can be minted in large quantities and divided into small fractions, the number of holders—and thus the number of entries in this map—can increase indefinitely.
Now, consider what happens if an attacker tries to overwhelm the contract by continuously adding new entries. Could this lead to a denial-of-service (DoS) attack, preventing legitimate users from interacting with the contract? On Ethereum, this issue is addressed effectively. The fee model requires users who write new data to the contract state to pay a fee for that data. This means that any attacker attempting to spam the contract would incur significant costs. Additionally, the gas cost for writing to a map on Ethereum is fixed, regardless of the map’s size, ensuring that legitimate users are not penalized by the spam.
In summary, Ethereum’s fee structure makes spamming with unbounded data structures economically unfeasible, and the system itself provides a layer of protection.
Transitioning from Unbounded Maps to Unbounded Contracts
For TON smart contract developers, the system doesn’t automatically protect against spamming unbounded data structures within a contract’s state. Unlike Ethereum, where gas fees for writes are constant, TON’s gas fee model makes write costs proportional to the amount of data in the structure. This is due to TON’s “Bag of Cells” architecture, where the contract state is divided into 1023-bit chunks known as “cells,” which the developer must manage. In this setup, maps are implemented as a tree of cells, and writing to a leaf node requires updating hashes along the entire tree. If an attacker spams the map with keys, they could push some user balances deep enough into the tree that updating them exceeds the gas limit.
To mitigate this risk, the best practice in TON is to avoid unbounded data structures in your contract state. This helps protect your contract from potential DoS attacks. The solution typically involves contract sharding: instead of storing an infinite number of user balances in a single USDC contract, you can split the contract into multiple child contracts, each managing the balance of a single user.
This approach explains why, on TON, NFT collection contracts place each item in a separate contract (since the number of items can be unlimited), and why fungible token contracts create individual contracts for each user’s balance.
To understand why TON is designed this way, it’s important to consider the limitations of Ethereum’s gas fee model, where the cost of writing to a map is fixed regardless of the map’s size. While this simplicity works for small maps, as maps grow to billions of entries, the effort required by miners to update them increases significantly, making a more scalable solution like TON’s necessary.
- Wallets Are Smart Contracts, and One Public Key Can Deploy Multiple Wallets
On Ethereum, a user’s wallet is directly tied to their address, which is derived from a public key and its corresponding private key. This creates a one-to-one relationship: one address per public key and one public key per address. As long as the user has access to their private key, they retain control over their wallet.
Moreover, on Ethereum, users don’t need to take any special actions to have a wallet—it’s simply their Ethereum address. This address can store ETH, hold ERC20 tokens and NFTs, and directly interact with other smart contracts by sending and signing transactions.
In contrast, on TON, wallets are actually smart contracts themselves. This means that a single public key can be associated with multiple wallets, each potentially offering different functionalities. As a result, users can deploy several wallets from the same public key, each tailored for specific purposes, providing a level of flexibility and customization not seen in Ethereum’s more straightforward model.
Shifting from Address to Smart Contract
In TON, wallets aren’t automatically provided; they are independent smart contracts that must be deployed just like any other contract. When a new user begins using the TON blockchain, their first task is to deploy a wallet on-chain. The wallet’s address is generated from the wallet contract’s code and various initialization parameters, such as the user’s public key.
This setup allows users to deploy multiple wallets, each with a unique address. These wallets can vary in their underlying code (as new versions are periodically released by the foundation) or in their initialization parameters (one common parameter is a sequence number). As a result, even if a user retains their private key, they still need to remember their wallet address or the specific initialization parameters used during its creation.
When sending a transaction to a dapp on TON, the process differs from Ethereum. The user signs a message with their private key, but instead of sending this transaction directly to the dapp’s smart contract, it is sent to the user’s wallet contract. The wallet contract then forwards the message to the dapp’s smart contract.
This design provides TON with a greater degree of flexibility. The community can develop new types of wallet contracts over time. For example, a wallet without a nonce (transaction sequence number) could be created, allowing multiple transactions to be sent in parallel from different clients without prior synchronization. Additionally, specialized wallets like multi-signature wallets—which on Ethereum require a separate smart contract—can function seamlessly alongside regular wallets on TON.