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],
            }],
        }],
    }
}
}