Solidity vs FunC
Smart contract development involves using predefined languages such as Solidity for Ethereum and FunC for TON. Solidity is an object-oriented, high-level, strictly typed language influenced by C++, Python, and JavaScript. It is designed explicitly to write smart contracts on Ethereum blockchain platforms.
FunC is a high-level language used to program smart contracts on TON Blockchain. It is a domain-specific, C-like, statically typed language.
Below, we briefly analyze data types, storage, functions, flow control, and dictionaries (hashmaps).
Differences between Solidity and FunC
Storage layout
Solidity
Solidity stores state variables in a flat key-value storage where each slot is a 256-bit word. Slots are numbered sequentially starting from zero, and declaration order (with packing rules) determines slot assignment. The storage
keyword sets data location for reference types; it does not define layout.
FunC
Permanent storage data in TON Blockchain is stored as a cell. Cells play the role of memory in the stack-based TVM. To read data from a cell, you need to transform a cell into a slice and then obtain the data bits and references to other cells by loading them from the slice. To write data, you must store data bits and references to other cells in a builder and cast the builder into a new cell.
Data types
Solidity
Solidity includes the following basic data types:
- Signed and Unsigned integers
- Boolean
- Addresses, typically around 20 bytes, are used to store Ethereum wallet or smart contract addresses. If the type is
address payable
, the address can receive Ether via.transfer
and.send
. - Byte arrays —
bytes
is a dynamically sized byte array; fixed-size byte arrays arebytes1
…bytes32
. - Literals — integer, string, hex, and address literals.
- Enums
- Arrays — fixed or dynamic
- Structs
- Mappings
FunC
In the case of FunC, the main data types are:
- Integers
- Cell — the basic opaque data structure in TON, containing up to 1023 bits and up to 4 references to other cells
- Slice and Builder — specialized views for reading from and writing to cells.
- Continuation — a TVM continuation used to manage execution flow.
- Tuples — are an ordered collection of up to 255 components, having arbitrary value types, possibly distinct.
- Tensors — are an ordered collection ready for mass assigning like:
(int, int) a = (2, 4)
. A special case of tensor type is the unit type()
. It represents that a function doesn’t return any value or has no arguments.
Currently, FunC does not support defining custom types. Read more about types in the Statements page.
Declaring and using variables
Solidity
Solidity is a statically typed language, meaning each variable's type must be specified when declared.
uint test = 1; // Declaring an unsigned variable of integer type
bool isActive = true; // Logical variable
string name = "Alice"; // String variable
FunC
FunC is a more abstract and function-oriented language. It is statically typed and supports type inference (e.g., var
).
(int x, int y) = (1, 2); // A tuple containing two integer variables
var z = x + y; // Type‑inferred variable declaration
Read more on the Statements page.
Loops
Solidity
Solidity supports for
, while
, and do { ... } while
loops.
If you want to do something 10 times, you can do it this way:
uint x = 1;
for (uint i; i < 10; i++) {
x *= 2;
}
// x = 1024
FunC
FunC, in turn, supports repeat
, while
, and do { ... } until
loops. The for
loop is not supported. If you want to execute the same code as in the example above on FunC, you can use repeat
int x = 1;
repeat(10) {
x *= 2;
}
;; x = 1024
Read more on the Statements page.
Functions
Solidity
Solidity approaches function declarations with a blend of clarity and control. In this programming language, each function is initiated with the keyword function
, followed by the function's name and its parameters. The function's body is enclosed within curly braces, clearly defining the operational scope. Additionally, return values are indicated using the returns
keyword.
What sets Solidity apart is its categorization of function visibility—you can designate functions as public
, private
, internal
, or external
. These definitions dictate the conditions under which developers can access and call other parts of the contract or external entities. Below is an example in which we set the global variable num
in the Solidity language:
function set(uint256 _num) public returns (bool) {
num = _num;
return true;
}
FunC
Transitioning to FunC, the FunC program is essentially a list of function declarations/definitions and global variable declarations. A FunC function declaration typically starts with an optional declarator, followed by the return type and the function name.
Parameters are listed next, and the declaration ends with a selection of specifiers—such as impure
, inline/inline_ref
, and method_id
. These specifiers adjust the function's visibility, ability to modify contract storage, and inlining behavior. Below is an example in which we store a storage variable as a cell in persistent storage in the FunC language:
() save_data(int num) impure inline {
set_data(begin_cell()
.store_uint(num, 32)
.end_cell()
);
}
Read more on Functions page.
Flow control structures
Solidity
Most of the control structures known from curly-braces languages are available in Solidity, including: if
, else
, while
, do
, for
, break
, continue
, return
, with the usual semantics known from C or JavaScript.
FunC
FunC supports classic if-else
statements, ifnot
, repeat
, while
, and do/until
loops. Also, since v0.4.0, try-catch
statements are supported.
Read more on the Statements page.
Dictionaries
Dictionary or hashmap data structure is essential for Solidity and FunC contract development because it allows developers to efficiently store and retrieve data in smart contracts, specifically data related to a specific key, such as a user’s balance or ownership of an asset.
Solidity
Mapping is a hash table in Solidity that stores data as key-value pairs, where the key can be any of the built-in data types, excluding reference types, and the data type's value can be any type. In Solidity and on the Ethereum blockchain, mappings typically connect a unique Ethereum address to a corresponding value type. In any other programming language, a mapping is equivalent to a dictionary.
In Solidity, mappings don't have a length or the concept of setting a key or a value. Mappings can only be declared in storage (state variables); they are not allowed in memory. When you initialize mappings, they include every possible key and map to values whose byte representations are all zeros.
FunC
The analog of mappings in FunC is dictionaries or TON hashmaps. In the context of TON, a hashmap is a data structure represented by a tree of cells. A hashmap maps keys to values of arbitrary types so that quick lookup and modification are possible. The abstract representation of a hashmap in TVM is a Patricia tree or a compact binary trie.
Working with potentially large cell trees can create several problems. Each update operation builds an appreciable number of cells (each cell built costs 500 gas), meaning these operations can run out of resources if used carelessly. To avoid exceeding the gas limit, limit the number of dictionary updates in a single transaction.
Also, a binary tree for N
key-value pairs contains N-1
forks, which means a total of at least 2N-1
cells. The storage of a smart contract is limited to 65536
unique cells, so the maximum number of entries in the dictionary is 32768
, or slightly more if there are repeating cells.
Read more about Dictionaries in TON.
Smart contract communication
Solidity and FunC provide different approaches to interacting with smart contracts. The main difference lies in the mechanisms of invocation and interaction between contracts.
Solidity
Solidity uses object-oriented contracts that interact with each other through method calls. This design is similar to method calls in traditional object-oriented programming languages.
// External contract interface
interface IReceiver {
function receiveData(uint x) external;
}
contract Sender {
function sendData(address receiverAddress, uint x) public {
IReceiver receiver = IReceiver(receiverAddress);
receiver.receiveData(x); // Direct call of the contract function
}
}
FunC
FunC, used in the TON blockchain ecosystem, operates on messages to invoke and interact between smart contracts. Instead of calling methods directly, contracts send messages to each other, which can contain data and code for execution.
Consider an example where a smart contract sender must send a message with a number, and a smart contract receiver must receive that number and perform some manipulation on it.
Initially, the smart contract recipient must describe how it will receive messages.
() recv_internal(int my_balance, int msg_value, cell in_msg, slice in_msg_body) impure {
int op = in_msg_body~load_uint(32);
if (op == 1) {
int num = in_msg_body~load_uint(32);
;; do some manipulations
return ();
}
if (op == 2) {
;;...
}
}
Receiving message flow:
recv_internal()
is invoked when the contract receives an inbound internal message.- Parameters: contract balance, message value, original message cell, and the
in_msg_body
slice—the body of the received message. - Our message body will store two integer numbers. The first number is a 32-bit unsigned integer
op
defining the smart contract's operation. You can draw some analogy with Solidity and think ofop
as a function signature. - We use
load_uint()
to readop
as a number from the resulting slice. - Next, we execute business logic for a given operation. Note that we omitted this functionality in this example.
Next, the sender's smart contract is to send the message correctly. This is accomplished with send_raw_message
, which expects a serialized message as an argument.
int num = 10;
cell msg_body_cell = begin_cell().store_uint(1,32).store_uint(num,32).end_cell();
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr) ;; in the example, we hardcode the recipient's address
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_ref(msg_body_cell)
.end_cell();
send_raw_message(msg, mode);
Sending message flow:
- Initially, we need to build our message. The complete structure of the send can be found here.
- The body of the message represents a cell. In
msg_body_cell
we do:begin_cell()
- creates builder for the future cell, firststore_uint
- stores the first uint into builder (1 - this is ourop
), secondstore_uint
- stores the second uint into builder (num - this is our number that we will manipulate in the receiving contract),end_cell()
- creates the cell. - To attach the body that will come in
recv_internal
in the message, we reference the collected cell in the message itself withstore_ref
. - Sending a message.
This example shows how smart contracts can communicate with each other.
Read more on the Internal messages page.
See also
- TON documentation
- FunC overview
- Six unique aspects of TON Blockchain that will surprise Solidity developers