Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Storage to example contracts #1076 #1210

Merged
merged 2 commits into from
Jan 22, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 265 additions & 0 deletions docs/build/smart-contracts/example-contracts/storage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
---
title: Storage
description: Increment a counter and store the incremented value
sidebar_position: 60
---

<head>
<title>Increment a counter and store the incremented value.</title>
<meta charSet="utf-8" />
<meta
property="og:title"
content="Increment a counter and store the incremented value."
/>
<meta
property="og:description"
content="Use storage to retain the value of a counter in an incrementer smart contract"
/>
</head>

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

<Tabs groupId="platform" defaultValue={getPlatform()}>

<TabItem value="unix" label="macOS/Linux">

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

</TabItem>

<TabItem value="windows" label="Windows (PowerShell)">

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

</TabItem>

</Tabs>

### Invoke

<Tabs groupId="platform" defaultValue={getPlatform()}>

<TabItem value="unix" label="macOS/Linux">

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

</TabItem>

<TabItem value="windows" label="Windows (PowerShell)">

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

</TabItem>

</Tabs>

### 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
Loading