Skip to main content

Increment

The increment example demonstrates how to write a simple contract, with a single function that increments an internal counter and returns the value.

Run the Example

First go through the Setup process to get your development environment configured, then clone the v0.0.4 tag of soroban-examples repository:

git clone -b v0.0.4 https://github.com/stellar/soroban-examples

To run the tests for the example, navigate to the increment directory, and use cargo test.

cd increment
cargo test

You should see the output:

running 1 test
test test::test ... ok

Code

increment/src/lib.rs
const COUNTER: Symbol = symbol!("COUNTER");

pub struct IncrementContract;

#[contractimpl]
impl IncrementContract {
pub fn increment(env: Env) -> u32 {
let mut count: u32 = env
.contract_data()
.get(COUNTER)
.unwrap_or(Ok(0)) // If no value set, assume 0.
.unwrap(); // Panic if the value of COUNTER is not u32.
count += 1;
env.contract_data().set(COUNTER, count);
count
}
}

Ref: https://github.com/stellar/soroban-examples/tree/v0.0.4/increment

How it Works

Open the increment/src/lib.rs file to follow along.

Contract Data Keys

Contract data that is stored is stored associated with a key. The key is the value that can be used at a later time to lookup the value.

Symbols are a space and execution efficient value to use as static keys or names of things. They can also be used as short strings. When produced using symbol!(...) they are computed at compile time and stored in code as a 64-bit value. Their maximum character length is 10.

const COUNTER: Symbol = symbol!("COUNTER");

Contract Data Access

The Env contract_data() function is used to retrieve access and update a counter. The executing contract is the only contract that can query or modify contract data that it has stored. The data stored is viewable on ledger anywhere the ledger is viewable, but contracts executing within the Soroban environment are restricted to their own data.

The get() function gets the current value associated with the counter key.

let mut count: u32 = env
.contract_data()
.get(COUNTER)
.unwrap_or(Ok(0)) // If no value set, assume 0.
.unwrap(); // Panic if the value of COUNTER is not u32.

If no value is currently stored, the value given to unwrap_or(...) is returned instead.

Values stored as contract data and retrieved are transmitted from the environment and expanded into the type specified. In this case a u32. If the value can be expanded the type returned will be an Ok(u32), otherwise the type returned will be an Err(_). The final unwrap() in the above code snippet is assuming the value will always be a u32. If a developer caused it to be some other type a panic would occur at the unwrap.

The set() function stores the new count value against the key, replacing the existing value.

env.contract_data().set(COUNTER, count);

Tests

Open the increment/src/test.rs file to follow along.

increment/src/test.rs
#[test]
fn test() {
let env = Env::default();
let contract_id = BytesN::from_array(&env, &[0; 32]);
env.register_contract(&contract_id, IncrementContract);
let client = IncrementContractClient::new(&env, &contract_id);

assert_eq!(client.increment(), 1);
assert_eq!(client.increment(), 2);
assert_eq!(client.increment(), 3);
}

In any test the first thing that is always required is an Env, which is the Soroban environment that the contract will run in.

let env = Env::default();

Contracts must be registered with the environment with a contract ID, which is a 32-byte value.

let contract_id = BytesN::from_array(&env, &[0; 32]);
env.register_contract(&contract_id, IncrementContract);

All public functions within an 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 IncrementContract, and the client is named IncrementContractClient.

let client = IncrementContractClient::new(&env, &contract_id);

The values returned by functions can be asserted on:

assert_eq!(client.increment(), 1);

Build the Contract

To build the contract, use the cargo build command.

cargo build --target wasm32-unknown-unknown --release

A .wasm file should be outputted in the ../target directory:

../target/wasm32-unknown-unknown/release/soroban_increment_contract.wasm

Run the Contract

If you have [soroban-cli] installed, you can invoke contract functions in the WASM using it.

soroban-cli invoke \
--wasm ../target/wasm32-unknown-unknown/release/soroban_increment_contract.wasm \
--id 1 \
--fn increment

The following output should occur using the code above.

1

Run it a few more times to watch the count change.

Use the soroban-cli to inspect what the counter is after a few runs.

soroban-cli read --id 1 --key COUNTER