Skip to content

tags: - pchain-sdk - tutorial - Smart Contract


Chapter 5

In the previous chapters, we have understood how we write contract methods and access storage in the world state. In this chapter, we are going to demonstrate the functionality of collections provided by pchain-sdk.

pchain-sdk provides several collection structures that are designed for gas efficiency:

  • Cacher: allows lazy initialization
  • Vector: lazily stores a list of items
  • FastMap: lazily stores items into a key-value map
  • IterableMap: lazily stores items into an iterable key-value map

We create the MyCollections struct which includes all these collection structures.

lib.rs

use pchain_sdk::{
    contract, contract_methods, call, Cacher, collections::{Vector, FastMap, IterableMap}
};

type Address = [u8; 32];

#[contract]
pub struct MyCollections {
    lazy_cat: Cacher<String>,
    pretty_numbers: Vector<i32>,
    address_resolver: FastMap<Address, String>,
    prices: IterableMap<String, u32>
}

Cacher

Cacher is a data wrapper to support Lazy Read and Lazy Write to Contract Storage.

#[contract_methods]
impl MyCollections {

    /// Here we use the receiver `&self` without loading data before executing this method. 
    #[call]
    fn meow(&self) -> String {
        // Actual loading happens here. 
        // Dereference as immutable and invoke a function as &String
        self.lazy_cat.to_uppercase()
    }

    /// Here we use the receiver `&mut self` without loading data before executing this method. 
    #[call]
    fn feed(&mut self, data: String) {
        // Dereference as mutable and then assign a value to it
        self.lazy_cat.set(data);
        // Actual saving happens after this method
    }
}

Vector

/// Here we use the receiver `&self` without loading data before executing this method. 
#[call]
fn pick(&self, index: usize) -> Option<i32> {
    // Actual loading happens here. 
    // Dereference as immutable and call functions from iterator
    self.pretty_numbers.iter().nth(index).map(|value| *value)
}

/// Here we use the receiver `&mut self` without loading data before executing this method. 
#[call]
fn push(&mut self, num: i32) -> usize {
    // Dereference as mutable and then set a value to it
    self.pretty_numbers.push(&num);
    self.pretty_numbers.len()
    // Actual saving happens after this method
}

FastMap

/// Here we use the receiver `&self` without loading data before executing this method. 
#[call]
fn resolve(&self, address: Address) -> Option<String> {
    // Actual loading happens here. 
    // Dereference as immutable and call functions from key-value map
    self.address_resolver.get(&address)
}

/// Here we use the receiver `&mut self` without loading data before executing this method. 
#[call]
fn add_record(&mut self, address: Address, name: String) {
    // Dereference as mutable and then insert a value
    self.address_resolver.insert(&address, name)
    // Actual saving happens after this method
}

IterableMap

/// Here we use the receiver `&self` without loading data before executing this method. 
#[call]
fn price(&self, item: String) -> Option<u32> {
    // Actual loading happens here. 
    // Dereference as immutable and call functions from key-value map
    self.prices.get(&item)
}

/// Here we use the receiver `&mut self` without loading data before executing this method. 
#[call]
fn set_price(&mut self, item: String, price: u32) {
    // Dereference as mutable and then insert a value
    self.prices.insert(&item, price);
    // Actual saving happens after this method
}

/// This method the iterable-map to calculate the sum of the values by iterating each item.
#[call]
fn total_price(&self) -> u32 {
    self.prices.values().sum()
}