sidebar_position | title |
---|---|
2 |
Increment |
The increment example demonstrates how to write a simple contract, with a single function that increments an internal counter and returns the value.
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
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
Open the increment/src/lib.rs
file to follow along.
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.
Symbol
s 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");
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);
Open the increment/src/test.rs
file to follow along.
#[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 = FixedBinary::from_array(&env, [0; 32]);
env.register_contract(&contract_id, HelloContract);
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);
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
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