diff --git a/src/frame/contracts.rs b/src/frame/contracts.rs index 1941918dd9..0b3a1d72ed 100644 --- a/src/frame/contracts.rs +++ b/src/frame/contracts.rs @@ -91,6 +91,7 @@ pub struct CallCall<'a, T: Contracts> { /// Address of the contract. pub dest: &'a ::Address, /// Value to transfer to the contract. + #[codec(compact)] pub value: ::Balance, /// Gas limit. #[codec(compact)] @@ -115,6 +116,17 @@ pub struct InstantiatedEvent { pub contract: ::AccountId, } +/// Contract execution event. +/// +/// Emitted upon successful execution of a contract, if any contract events were produced. +#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)] +pub struct ContractExecutionEvent { + /// Caller of the contract. + pub caller: ::AccountId, + /// SCALE encoded contract event data. + pub data: Vec, +} + #[cfg(test)] #[cfg(feature = "integration-tests")] mod tests { @@ -122,38 +134,144 @@ mod tests { 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 { - 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, + signer: PairSigner, } - #[async_std::test] - #[cfg(feature = "integration-tests")] - async fn tx_put_code() { - env_logger::try_init().ok(); + impl TestContext { + async fn init() -> Self { + env_logger::try_init().ok(); + + let client = ClientBuilder::::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_add(1, Ordering::SeqCst); + + 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( + client: &Client, + stash: &mut PairSigner, + ) -> PairSigner { + 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) + } - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let client = ClientBuilder::::new() - .build() - .await - .unwrap(); + async fn put_code( + &self, + ) -> Result, Error> { + const CONTRACT: &str = r#" + (module + (func (export "call")) + (func (export "deploy")) + ) + "#; + let code = wabt::wat2wasm(CONTRACT).expect("invalid wabt"); - let code = contract_wasm(); - let result = client.put_code_and_watch(&signer, &code).await.unwrap(); - let code_stored = result.code_stored().unwrap(); + 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( + &self, + code_hash: &::Hash, + data: &[u8], + ) -> Result, 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: &::Address, + input_data: &[u8], + ) -> Result, 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] + async fn tx_put_code() { + 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 @@ -162,41 +280,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::::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] + 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) ); } }