Create a New Project
Create a new project using theinit command to create a soroban-hello-world project.
init command will create a Rust workspace project, using the recommended structure for including Soroban contracts. Let’s take a look at the project structure:
Cargo.toml
TheCargo.toml file at the root of the project is set up as Rust Workspace, which allows us to include multiple smart contracts in one project.
Rust Workspace
TheCargo.toml file sets the workspace’s members as all contents of the contracts directory and sets the workspace’s soroban-sdk dependency version including the testutils feature, which will allow test utilities to be generated for calling the contract in tests.
Cargo.toml
The
testutils are automatically enabled inside Rust unit tests inside the same crate as your contract. If you write tests from another crate, you’ll need to require the testutils feature for those tests and enable the testutils feature when running your tests with cargo test --features testutils to be able to use those test utilities.release Profile
Configuring the release profile to optimize the contract build is critical. The network enforces a maximum contract size (currently 128KB on Mainnet, a value that can change with network configuration). Rust programs, even small ones, without these configurations almost always exceed this size.
The Cargo.toml file has the following release profile configured.
release-with-logs Profile
Configuring a release-with-logs profile can be useful if you need to build a .wasm file that has logs enabled for printing debug logs when using the stellar-cli. Note that this is not necessary to access debug logs in tests or to use a step-through-debugger.
Contracts Directory
Thecontracts directory is where Soroban contracts will live, each in their own directory. There is already a hello-world contract in there to get you started.
Contract-specific Cargo.toml file
Each contract should have its ownCargo.toml file, which relies on the top-level Cargo.toml that we just discussed.
This is where we can specify contract-specific package information.
contracts/hello-world/Cargo.toml
crate-type includes cdylib, which is required for building contracts. It also includes lib, so other crates, such as a fuzz target, can import the contract as a Rust library.
Contract Source Code
Creating a Soroban contract involves writing Rust code in the project’slib.rs file.
All contracts should begin with #![no_std] to ensure that the Rust standard library is not included in the build. The Rust standard library is large and not well suited to being deployed into small programs like those deployed to blockchains.
soroban-sdk crate.
std::vec::Vec, are not available, as there is no allocator and no heap memory in Soroban contracts by default (the SDK does provide an opt-in allocator through its alloc feature — see the alloc example). The soroban-sdk provides a variety of types like Vec, Map, Bytes, BytesN, Symbol, that all utilize the Soroban environment’s memory and native capabilities. Primitive values like u128, i128, u64, i64, u32, i32, and bool can also be used. Floats and floating point math are not supported.
Contract inputs must not be references.
The #[contract] attribute designates the Contract struct as the type to which contract functions are associated. This implies that the struct will have contract functions implemented for it.
impl block for the struct, which is annotated with #[contractimpl]. It is important to note that contract functions should have names with a maximum length of 32 characters. Additionally, if a function is intended to be invoked from outside the contract, it should be marked with the pub visibility modifier. It is common for the first argument of a contract function to be of type Env, allowing access to a copy of the Soroban environment, which is typically necessary for various operations within the contract.
contracts/hello-world/src/lib.rs
mod test line at the bottom, this will tell Rust to compile and run the test code, which we’ll take a look at next.
Contract Unit Tests
Writing tests for Soroban contracts involves writing Rust code using the test facilities and toolchain that you’d use for testing any Rust code. Given ourContract, a simple test will look like this.
- contracts/hello-world/src/lib.rs
- contracts/hello-world/src/test.rs
Env, which is the Soroban environment that the contract will run inside of.
None and one will be generated.
impl block that is annotated with the #[contractimpl] attribute have a corresponding function generated in a generated client type. The client type will be named the same as the contract type with Client appended. For example, in our contract the contract type is Contract, and the client is named ContractClient.
Run the Tests
Runcargo test and watch the unit test run. You should see the following output:
The first time you run the tests you may see output in the terminal of cargo compiling all the dependencies before running the tests.
Build the contract
To build a smart contract to deploy or run, use thestellar contract build command.
cargo build that sets the target to wasm32v1-none and the profile to release (it also optimizes the resulting .wasm file by default). You can think of it as a shortcut for the following command:
.wasm file will be outputted in the target directory, at target/wasm32v1-none/release/hello_world.wasm. The .wasm file is the built contract.
The .wasm file contains the logic of the contract, as well as the contract’s specification / interface types, which can be imported into other contracts who wish to call it. This is the only artifact needed to deploy the contract, share the interface with others, or integration test against the contract.
Optimizing Builds
Thestellar contract build command optimizes the generated .wasm by default, so there’s no extra step needed to minimize the size of your contract. If you’d like to skip that optimization (during quick, iterative development, for example), you can disable it:
Older versions of the Stellar CLI provided a standalone
stellar contract optimize command. That command is now deprecated in favor of the (default) --optimize flag on stellar contract build.