Skip to content

Commit

Permalink
A0-1140: e2e tests for contracts (#697)
Browse files Browse the repository at this point in the history
* Fix invalid clap default

* Setup framework for testing the button

* Test early bird play

* Test marketplace contract

* Test all button game variants

* Run contract e2e tests on CI

* Bump aleph_client version

* Fix cache target list

* Fix clippy warnings

* Simplify scopes

* Add docs

* Use From/TryFrom instead of custom traits

* Allow minting only in dev

* Fix clippy
  • Loading branch information
obrok authored Nov 4, 2022
1 parent fb8c407 commit d9fe46d
Show file tree
Hide file tree
Showing 24 changed files with 946 additions and 165 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/contracts-e2e-tests-and-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ jobs:
target-key: e2e-contracts
cargo-key: e2e-contracts
cache-version: v3
cargo-targets: e2e-tests-contracts/target/
cargo-targets: |
e2e-tests/target/
contracts/access_control/target/
contracts/button/target/
contracts/game_token/target/
contracts/marketplace/target/
contracts/simple_dex/target/
contracts/ticket_token/target/
contracts/wrapped_azero/target/
- name: Install cargo-contract
run: |
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion aleph-client/Cargo.lock

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

2 changes: 1 addition & 1 deletion aleph-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "aleph_client"
version = "1.9.0"
version = "1.10.0"
edition = "2021"
license = "Apache 2.0"

Expand Down
73 changes: 73 additions & 0 deletions aleph-client/src/contract/convertible_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::ops::Deref;

use anyhow::{bail, Result};
use contract_transcode::Value;
use sp_core::crypto::Ss58Codec;

use crate::AccountId;

/// Temporary wrapper for converting from [Value] to primitive types.
///
/// ```
/// # #![feature(assert_matches)]
/// # #![feature(type_ascription)]
/// # use std::assert_matches::assert_matches;
/// # use anyhow::{anyhow, Result};
/// # use aleph_client::{AccountId, contract::ConvertibleValue};
/// use contract_transcode::Value;
///
/// assert_matches!(ConvertibleValue(Value::UInt(42)).try_into(), Ok(42));
/// assert_matches!(ConvertibleValue(Value::Bool(true)).try_into(), Ok(true));
/// assert_matches!(
/// ConvertibleValue(Value::Literal("5H8cjBBzCJrAvDn9LHZpzzJi2UKvEGC9VeVYzWX5TrwRyVCA".to_string())).
/// try_into(): Result<AccountId>,
/// Ok(_)
/// );
/// assert_matches!(
/// ConvertibleValue(Value::String("not a number".to_string())).try_into(): Result<u128>,
/// Err(_)
/// );
/// ```
#[derive(Debug, Clone)]
pub struct ConvertibleValue(pub Value);

impl Deref for ConvertibleValue {
type Target = Value;

fn deref(&self) -> &Value {
&self.0
}
}

impl TryFrom<ConvertibleValue> for bool {
type Error = anyhow::Error;

fn try_from(value: ConvertibleValue) -> Result<bool, Self::Error> {
match value.0 {
Value::Bool(value) => Ok(value),
_ => bail!("Expected {:?} to be a boolean", value.0),
}
}
}

impl TryFrom<ConvertibleValue> for u128 {
type Error = anyhow::Error;

fn try_from(value: ConvertibleValue) -> Result<u128, Self::Error> {
match value.0 {
Value::UInt(value) => Ok(value),
_ => bail!("Expected {:?} to be an integer", value.0),
}
}
}

impl TryFrom<ConvertibleValue> for AccountId {
type Error = anyhow::Error;

fn try_from(value: ConvertibleValue) -> Result<AccountId, Self::Error> {
match value.0 {
Value::Literal(value) => Ok(AccountId::from_ss58check(&value)?),
_ => bail!("Expected {:?} to be a string", value),
}
}
}
44 changes: 25 additions & 19 deletions aleph-client/src/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@
//!
//! ```no_run
//! # use anyhow::{Result, Context};
//! # use sp_core::crypto::AccountId32;
//! # use aleph_client::AccountId;
//! # use aleph_client::{Connection, SignedConnection};
//! # use aleph_client::contract::ContractInstance;
//! # use aleph_client::contract::util::to_u128;
//! #
//! #[derive(Debug)]
//! struct PSP22TokenInstance {
//! contract: ContractInstance,
//! }
//!
//! impl PSP22TokenInstance {
//! fn new(address: AccountId32, metadata_path: &Option<String>) -> Result<Self> {
//! fn new(address: AccountId, metadata_path: &Option<String>) -> Result<Self> {
//! let metadata_path = metadata_path
//! .as_ref()
//! .context("PSP22Token metadata not set.")?;
Expand All @@ -25,26 +24,26 @@
//! })
//! }
//!
//! fn transfer(&self, conn: &SignedConnection, to: AccountId32, amount: u128) -> Result<()> {
//! fn transfer(&self, conn: &SignedConnection, to: AccountId, amount: u128) -> Result<()> {
//! self.contract.contract_exec(
//! conn,
//! "PSP22::transfer",
//! vec![to.to_string().as_str(), amount.to_string().as_str(), "0x00"].as_slice(),
//! )
//! }
//!
//! fn balance_of(&self, conn: &Connection, account: AccountId32) -> Result<u128> {
//! to_u128(self.contract.contract_read(
//! fn balance_of(&self, conn: &Connection, account: AccountId) -> Result<u128> {
//! self.contract.contract_read(
//! conn,
//! "PSP22::balance_of",
//! &vec![account.to_string().as_str()],
//! )?)
//! )?.try_into()
//! }
//! }
//! ```
mod convertible_value;
pub mod event;
pub mod util;

use std::{
fmt::{Debug, Formatter},
Expand All @@ -54,36 +53,37 @@ use std::{
use ac_primitives::ExtrinsicParams;
use anyhow::{anyhow, Context, Result};
use contract_metadata::ContractMetadata;
use contract_transcode::{ContractMessageTranscoder, Value};
use contract_transcode::ContractMessageTranscoder;
pub use convertible_value::ConvertibleValue;
use ink_metadata::{InkProject, MetadataVersioned};
use serde_json::{from_reader, from_str, from_value, json};
use sp_core::{crypto::AccountId32, Pair};
use sp_core::Pair;
use substrate_api_client::{compose_extrinsic, GenericAddress, XtStatus};

use crate::{try_send_xt, AnyConnection, SignedConnection};
use crate::{try_send_xt, AccountId, AnyConnection, SignedConnection};

/// Represents a contract instantiated on the chain.
pub struct ContractInstance {
address: AccountId32,
address: AccountId,
ink_project: InkProject,
}

impl ContractInstance {
const MAX_READ_GAS: u64 = 500000000000u64;
const MAX_GAS: u64 = 10000000000u64;
const MAX_GAS: u64 = 100000000000u64;
const PAYABLE_VALUE: u64 = 0u64;
const STORAGE_FEE_LIMIT: Option<u128> = None;

/// Creates a new contract instance under `address` with metadata read from `metadata_path`.
pub fn new(address: AccountId32, metadata_path: &str) -> Result<Self> {
pub fn new(address: AccountId, metadata_path: &str) -> Result<Self> {
Ok(Self {
address,
ink_project: load_metadata(metadata_path)?,
})
}

/// The address of this contract instance.
pub fn address(&self) -> &AccountId32 {
pub fn address(&self) -> &AccountId {
&self.address
}

Expand All @@ -93,7 +93,11 @@ impl ContractInstance {
}

/// Reads the value of a read-only, 0-argument call via RPC.
pub fn contract_read0<C: AnyConnection>(&self, conn: &C, message: &str) -> Result<Value> {
pub fn contract_read0<C: AnyConnection>(
&self,
conn: &C,
message: &str,
) -> Result<ConvertibleValue> {
self.contract_read(conn, message, &[])
}

Expand All @@ -103,7 +107,7 @@ impl ContractInstance {
conn: &C,
message: &str,
args: &[&str],
) -> Result<Value> {
) -> Result<ConvertibleValue> {
let payload = self.encode(message, args)?;
let request = self.contract_read_request(&payload);
let response = conn
Expand Down Expand Up @@ -166,10 +170,12 @@ impl ContractInstance {
ContractMessageTranscoder::new(&self.ink_project).encode(message, args)
}

fn decode_response(&self, from: &str, contract_response: &str) -> Result<Value> {
fn decode_response(&self, from: &str, contract_response: &str) -> Result<ConvertibleValue> {
let contract_response = contract_response.trim_start_matches("0x");
let bytes = hex::decode(contract_response)?;
ContractMessageTranscoder::new(&self.ink_project).decode_return(from, &mut bytes.as_slice())
ContractMessageTranscoder::new(&self.ink_project)
.decode_return(from, &mut bytes.as_slice())
.map(ConvertibleValue)
}
}

Expand Down
48 changes: 0 additions & 48 deletions aleph-client/src/contract/util.rs

This file was deleted.

1 change: 1 addition & 0 deletions aleph-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ac_primitives::{PlainTipExtrinsicParamsBuilder, SubstrateDefaultSignedExtra}
pub use account::{get_free_balance, locks};
pub use balances::total_issuance;
use codec::{Decode, Encode};
pub use contract_transcode;
pub use debug::print_storages;
pub use elections::{
get_committee_seats, get_current_era_non_reserved_validators,
Expand Down
2 changes: 1 addition & 1 deletion bin/cliain/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct ContractOptions {
#[clap(long, default_value = "0")]
pub balance: u128,
/// The gas limit enforced when executing the constructor
#[clap(long, default_value = "1_000_000_000")]
#[clap(long, default_value = "1000000000")]
pub gas_limit: u64,
/// The maximum amount of balance that can be charged/reserved from the caller to pay for the storage consumed
#[clap(long)]
Expand Down
2 changes: 1 addition & 1 deletion contracts/button/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod errors;
use ink_lang as ink;

#[ink::contract]
mod button_game {
pub mod button_game {
use access_control::{roles::Role, traits::AccessControlled, ACCESS_CONTROL_PUBKEY};
use game_token::MINT_SELECTOR;
use ink_env::{
Expand Down
5 changes: 4 additions & 1 deletion contracts/env/dev
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export AUTHORITY_SEED=//Alice
export LIFETIME=20

# mint this many ticket tokens
export TICKET_BALANCE=100
export TICKET_BALANCE=100000000

# initial price of ticket on the marketplace
export INITIAL_PRICE=69

# use dev-only hacks, like allowing the authority to mint game tokens on demand
export ENV_NAME=dev
3 changes: 3 additions & 0 deletions contracts/env/fe-benjamin
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ export TICKET_BALANCE=100

# initial price of ticket on the marketplace
export INITIAL_PRICE=69

# serious environment with no hacks
ENV_NAME=prod
3 changes: 3 additions & 0 deletions contracts/scripts/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ function deploy_button_game {
cd "$CONTRACTS_PATH"/access_control

cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Owner('"$contract_address"')' --suri "$AUTHORITY_SEED" --skip-confirm
if [ "$ENV_NAME" = "dev" ]; then
cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$AUTHORITY" 'Minter('"$game_token"')' --suri "$AUTHORITY_SEED" --skip-confirm
fi
cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$contract_address" 'Admin('"$marketplace"')' --suri "$AUTHORITY_SEED" --skip-confirm
cargo contract call --url "$NODE" --contract "$ACCESS_CONTROL" --message grant_role --args "$contract_address" 'Minter('"$game_token"')' --suri "$AUTHORITY_SEED" --skip-confirm

Expand Down
Loading

0 comments on commit d9fe46d

Please sign in to comment.