App
We are going to write the application functionality.
This is what we will use in our tests to interact with the contract.
The aim of this code is to read state and create solutions.
Start by adding the imports you are going to need.
#![allow(unused)] fn main() { use anyhow::bail; use essential_rest_client::EssentialClient; use essential_types::{ solution::{Mutation, Solution, SolutionData}, PredicateAddress, Word, }; }
This is the main struct that allows us to interact with the essential-server
.
It contains the client
that let's us send requests to the server and the predicate
address for our counter contract.
#![allow(unused)] fn main() { pub struct App { client: EssentialClient, predicate: PredicateAddress, } }
Add an impl block.
#![allow(unused)] fn main() { impl App { } }
The COUNTER_KEY
is the key that points to the counter: int
storage.
#![allow(unused)] fn main() { impl App { pub const COUNTER_KEY: Word = 0; } }
Add a new
method so the App
can be created. \
This takes the address of the server and the contract.
#![allow(unused)] fn main() { impl App { // ... pub fn new(addr: String, predicate: PredicateAddress) -> anyhow::Result<Self> { let client = EssentialClient::new(addr)?; Ok(Self { client, predicate, }) } } }
Read storage
Read the current count from storage.
Using the essential-client
we make a query to the state at the address of the counter contract and the COUNTER_KEY
.
#![allow(unused)] fn main() { impl App { // ... pub async fn read_count(&self) -> anyhow::Result<Word> { let output = self .client .query_state(&self.predicate.contract, &vec![Self::COUNTER_KEY]) .await?; // ... } // ... } }
State can return a value of any size including empty.
Add this match
expression that maps:
- empty to
0
. - a single word to the count.
- anything else to an error.
Then return the count.
#![allow(unused)] fn main() { impl App { // ... pub async fn read_count(&self) -> anyhow::Result<Word> { let output = self .client .query_state(&self.predicate.contract, &vec![Self::COUNTER_KEY]) .await?; let count = match &output[..] { [] => 0, [count] => *count, _ => bail!("Expected one word, got: {:?}", output), }; Ok(count) } // ... } }
Create a solution
Add this function (outside the impl) that takes the predicate address and the count we are trying to set the state to.
The solution has a single SolutionData
that solves this predicate (other solutions may solve multiple predicates).
There's no decision variables
or transient data
so those are set to default.
Add in a single state Mutation
. The key is the COUNTER_KEY
and the value is the new count.
#![allow(unused)] fn main() { pub fn create_solution(predicate: PredicateAddress, new_count: Word) -> Solution { Solution { data: vec![SolutionData { predicate_to_solve: predicate, decision_variables: Default::default(), transient_data: Default::default(), state_mutations: vec![Mutation { key: vec![App::COUNTER_KEY], value: vec![new_count], }], }], } } }
Back in the impl App
add a method to create and submit a solution that will increment the count.
#![allow(unused)] fn main() { impl App { // ... pub async fn increment(&self) -> anyhow::Result<Word> { let new_count = self.read_count().await? + 1; let solution = create_solution(self.predicate.clone(), new_count); self.client.submit_solution(solution).await?; Ok(new_count) } // ... } }
Check your `lib.rs` matches this.
#![allow(unused)] fn main() { use anyhow::bail; use essential_rest_client::EssentialClient; use essential_types::{ solution::{Mutation, Solution, SolutionData}, PredicateAddress, Word, }; pub struct App { client: EssentialClient, predicate: PredicateAddress, } impl App { pub const COUNTER_KEY: Word = 0; pub fn new(addr: String, predicate: PredicateAddress) -> anyhow::Result<Self> { let client = EssentialClient::new(addr)?; Ok(Self { client, predicate, }) } pub async fn read_count(&self) -> anyhow::Result<Word> { let output = self .client .query_state(&self.predicate.contract, &vec![Self::COUNTER_KEY]) .await?; let count = match &output[..] { [] => 0, [count] => *count, _ => bail!("Expected one word, got: {:?}", output), }; Ok(count) } pub async fn increment(&self) -> anyhow::Result<Word> { let new_count = self.read_count().await? + 1; let solution = create_solution(self.predicate.clone(), new_count); self.client.submit_solution(solution).await?; Ok(new_count) } } pub fn create_solution(predicate: PredicateAddress, new_count: Word) -> Solution { Solution { data: vec![SolutionData { predicate_to_solve: predicate, decision_variables: Default::default(), transient_data: Default::default(), state_mutations: vec![Mutation { key: vec![App::COUNTER_KEY], value: vec![new_count], }], }], } } }