Skip to content

Latest commit

 

History

History
132 lines (98 loc) · 5.22 KB

tutorial.md

File metadata and controls

132 lines (98 loc) · 5.22 KB

Odra Counter Tutorial

This guide will demonstrate the process of creating a counter smart contract using Odra. Initially, it will cover the conventional approach of utilizing casper_contract, followed by an explanation of how the Odra framework simplifies this process.

Start by opening the Counter v1 example on the casper-ecosystem github profile. This smart contract has three entrypoints: call, counter_inc, and counter_get. Let's look at the first two of these and what they do. As you may know, the call entrypoint is called once when the contract is deployed, and can never be invoked again. In this case, call starts by initializing the count, which can be incremented by counter_inc:

// Initialize the count to 0, locally.
let count_start = storage::new_uref(0_i32);

It then creates a Named Keys object, and adds the count to it:

// In the named keys of the contract, add a key for the count.
let mut counter_named_keys = NamedKeys::new();
let key_name = String::from(COUNT_KEY);
counter_named_keys.insert(key_name, count_start.into());

As mentioned above, the Counter contract has another two entrypoints, counter_inc and counter_get, but for these to be callable, they need to be exposed, which is done next in the call function:

let mut counter_entry_points = EntryPoints::new();

counter_entry_points.add_entry_point(EntryPoint::new(
	ENTRY_POINT_COUNTER_GET,
	Vec::new(),
	CLType::I32,
	EntryPointAccess::Public,
	EntryPointType::Contract,
));

counter_entry_points.add_entry_point(EntryPoint::new(
	ENTRY_POINT_COUNTER_INC,
	Vec::new(),
	CLType::Unit,
	EntryPointAccess::Public,
	EntryPointType::Contract,
));

Finally, a new contract is officially created in global state, and the contract hash is stored in the deploying account's named keys:

// Create a new contract package that can be upgraded.
let (stored_contract_hash, contract_version) = storage::new_contract(
	counter_entry_points,
	Some(counter_named_keys),
	Some(CONTRACT_PACKAGE_NAME.to_string()),
	Some(CONTRACT_ACCESS_UREF.to_string()),
);

// Store the contract version in the context's named keys.
let version_uref = storage::new_uref(contract_version);
runtime::put_key(CONTRACT_VERSION_KEY, version_uref.into());

// Create a named key for the contract hash.
runtime::put_key(CONTRACT_KEY, stored_contract_hash.into());

Now let's look at counter_inc, which is responsible for incrementing the counter. All it does is get the current count from the COUNT_KEY named key, converts it to a URef, and adds one to it.

#[no_mangle]
pub extern "C" fn counter_inc() {
    let uref: URef = runtime::get_key(COUNT_KEY)
        .unwrap_or_revert_with(ApiError::MissingKey)
        .into_uref()
        .unwrap_or_revert_with(ApiError::UnexpectedKeyVariant);
    storage::add(uref, 1); // Increment the count by 1.
}

This smart contract has a very limited functionality of incrementing a single arbitrary integer, yet it still requires close to one hundred lines of code to implement. Let's look at how the Odra framework can be used to make the Counter implementation much simpler.

Create a new Odra project by opening a terminal and running:

cargo odra new --name counter -t blank

Note: The -t blank flag will create the contract as a blank template.

Opening counter/src/lib.rs will display a blank file. [// n otherwise blank file with the #![no_std] macro on the first line.]

You can use the Odra Variable module to easily store and read global state values. This will be the only import of the file:

use odra::Variable;

All smart contracts are modules in Odra, so we need to define a new module Counter, listing all the fields in our contract:

#[odra::module]
pub struct Counter {
    count: Variable<u32>,
}

This acts like an interface, and allows our Counter contract to be implemented in other contracts.

Now you can implement the Counter module. Do so by using the Rust impl keyword and denoting the implementation as a #[odra::module] to ensure entrypoints are automatically generated for your functions:

#[odra::module]
impl Counter {
  
}

The first function to be implemented is the constructor, which like the call function in a traditional Casper contract, is only ever called once upon deployment. Constructors in Odra are marked by the #[odra(init)] macro. In this function, set the initial value of the count variable to 0:

#[odra(init)]
pub fn init(&mut self) {
	self.count.set(0);
}

All that's left is the increment function, which will be named counter_inc for consistency. This function will contain a single line, that will get the current count, and set it to that number, plus one:

pub fn counter_inc(&mut self) {
	self.count.set(self.count.get_or_default() + 1);
}

The get_or_default method is used here to avoid having to unwrap an Option value. You could instead use the get method if you prefer to perform the Option unwrapping.

Congratulations! You've just written your first smart contract with the Odra framework.