Skip to main content

Storage

The increment example demonstrates how to call a contract from another contract.

Open in Gitpod

Run the Example

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

git clone -b v22.0.1 https://github.com/stellar/soroban-examples

Or, skip the development environment setup and open this example in Gitpod.

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_short!("COUNTER");

#[contract]
pub struct IncrementContract;

#[contractimpl]
impl IncrementContract {
pub fn increment(env: Env) -> u32 {
let mut count: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0);
log!(&env, "count: {}", count);

count += 1;
env.storage().instance().set(&COUNTER, &count);
env.storage().instance().extend_ttl(50, 100);

count
}
}

Ref: https://github.com/stellar/soroban-examples/tree/v22.0.1/increment

How it Works

This contract will get a counter value from storage (or use the value 0 if no value has been stored yet), and increment this counter every time it's called.

Open the increment/src/lib.rs file or see the code above to follow along.

Contract Data Key

Contract data is associated with a key, which can be used at a later time to look up the value.

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

Symbol is a short (up to 32 characters long) string type with limited character space (only a-zA-Z0-9_ characters are allowed). Identifiers like contract function names and contract data keys are represented by Symbols.

The symbol_short!() macro is a convenient way to pre-compute short symbols up to 9 characters in length at compile time using Symbol::short. It generates a compile-time constant that adheres to the valid character set of letters (a-zA-Z), numbers (0-9), and underscores (_). If a symbol exceeds the 9-character limit, Symbol::new should be utilized for creating symbols at runtime.

Contract Data Access

let mut count: u32 = env
.storage()
.instance()
.get(&COUNTER)
.unwrap_or(0); // If no value set, assume 0.

The Env.storage() function is used to access and update contract data. 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.

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 a u32. Otherwise, if a developer caused it to be some other type, a panic would occur at the unwrap.

env.storage()
.instance()
.set(&COUNTER, &count);

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

Managing Contract Data TTLs with extend_ttl()

env.storage().instance().extend_ttl(100, 100);

All contract data has a Time To Live (TTL), measured in ledgers, that must be periodically extended. If an entry's TTL is not periodically extended, the entry will eventually become "archived." You can learn more about this in the State Archival document.

For now, it's worth knowing that there are three kinds of storage: Persistent, Temporary, and Instance. This contract only uses Instance storage: env.storage().instance(). Every time the counter is incremented, this storage's TTL gets extended by 100 ledgers, or about 500 seconds.

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 = env.register(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();

The contract is registered with the environment using the contract type.

let contract_id = env.register(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.

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

The test asserts that the result that is returned is as we expect.

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

Build the Contracts

To build the contract into a .wasm file, use the stellar contract build command.

stellar contract build

The .wasm file should be found in the contract target directory after building the contract:

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

Run the Contract

If you have stellar-cli installed, you can deploy the contract, and invoke the contract function.

Deploy

stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/soroban_increment_contract.wasm \
--id a

Invoke

stellar contract invoke \
--wasm target/wasm32-unknown-unknown/release/soroban_events_contract.wasm \
--id 1 \
-- \
increment

Result

The following output should occur the first time the code above is used.

1

The value will be incremented every time the contract function is invoked.