DEV Community

yuzurush
yuzurush

Posted on • Updated on

Soroban Contracts 101 : Custom Types

Hi there! Welcome to my third post of my series called "Soroban Contracts 101", where I'll be explaining the basics of Soroban contracts, such as data storage, authentication, custom types, and more. All the code that we're gonna explain throughout this series will mostly come from soroban-contracts-101 github repository.

In this third post of the series, I'll be covering soroban contract custom types. Custom types in smart contracts allow you to define your own complex types to use in your contract logic. Custom types allow you to raise the abstraction level of your contract code and can make it more readable, maintainable and robust.

The Contract Code

#![no_std]
use soroban_sdk::{contractimpl, contracttype, symbol, Env, Symbol};

#[contracttype]
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct State {
    pub count: u32,
    pub last_incr: u32,
}

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

pub struct IncrementContract;

#[contractimpl]
impl IncrementContract {
    /// Increment increments an internal counter, and returns the value.
    pub fn increment(env: Env, incr: u32) -> u32 {
        // Get the current count.
        let mut state = Self::get_state(env.clone());
        // Increment the count.
        state.count += incr;
        state.last_incr = incr;
        // Save the count.
        env.storage().set(&STATE, &state);
        // Return the count to the caller.
        state.count
    }
    /// Return the current state.
    pub fn get_state(env: Env) -> State {
        env.storage()
            .get(&STATE)
            .unwrap_or_else(|| Ok(State::default())) // If no value set, assume 0.
            .unwrap() // Panic if the value of COUNTER is not a State.
    }
}

mod test;
Enter fullscreen mode Exit fullscreen mode

This code is evolved version of the Storing Data Contract from the second post, it now :

  • Stores a State struct in storage instead of a single count value
  • State struct has count and last_incr fields
  • The increment function takes an increment amount as an argument and increments by that amount
  • It also stores the last increment amount in the State
  • It returns the new total count

So this contract now stores more data (the last increment amount as well as the total count), and allows specifying the increment amount on each call. Other than that, it works similarly - storing data persistently in blockchain storage and returning updated values on each call.

The Test Code

#[test]
fn test() {
    let env = Env::default();
    let contract_id = env.register_contract(None, IncrementContract);
    let client = IncrementContractClient::new(&env, &contract_id);

    assert_eq!(client.increment(), 1);
    assert_eq!(client.increment(), 2);
    assert_eq!(client.increment(), 3);
}
Enter fullscreen mode Exit fullscreen mode

This code is a unit test for our contract. It does the following:

  • Creates a default Env for testing
  • Registers the IncrementContract
  • Creates a client to call its methods
  • Calls increment with increments of 1 and 10 and checks the results
  • Calls get_state and checks that it returns the expected State (count 11, last_incr 10)

So this tests the key functionality of our contract - incrementing by specified amounts and retrieving the current State.

Running Contract Tests

To ensure that the contract functions as intended, you can run the contract tests using the following command:

cargo test 
Enter fullscreen mode Exit fullscreen mode

If the tests are successful, you should see an output similar to:

running 1 test
test test::test ... ok
Enter fullscreen mode Exit fullscreen mode

Building The Contract

To build the contract, use the following command:

cargo build --target wasm32-unknown-unknown --release
Enter fullscreen mode Exit fullscreen mode

This should output a .wasm file in the ../target directory:

../target/wasm32-unknown-unknown/release/soroban_custom_types_contract.wasm
Enter fullscreen mode Exit fullscreen mode

Invoking The Contract

To invoke the increment function of the contract, use the following command with Soroban-CLI:

soroban contract invoke \
    --wasm ../target/wasm32-unknown-unknown/release/soroban_custom_types_contract.wasm \
    --id 1 \
    -- \
    increment \
    --incr 4
Enter fullscreen mode Exit fullscreen mode

You should see the following output:

4
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this third post of the series, we explained implementation custom types in soroban contracts. We hope this article has given you an idea of how Soroban custom types works. For more information about custom types, check here https://soroban.stellar.org/docs/learn/custom-types. Stay tuned for more post in this "Soroban Contracts 101" Series where we will dive deeper into Soroban Contracts and their functionalities.

Oldest comments (0)