Skip to content

Chapter 2

In Chapter 2, we will implement a smart contract called MyLittlePony, to demonstrate how a contract can:

  • define entrypoint methods
  • define fields as data in contract storage

From the last chapter, we have learned that the macro contract on struct allows loading or storing fields from or into the world state. The key to be stored is an u8 integer ordered by the index of the fields.

In this chapter, we first create a struct, MyLittlePony, that consists of name, age, and gender. In this case, name has key [0] while age has key [1].

lib.rs: define MyLittlePony

use pchain_sdk::{
    contract, contract_methods, call, contract_field
};

#[contract]
pub struct MyLittlePony {
    name: String,
    age: u32,
    gender: Gender,
}

Next, we need to declare the Gender struct, which is the type of the gender field. To use the nested struct in the contract struct, we need the contract_field macro to access the key-value pair in canonical format. For instance, name in Gender struct has a key [2][0] in the contract MyLittlePony.

lib.rs: define Gender

#[contract_field]
struct Gender {
    name: String,
    description: String
}

After getting all the structs ready, we should start implementing the contract methods. This smart contract should provide three functionalities:

  • self_introduction()
  • grow_up()
  • change_pony()

Firstly, self_introduction() uses receiver &self to load all data before executing this method. All data will be loaded to the receiver self from the world state. Therefore, we can have access to all the fields in the contract, including the fields in the Gender struct.

lib.rs: load data with &self

#[contract_methods]
impl MyLittlePony {

    #[call]
    fn self_introduction(&self) -> String {
        format!("Hi, I am {}. Age of {}. I am {} that means {}.",
            self.name, self.age, self.gender.name, self.gender.description)
    }
}

In the next method, we are going to illustrate how we can use contract getter and setter to obtain/store data from or to the world state. Write cost is smaller compared to what we did in self_introduction() because there is only one key-value pair in the world state to be mutated.

Instead of passing &self as an argument, simply do Self::get_<field_name>() to obtain value and Self::set_<field_name>() to store updated value.

lib.rs: getter and setter

#[call]
fn grow_up() {
    let age = Self::get_age();
    Self::set_age(age+1)
}

Lastly, we want to change our little pony, so we use a mutable receiver &mut self to load data before executing this method, then store all data after execution. However, we should be cautious when using a mutable receiver as it is expensive to load and store since it mutates all key-value pairs in the world state.

lib.rs: load data with a mutable receiver

#[call]
fn change_pony(&mut self, name: String, age: u32, gender_name: String, description: String) {
    pchain_sdk::log(
        "update_gender".to_string().as_bytes(), 
        format!("update name:{} description: {}", name, description).as_bytes());
    self.name = name;
    self.age = age;
    self.gender.name = gender_name;
    self.gender.description = description;
}

Now you should have learned how to get and set contract fields in your smart contract.