diff --git a/docs/build/smart-contracts/example-contracts/storage.mdx b/docs/build/smart-contracts/example-contracts/storage.mdx new file mode 100644 index 000000000..d4bf13a9d --- /dev/null +++ b/docs/build/smart-contracts/example-contracts/storage.mdx @@ -0,0 +1,265 @@ +--- +title: Storage +description: Increment a counter and store the incremented value +sidebar_position: 60 +--- + + + Increment a counter and store the incremented value. + + + + + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import { getPlatform } from "@site/src/helpers/getPlatform"; + +The [increment example] demonstrates how to call a contract from another contract. + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)][oigp] + +[oigp]: https://gitpod.io/#https://github.com/stellar/soroban-examples/tree/v22.0.1 +[increment example]: https://github.com/stellar/soroban-examples/tree/v22.0.1/increment + +## 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: + +[setup]: ../getting-started/setup.mdx + +``` +git clone -b v22.0.1 https://github.com/stellar/soroban-examples +``` + +Or, skip the development environment setup and open this example in [Gitpod][oigp]. + +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 + +```rust title="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. + +```rust +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 + +```rust +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. + +[the environment]: https://developers.stellar.org/docs/learn/encyclopedia/contract-development/environment-concepts + +```rust +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()` + +```rust +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](../../../learn/encyclopedia/storage/state-archival.mdx) 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](../../../learn/fundamentals/stellar-data-structures/ledgers.mdx), or about 500 seconds. + +## Tests + +Open the `increment/src/test.rs` file to follow along. + +```rust title="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. + +```rust +let env = Env::default(); +``` + +The contract is registered with the environment using the contract type. + +```rust +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. + +```rust +let client = IncrementContractClient::new(&env, &contract_id); +``` + +The test asserts that the result that is returned is as we expect. + +```rust +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. + +```sh +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 + + + + + +```sh +stellar contract deploy \ + --wasm target/wasm32-unknown-unknown/release/soroban_increment_contract.wasm \ + --id a +``` + + + + + +```powershell +stellar contract deploy ` + --wasm target/wasm32-unknown-unknown/release/soroban_increment_contract.wasm ` + --id a +``` + + + + + +### Invoke + + + + + +```sh +stellar contract invoke \ + --wasm target/wasm32-unknown-unknown/release/soroban_events_contract.wasm \ + --id 1 \ + -- \ + increment +``` + + + + + +```powershell +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. + +```json +1 +``` + +The value will be incremented every time the contract function is invoked. + +[`stellar-cli`]: ../getting-started/setup.mdx#install-the-stellar-cli