Table of Contents
- Solidity Syntax, Data Types, and Basic Constructs
- Rust Syntax, Data Types, and Ownership Model
- Writing and Interacting With Simple Smart Contracts
Solidity Syntax
Solidity is a programming language designed specifically for creating smart contracts on the Ethereum Virtual Machine (EVM). It has a syntax similar to JavaScript and supports a variety of data types and constructs.Data Types
Solidity supports various data types, such as:- Boolean:
bool - Integer:
int(signed) anduint(unsigned) - Address:
address - String:
string - Bytes:
bytes(dynamic-size) andbytes32(fixed-size) - Arrays: dynamic-size or fixed-size and can be declared with various types.
- Structs:
struct - Enums:
enum - Mapping:
mapping
Basic Constructs
Some of the basic constructs in Solidity include:Variables: Declared with a data type and an identifier.Functions: Defined with thefunctionkeyword.Modifiers: Used to modify functions’ behavior.Events: Used to log changes in the contract state.Inheritance: Solidity supports single and multiple inheritance.
Rust Syntax
Rust is a programming language that is well-suited for building smart contracts due to its emphasis on safety, concurrency, and performance. It enforces strict ownership and borrowing rules to prevent data races and other common bugs.Data Types
The Soroban Rust SDK supports a variety of Built-In Types which consist of both Primitive and Custom Types, such as:Primitive Data Types
-
32-bit Integers: signed (
i32) and unsigned (u32) -
64-bit Integers: signed (
i64) and unsigned (u64) -
128-bit Integers: signed (
i128) and unsigned (u128) -
Bool (
bool) -
Bytes, Strings (
Bytes,BytesN): byte arrays and strings that can be passed to contracts and stores -
Vec (
Vec): sequential and indexable growable collection type -
Map (
Map): ordered key-value dictionary -
Address (
Address): universal opaque identifier used in contracts -
String (
String): a contiguous growable array type containing u8s and requires an env to be passed in -
Symbol:
- (
Symbol::new): small efficient strings up to 32 characters in length and requires an env to be passed in - (
symbol_short!) small efficient strings up to 9 characters in length
a-zA-Z0-9_and are encoded into 64-bit integers. - (
Custom Data Types
Structs(with Named Fields): A custom type consisting of named fields stored on the ledger as amapof key-value pairs.Structs(with Unnamed Fields): A custom type consisting of unnamed fields stored on the ledger as a vector of values.Enum(Unit and Tuple Variants): A custom type consisting of unit and tuple variants stored on the ledger as a two-element vector, with the first element being the name of the variant and the second being the value.Enum(Integer Variants): A custom type consisting of integer variants stored on the ledger as theu32value.
A Brief Introduction to Modules, Macros, Structs, Traits, and Attribute Macros
In this section, we will provide a concise introduction to some fundamental concepts in Rust:Modules, Macros, Structs, Traits, and Attribute Macros.
These concepts are essential for understanding and writing efficient Rust code, and they will assist you on your journey as a smart contract developer.
1. Modules
Modules in Rust are used to organize and separate code into different namespaces. They enable better code organization, reusability, and encapsulation. To define a module, use themod keyword followed by a block containing the module’s contents.
2. Macros
Macros in Rust are powerful tools that allow you to do metaprogramming, enabling you to build chunks of reusable code at compile time. There are two basic types: declarative and procedural macros. The most common is the declarative macro, or plain “macro”, which is defined withmacro_rules!
3. Structs
Structs are custom data types in Rust that enable you to bundle data together. They provide a way to define and create more complex data structures.4. Traits
Traits in Rust define a shared set of behaviors that types can then either use as-is (default implementations) or implement themselves. They can be thought of as interfaces in other languages. Traits are defined with thetrait keyword, and their methods can be implemented for different types using the impl keyword.
5. Attribute Macros
Attribute macros in Rust are a form of procedural macros that enable you to define custom attributes for various language elements such as functions, structs, and enums. They can modify or generate code based on the annotated items.#[contractimpl] which exports publicly accessible functions to the Soroban environment.
Functions that are publicly accessible in the implementation are invocable by other contracts, or directly by transactions, when deployed.
Ownership Model
Rust enforces strict ownership rules to manage memory and resources:- Each value has a single owner.
- When the owner goes out of scope, the value is automatically deallocated.
Borrowing: Values can be borrowed as immutable or mutable references.Lifetimes: Used to ensure that references remain valid.
Smart Contract Dialect
Contract development in Rust involves certain restrictions due to either unavailable features in the deployment environment or high runtime costs. Thus, the code written for contracts can be seen as a distinct dialect of Rust, focusing on deterministic behavior and minimized code size. To learn more about Rust’s Contract Dialect, check out the Contract Rust Dialect Page.Writing and Interacting with Simple Smart Contracts
In this section, we’ll learn how to write and interact with simple smart contracts in Solidity and Rust.Writing a Smart Counter in Solidity
Here’s an example of a simple Solidity smart contract for a counter:0.8.0 or higher.
Counter.
private variable called _count of type uint256 (unsigned integer). This variable will be used to store the current value of the counter. It is marked as private, which means it can only be accessed from within the contract.
getCount() that returns the current value of the counter. The function is marked as public, which means it can be called from outside the contract. The view keyword indicates that this function doesn’t modify the state of the contract. The returns keyword specifies the return type of the function.
increment() that increments the counter by 1. It doesn’t return anything, but it modifies the state of the contract. Like getCount(), it’s marked as public, which means it can be called from both inside and outside the contract.
Interacting with the Solidity Smart Counter
We can interact with the smart contract using theRemix IDE. To do so, follow these steps:
- Click the following link to open the Gist in Remix.
-
Navigate to the
Counter.solfile in the file explorer.

- Press
Ctrl/Cmd + sto compile the contract. - Navigate to the
Deploy & Run Transactionstab and click theDeploybutton.

Deployed Contracts tab:

- Click the
incrementbutton to increment the counter. - Click the
getCountbutton to get the current count.

Writing a Smart Counter in Rust
In this section, we’ll create a Rust program that simulates the functionality of the Counter smart contract. Here’s an example of a simple counter in Rust:Soroban Rust SDK, a Rust-based smart contract development toolkit developed by the Stellar Development Foundation (SDF). The Soroban Rust SDK provides a powerful set of tools for writing smart contracts that run on the Soroban Virtual Machine.
Here’s a line-by-line explanation of what the code is doing:
no_std, we establish a leaner, “barebones” starting point for projects, encompassing only the Rust core and a few other essential components, rather than the full breadth of the standard library.
contractimpl macro is used to implement the smart contract, while the log macro is used for logging messages. The Env struct represents the environment the contract is executing in, and the Symbol type is a small, efficient string type.
Symbol value with the string “COUNTER”. The constant COUNTER is then used as a key to identify the count value stored in the contract storage.
IncrementContract, which will contain the implementation of the smart contract.
IncrementContract struct as a smart contract.
As previously noted, the #[contractimpl] attribute exports public functions to the Soroban environment. Meaning, these functions become accessible within the implementation and can be invoked by other contracts or directly by transactions upon deployment.
increment that takes an Env struct as an argument and returns a u32. Env is the environment the contract is executing in, and u32 is the type of value returned by the function.
count of type unsigned 32-bit integer (u32) is being created. The storage environment is accessed using env.storage(), and the value associated with the key COUNTER is retrieved using the get method. If there is no value set for the key COUNTER, a default value of 0 is used.
log macro provided by the Soroban Rust SDK.
set method on the storage object.
get_count that takes an Env struct as an argument and returns a u32. Once more we see the Env which is the environment the contract is executing in, and u32 as the type of the value returned by the function.
COUNTER from the contract storage. If there is no value set for the key COUNTER, a default value of 0 is used. Finally, the unwrap() method is called to extract the actual value from the Ok wrapper, which is then returned to the caller of the function.
Now that we have written our smart contract, it’s time to explore how we can interact with it using the Stellar CLI, one of many robust Developer Tools available. This powerful command-line tool allows us to interact with the Soroban Virtual Machine from a local machine, providing us with an efficient and flexible way to manage our smart contract.
Interacting with the Rust Smart Counter
To interact with the Rust counter, create a new Rust library using the cargo new command.src/lib.rs file with the code example above.
Cargo.toml file:
Note: For a detailed explanation of the Cargo.toml configuration used in this tutorial, check out the Hello World Example.
Next, build the project using the stellar contract build command.
target/wasm32v1-none/release directory.
To interact with the contract, we can use the stellar contract invoke command from the stellar-cli tool. Here’s an example of invoking the increment function on a contract with ID 1:
stellar contract invoke command to increment the counter multiple times.
To get the current value of the counter, you can use the following command: