Skip to content

Commit

Permalink
Session
Browse files Browse the repository at this point in the history
  • Loading branch information
pmikolajczyk41 committed Jun 28, 2023
1 parent 835bd56 commit 71b5e12
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions drink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ repository.workspace = true
version.workspace = true

[dependencies]
contract-transcode = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-balances = { workspace = true }
Expand All @@ -17,3 +18,6 @@ pallet-timestamp = { workspace = true }
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
thiserror = { workspace = true }

[features]
session = ["contract-transcode"]
2 changes: 2 additions & 0 deletions drink/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ pub mod chain_api;
pub mod contract_api;
mod error;
mod runtime;
#[cfg(feature = "session")]
pub mod session;

use std::time::SystemTime;

Expand Down
141 changes: 141 additions & 0 deletions drink/src/session.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
pub use contract_transcode;
use contract_transcode::{ContractMessageTranscoder, Value};
use frame_support::{dispatch::DispatchError, sp_runtime::AccountId32, weights::Weight};
use pallet_contracts_primitives::{ContractExecResult, ContractInstantiateResult};
use thiserror::Error;

use crate::{
contract_api::{ContractApi, GAS_LIMIT},
Sandbox, ALICE,
};

#[derive(Error, Debug)]
pub enum SessionError {
#[error("Encoding call data failed: {0}")]
Encoding(String),
#[error("Decoding call data failed: {0}")]
Decoding(String),
#[error("{0:?}")]
Drink(#[from] crate::Error),
#[error("Contract deployment has been reverted")]
DeploymentReverted,
#[error("Contract deployment failed before execution: {0:?}")]
DeploymentFailed(DispatchError),
#[error("Contract call has been reverted")]
CallReverted,
#[error("Contract call failed before execution: {0:?}")]
CallFailed(DispatchError),
#[error("No deployed contract")]
NoContract,
}

pub struct Session {
sandbox: Sandbox,

#[allow(unused)]
actor: AccountId32,
#[allow(unused)]
gas_limit: Weight,

transcoder: ContractMessageTranscoder,

deploy_results: Vec<ContractInstantiateResult<AccountId32, u128>>,
deploy_returns: Vec<AccountId32>,
call_results: Vec<ContractExecResult<u128>>,
call_returns: Vec<Value>,
}

impl Session {
pub fn new(transcoder: ContractMessageTranscoder) -> Result<Self, SessionError> {
Ok(Self {
sandbox: Sandbox::new().map_err(SessionError::Drink)?,
actor: ALICE,
gas_limit: GAS_LIMIT,
transcoder,
deploy_results: vec![],
deploy_returns: vec![],
call_results: vec![],
call_returns: vec![],
})
}

pub fn with_actor(self, actor: AccountId32) -> Self {
Self { actor, ..self }
}

pub fn with_gas_limit(self, gas_limit: Weight) -> Self {
Self { gas_limit, ..self }
}

pub fn with_transcoder(self, transcoder: ContractMessageTranscoder) -> Self {
Self { transcoder, ..self }
}

pub fn deploy(
mut self,
contract_bytes: Vec<u8>,
constructor: &str,
args: &[&str],
salt: Vec<u8>,
) -> Result<Self, SessionError> {
let data = self
.transcoder
.encode(constructor, args)
.map_err(|err| SessionError::Encoding(err.to_string()))?;

let result = self.sandbox.deploy_contract(contract_bytes, data, salt);

match &result.result {
Ok(exec_result) if exec_result.result.did_revert() => {
Err(SessionError::DeploymentReverted)
}
Ok(exec_result) => {
self.deploy_returns.push(exec_result.account_id.clone());
self.deploy_results.push(result);
Ok(self)
}
Err(err) => Err(SessionError::DeploymentFailed(*err)),
}
}

pub fn call(mut self, message: &str, args: &[&str]) -> Result<Self, SessionError> {
let data = self
.transcoder
.encode(message, args)
.map_err(|err| SessionError::Encoding(err.to_string()))?;

let address = self.last_deploy_return().ok_or(SessionError::NoContract)?;
let result = self.sandbox.call_contract(address.clone(), data);

match &result.result {
Ok(exec_result) if exec_result.did_revert() => Err(SessionError::CallReverted),
Ok(exec_result) => {
let decoded = self
.transcoder
.decode_return(message, &mut exec_result.data.as_slice())
.map_err(|err| SessionError::Decoding(err.to_string()))?;

self.call_returns.push(decoded);
self.call_results.push(result);
Ok(self)
}
Err(err) => Err(SessionError::CallFailed(*err)),
}
}

pub fn last_deploy_result(&self) -> Option<&ContractInstantiateResult<AccountId32, u128>> {
self.deploy_results.last()
}

pub fn last_deploy_return(&self) -> Option<AccountId32> {
self.deploy_returns.last().cloned()
}

pub fn last_call_result(&self) -> Option<&ContractExecResult<u128>> {
self.call_results.last()
}

pub fn last_call_return(&self) -> Option<Value> {
self.call_returns.last().cloned()
}
}
3 changes: 3 additions & 0 deletions examples/flipper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ ink = { version = "4.2.0", default-features = false, features = ["ink-debug"] }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true }

[dev-dependencies]
drink = { path = "../../drink", features = ["session"] }

[lib]
path = "lib.rs"

Expand Down
52 changes: 52 additions & 0 deletions examples/flipper/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,55 @@ mod flipper {
}
}
}

#[cfg(test)]
mod tests {
use std::{error::Error, fs, path::PathBuf};

use drink::session::{
contract_transcode::{ContractMessageTranscoder, Tuple, Value},
Session,
};

fn transcoder() -> ContractMessageTranscoder {
ContractMessageTranscoder::load(PathBuf::from("./target/ink/flipper.json"))
.expect("Failed to create transcoder")
}

fn bytes() -> Vec<u8> {
fs::read("./target/ink/flipper.wasm").expect("Failed to find or read contract file")
}

fn ok(v: Value) -> Value {
Value::Tuple(Tuple::new(Some("Ok"), vec![v]))
}

#[test]
fn initialization() -> Result<(), Box<dyn Error>> {
let init_value = Session::new(transcoder())?
.deploy(bytes(), "new", &["true"], vec![])?
.call("get", &[])?
.last_call_return()
.expect("Call was successful");

assert_eq!(init_value, ok(Value::Bool(true)));

Ok(())
}

#[test]
fn flipping() -> Result<(), Box<dyn Error>> {
let init_value = Session::new(transcoder())?
.deploy(bytes(), "new", &["true"], vec![])?
.call("flip", &[])?
.call("flip", &[])?
.call("flip", &[])?
.call("get", &[])?
.last_call_return()
.expect("Call was successful");

assert_eq!(init_value, ok(Value::Bool(false)));

Ok(())
}
}

0 comments on commit 71b5e12

Please sign in to comment.