Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call a contract #165

Merged
merged 18 commits into from
Sep 24, 2020
Merged
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 161 additions & 50 deletions src/frame/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub struct CallCall<'a, T: Contracts> {
/// Address of the contract.
pub dest: &'a <T as System>::Address,
/// Value to transfer to the contract.
#[codec(compact)]
pub value: <T as Balances>::Balance,
/// Gas limit.
#[codec(compact)]
Expand All @@ -115,44 +116,164 @@ pub struct InstantiatedEvent<T: Contracts> {
pub contract: <T as System>::AccountId,
}

/// Contract execution event.
///
/// Raised upon successful executionFailing of a contract call
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Raised upon successful executionFailing of a contract call
/// Emitted upon successful execution/failure of a contract call

#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)]
pub struct ContractExecutionEvent<T: Contracts> {
/// Caller of the contract.
pub caller: <T as System>::AccountId,
/// Raw contract event data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this SCALE encoded data that is runtime dependent or can we say anything at all about the content?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The data is the encoded contract event, I will update the comment

pub data: Vec<u8>,
}

#[cfg(test)]
mod tests {
use sp_keyring::AccountKeyring;

use super::*;
use crate::{
balances::*,
system::*,
Client,
ClientBuilder,
ContractsTemplateRuntime,
Error,
ExtrinsicSuccess,
PairSigner,
Signer,
};
use sp_core::{
crypto::AccountId32,
sr25519::Pair,
};
use std::sync::atomic::{
AtomicU32,
Ordering,
};

fn contract_wasm() -> Vec<u8> {
const CONTRACT: &str = r#"
(module
(func (export "call"))
(func (export "deploy"))
)
"#;
wabt::wat2wasm(CONTRACT).expect("invalid wabt")
static STASH_NONCE: std::sync::atomic::AtomicU32 = AtomicU32::new(0);

struct TestContext {
client: Client<ContractsTemplateRuntime>,
signer: PairSigner<ContractsTemplateRuntime, Pair>,
}

impl TestContext {
async fn init() -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is useful enough that it might be worth making it generic over the runtime and put it in a test-utils.rs for use in other places.

env_logger::try_init().ok();

let client = ClientBuilder::<ContractsTemplateRuntime>::new()
.build()
.await
.expect("Error creating client");
let mut stash = PairSigner::new(AccountKeyring::Alice.pair());
let nonce = client
.account(&stash.account_id(), None)
.await
.unwrap()
.nonce;
let local_nonce = STASH_NONCE
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x + 1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not fetch_add()?

.unwrap();

stash.set_nonce(nonce + local_nonce);

let signer = Self::generate_account(&client, &mut stash).await;

TestContext { client, signer }
}

/// generate a new keypair for an account, and fund it so it can perform smart contract operations
async fn generate_account(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: consider making this available to other code.

client: &Client<ContractsTemplateRuntime>,
stash: &mut PairSigner<ContractsTemplateRuntime, Pair>,
) -> PairSigner<ContractsTemplateRuntime, Pair> {
use sp_core::Pair as _;
let new_account = Pair::generate().0;
let new_account_id: AccountId32 = new_account.public().into();
// fund the account
let endowment = 200_000_000_000_000;
let _ = client
.transfer_and_watch(stash, &new_account_id, endowment)
.await
.expect("New account balance transfer failed");
stash.increment_nonce();
PairSigner::new(new_account)
}

async fn put_code(
&self,
) -> Result<CodeStoredEvent<ContractsTemplateRuntime>, Error> {
const CONTRACT: &str = r#"
(module
(func (export "call"))
(func (export "deploy"))
)
"#;
let code = wabt::wat2wasm(CONTRACT).expect("invalid wabt");

let result = self.client.put_code_and_watch(&self.signer, &code).await?;
let code_stored = result.code_stored()?.ok_or_else(|| {
Error::Other("Failed to find a CodeStored event".into())
})?;
log::info!("Code hash: {:?}", code_stored.code_hash);
Ok(code_stored)
}

async fn instantiate(
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
&self,
code_hash: &<ContractsTemplateRuntime as System>::Hash,
data: &[u8],
) -> Result<InstantiatedEvent<ContractsTemplateRuntime>, Error> {
// call instantiate extrinsic
let result = self
.client
.instantiate_and_watch(
&self.signer,
100_000_000_000_000, // endowment
500_000_000, // gas_limit
code_hash,
data,
)
.await?;

log::info!("Instantiate result: {:?}", result);
let instantiated = result.instantiated()?.ok_or_else(|| {
Error::Other("Failed to find a Instantiated event".into())
})?;

Ok(instantiated)
}

async fn call(
&self,
contract: &<ContractsTemplateRuntime as System>::Address,
input_data: &[u8],
) -> Result<ExtrinsicSuccess<ContractsTemplateRuntime>, Error> {
let result = self
.client
.call_and_watch(
&self.signer,
contract,
0, // value
500_000_000, // gas_limit
input_data,
)
.await?;
log::info!("Call result: {:?}", result);
Ok(result)
}
}

#[async_std::test]
#[cfg(feature = "integration-tests")]
async fn tx_put_code() {
env_logger::try_init().ok();

let signer = PairSigner::new(AccountKeyring::Alice.pair());
let client = ClientBuilder::<ContractsTemplateRuntime>::new()
.build()
.await
.unwrap();

let code = contract_wasm();
let result = client.put_code_and_watch(&signer, &code).await.unwrap();
let code_stored = result.code_stored().unwrap();
let ctx = TestContext::init().await;
let code_stored = ctx.put_code().await;

assert!(
code_stored.is_some(),
code_stored.is_ok(),
format!(
"Error calling put_code and receiving CodeStored Event: {:?}",
code_stored
Expand All @@ -163,39 +284,29 @@ mod tests {
#[async_std::test]
#[cfg(feature = "integration-tests")]
async fn tx_instantiate() {
env_logger::try_init().ok();
let signer = PairSigner::new(AccountKeyring::Bob.pair());
let client = ClientBuilder::<ContractsTemplateRuntime>::new()
.build()
.await
.unwrap();

// call put_code extrinsic
let code = contract_wasm();
let result = client.put_code_and_watch(&signer, &code).await.unwrap();
let code_stored = result.code_stored().unwrap();
let code_hash = code_stored.unwrap().code_hash;

log::info!("Code hash: {:?}", code_hash);

// call instantiate extrinsic
let result = client
.instantiate_and_watch(
&signer,
100_000_000_000_000, // endowment
500_000_000, // gas_limit
&code_hash,
&[], // data
)
.await
.unwrap();
let ctx = TestContext::init().await;
let code_stored = ctx.put_code().await.unwrap();

let instantiated = ctx.instantiate(&code_stored.code_hash, &[]).await;

assert!(
instantiated.is_ok(),
format!("Error instantiating contract: {:?}", instantiated)
);
}

#[async_std::test]
#[cfg(feature = "integration-tests")]
async fn tx_call() {
let ctx = TestContext::init().await;
let code_stored = ctx.put_code().await.unwrap();

log::info!("Instantiate result: {:?}", result);
let event = result.instantiated().unwrap();
let instantiated = ctx.instantiate(&code_stored.code_hash, &[]).await.unwrap();
let executed = ctx.call(&instantiated.contract, &[]).await;

assert!(
event.is_some(),
format!("Error instantiating contract: {:?}", result)
executed.is_ok(),
format!("Error calling contract: {:?}", executed)
);
}
}