Solidity vs FunC
Introduction
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.
The sections below will analyze briefly the following aspects of these languages: data types, storage, functions, flow control structures, and dictionaries (hashmaps).
Differences of Solidity and FunC
Storage layout
Solidity
Solidity uses a flat storage model, meaning it stores all state variables in a single, continuous block of memory called storage. The storage is a key-value store where each key is a 256-bit integer representing the storage slot number, and each value is the 256-bit word stored at that slot. Ethereum numbers the slots sequentially, starting from zero, and each slot can store a single word. Solidity allows the programmer to specify the storage layout using the storage keyword to define state variables. The order in which you define the variables determines their position in the storage.
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 address type contains the suffix keyword
payable,
it restricts it from storing only wallet addresses and using the transfer and send crypto functions. - Byte arrays — declared with the keyword bytes, is a fixed-size array used to store a predefined number of bytes up to 32, usually declared along with the keyword.
- Literals — Immutable values such as addresses, rationals and integers, strings, Unicode, and hexadecimal can be stored in a variable.
- Enums
- Arrays fixed or dynamic)
- Structs
- Mappings
FunC
In the case of FunC, the main data types are:
- Integers
- Cell — basic for TON opaque data structure, which contains up to 1,023 bits and up to 4 references to other cells
- Slice and Builder — special flavors of the cell to read from and write to cells,
- Continuation — another flavour of cell that contains ready-to-execute TVM byte-code
- Tuples — is an ordered collection of up to 255 components, having arbitrary value types, possibly distinct.
- Tensors — is 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 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 supports dynamic typing and functional programming styles.
(int x, int y) = (1, 2); // A tuple containing two integer variables
var z = x + y; // Dynamic variable declaration
Read more in 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 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 in 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 are only applicable to state variables that serve as store reference types. When you initialize mappings, they include every possible key and map to values whose byte representations are all zeros.
FunC
An analogy 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. Hashmap maps keys to values of arbitrary type 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-orienteered 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()
function is executed when a contract is accessed directly within the blockchain. For example, when a contract accesses our contract.- The function accepts the amount of the contract balance, the amount of the incoming message, the cell with the original message, and the
in_msg_body
slice, which stores only 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 withsend_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("EQBIhPuWmjT7fP-VomuTWseE8JNWv2q7QYfsVQ1IZwnMk8wL"a) ;; 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()
- createsBuilder
for the future cell, firststore_uint
- stores the first uint intoBuilder
(1 - this is ourop
), secondstore_uint
- stores the second uint intoBuilder
(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 presented how smart contracts can communicate with each other.
Read more in Internal Messages page.