diff --git a/pop-api/Cargo.toml b/pop-api/Cargo.toml index ae381d08..4caf8eaa 100644 --- a/pop-api/Cargo.toml +++ b/pop-api/Cargo.toml @@ -8,7 +8,7 @@ version = "0.0.0" [dependencies] ink = { version = "5.0.0", default-features = false } pop-primitives = { path = "../primitives", default-features = false } -sp-io = { version = "31.0.0", default-features = false, features = [ +sp-io = { version = "37.0.0", default-features = false, features = [ "disable_allocator", "disable_oom", "disable_panic_handler", diff --git a/pop-api/integration-tests/Cargo.toml b/pop-api/integration-tests/Cargo.toml index 5e0e4f9c..cad7b4c4 100644 --- a/pop-api/integration-tests/Cargo.toml +++ b/pop-api/integration-tests/Cargo.toml @@ -16,6 +16,9 @@ log = "0.4.22" pallet-assets = { version = "37.0.0", default-features = false } pallet-balances = { version = "37.0.0", default-features = false } pallet-contracts = { version = "35.0.0", default-features = false } +pop-api = { path = "../../pop-api", default-features = false, features = [ + "fungibles", +] } pop-primitives = { path = "../../primitives", default-features = false } pop-runtime-devnet = { path = "../../runtime/devnet", default-features = false } scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = [ @@ -24,7 +27,6 @@ scale = { package = "parity-scale-codec", version = "3.6.12", default-features = sp-io = { version = "37.0.0", default-features = false } sp-runtime = { version = "=38.0.0", default-features = false } - [features] default = [ "std" ] std = [ @@ -33,6 +35,7 @@ std = [ "pallet-assets/std", "pallet-balances/std", "pallet-contracts/std", + "pop-api/std", "pop-primitives/std", "pop-runtime-devnet/std", "scale/std", diff --git a/pop-api/integration-tests/build.rs b/pop-api/integration-tests/build.rs index 5b969a9f..21334ac2 100644 --- a/pop-api/integration-tests/build.rs +++ b/pop-api/integration-tests/build.rs @@ -1,13 +1,14 @@ -use contract_build::{ - execute, BuildArtifacts, BuildMode, BuildResult, ExecuteArgs, ManifestPath, OutputType, - Verbosity, -}; use std::{ fs, path::{Path, PathBuf}, process, }; +use contract_build::{ + execute, BuildArtifacts, BuildMode, BuildResult, ExecuteArgs, ManifestPath, OutputType, + Verbosity, +}; + fn main() { let contracts_dir = PathBuf::from("./contracts/"); let contract_dirs = match get_subcontract_directories(&contracts_dir) { diff --git a/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs b/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs index 93838f3c..3ca787be 100755 --- a/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs +++ b/pop-api/integration-tests/contracts/create_token_in_constructor/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] use pop_api::{ - fungibles::{self as api}, + fungibles::{self as api, events::Created}, primitives::TokenId, StatusCode, }; @@ -14,22 +14,23 @@ mod create_token_in_constructor { #[ink(storage)] pub struct Fungible { - id: TokenId, + token: TokenId, } impl Fungible { #[ink(constructor, payable)] pub fn new(id: TokenId, min_balance: Balance) -> Result { - let contract = Self { id }; + let contract = Self { token: id }; // AccountId of the contract which will be set to the owner of the fungible token. let owner = contract.env().account_id(); api::create(id, owner, min_balance)?; + contract.env().emit_event(Created { id, creator: owner, admin: owner }); Ok(contract) } #[ink(message)] pub fn token_exists(&self) -> Result { - api::token_exists(self.id) + api::token_exists(self.token) } } } diff --git a/pop-api/integration-tests/contracts/fungibles/lib.rs b/pop-api/integration-tests/contracts/fungibles/lib.rs index 6530968a..17dda2af 100755 --- a/pop-api/integration-tests/contracts/fungibles/lib.rs +++ b/pop-api/integration-tests/contracts/fungibles/lib.rs @@ -6,7 +6,10 @@ /// 4. PSP-22 Mintable & Burnable use ink::prelude::vec::Vec; use pop_api::{ - fungibles::{self as api}, + fungibles::{ + self as api, + events::{Approval, Created, DestroyStarted, MetadataCleared, MetadataSet, Transfer}, + }, primitives::TokenId, StatusCode, }; @@ -60,7 +63,13 @@ mod fungibles { #[ink(message)] pub fn transfer(&mut self, token: TokenId, to: AccountId, value: Balance) -> Result<()> { - api::transfer(token, to, value) + api::transfer(token, to, value)?; + self.env().emit_event(Transfer { + from: Some(self.env().account_id()), + to: Some(to), + value, + }); + Ok(()) } #[ink(message)] @@ -73,12 +82,22 @@ mod fungibles { // In the PSP-22 standard a `[u8]`, but the size needs to be known at compile time. _data: Vec, ) -> Result<()> { - api::transfer_from(token, from, to, value) + api::transfer_from(token, from, to, value)?; + self.env().emit_event(Transfer { from: Some(from), to: Some(to), value }); + Ok(()) } #[ink(message)] - pub fn approve(&mut self, token: TokenId, spender: AccountId, value: Balance) -> Result<()> { - api::approve(token, spender, value) + pub fn approve( + &mut self, + token: TokenId, + spender: AccountId, + value: Balance, + ) -> Result<()> { + api::approve(token, spender, value)?; + self.env() + .emit_event(Approval { owner: self.env().account_id(), spender, value }); + Ok(()) } #[ink(message)] @@ -135,12 +154,16 @@ mod fungibles { admin: AccountId, min_balance: Balance, ) -> Result<()> { - api::create(id, admin, min_balance) + api::create(id, admin, min_balance)?; + self.env().emit_event(Created { id, creator: admin, admin }); + Ok(()) } #[ink(message)] pub fn start_destroy(&mut self, token: TokenId) -> Result<()> { - api::start_destroy(token) + api::start_destroy(token)?; + self.env().emit_event(DestroyStarted { token }); + Ok(()) } #[ink(message)] @@ -151,12 +174,16 @@ mod fungibles { symbol: Vec, decimals: u8, ) -> Result<()> { - api::set_metadata(token, name, symbol, decimals) + api::set_metadata(token, name.clone(), symbol.clone(), decimals)?; + self.env().emit_event(MetadataSet { token, name, symbol, decimals }); + Ok(()) } #[ink(message)] - pub fn clear_metadata(&self, token: TokenId) -> Result<()> { - api::clear_metadata(token) + pub fn clear_metadata(&mut self, token: TokenId) -> Result<()> { + api::clear_metadata(token)?; + self.env().emit_event(MetadataCleared { token }); + Ok(()) } #[ink(message)] diff --git a/pop-api/integration-tests/src/fungibles/mod.rs b/pop-api/integration-tests/src/fungibles/mod.rs index 346e67c6..d2e06285 100644 --- a/pop-api/integration-tests/src/fungibles/mod.rs +++ b/pop-api/integration-tests/src/fungibles/mod.rs @@ -1,3 +1,6 @@ +use pop_api::fungibles::events::{ + Approval, Created, DestroyStarted, MetadataCleared, MetadataSet, Transfer, +}; use pop_primitives::{ArithmeticError::*, Error, Error::*, TokenError::*, TokenId}; use utils::*; @@ -98,6 +101,11 @@ fn transfer_works() { assert_ok!(transfer(&addr, token, BOB, amount / 2)); let balance_after_transfer = Assets::balance(token, &BOB); assert_eq!(balance_after_transfer, balance_before_transfer + amount / 2); + // Successfully emit event. + let from = account_id_from_slice(addr.as_ref()); + let to = account_id_from_slice(BOB.as_ref()); + let expected = Transfer { from: Some(from), to: Some(to), value: amount / 2 }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); // Transfer token to account that does not exist. assert_eq!(transfer(&addr, token, FERDIE, amount / 4), Err(Token(CannotCreate))); // Token is not live, i.e. frozen or being destroyed. @@ -150,6 +158,11 @@ fn transfer_from_works() { assert_ok!(transfer_from(&addr, token, ALICE, BOB, amount / 2)); let balance_after_transfer = Assets::balance(token, &BOB); assert_eq!(balance_after_transfer, balance_before_transfer + amount / 2); + // Successfully emit event. + let from = account_id_from_slice(ALICE.as_ref()); + let to = account_id_from_slice(BOB.as_ref()); + let expected = Transfer { from: Some(from), to: Some(to), value: amount / 2 }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); }); } @@ -170,13 +183,23 @@ fn approve_works() { assets::freeze(&ALICE, token); assert_eq!(approve(&addr, token, &BOB, amount), Err(Module { index: 52, error: [16, 0] })); assets::thaw(&ALICE, token); - // Successful approvals: + // Successful approvals. assert_eq!(0, Assets::allowance(token, &addr, &BOB)); assert_ok!(approve(&addr, token, &BOB, amount)); assert_eq!(Assets::allowance(token, &addr, &BOB), amount); + // Successfully emit event. + let owner = account_id_from_slice(addr.as_ref()); + let spender = account_id_from_slice(BOB.as_ref()); + let expected = Approval { owner, spender, value: amount }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); // Non-additive, sets new value. assert_ok!(approve(&addr, token, &BOB, amount / 2)); assert_eq!(Assets::allowance(token, &addr, &BOB), amount / 2); + // Successfully emit event. + let owner = account_id_from_slice(addr.as_ref()); + let spender = account_id_from_slice(BOB.as_ref()); + let expected = Approval { owner, spender, value: amount / 2 }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); // Token is not live, i.e. frozen or being destroyed. assets::start_destroy(&ALICE, token); assert_eq!(approve(&addr, token, &BOB, amount), Err(Module { index: 52, error: [16, 0] })); @@ -316,6 +339,10 @@ fn create_works() { // Create token successfully. assert_ok!(create(&addr, TOKEN_ID, &BOB, 1)); assert_eq!(Assets::owner(TOKEN_ID), Some(addr.clone())); + // Successfully emit event. + let admin = account_id_from_slice(BOB.as_ref()); + let expected = Created { id: TOKEN_ID, creator: admin, admin }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); // Token ID is already taken. assert_eq!(create(&addr, TOKEN_ID, &BOB, 1), Err(Module { index: 52, error: [5, 0] }),); }); @@ -335,9 +362,15 @@ fn instantiate_and_create_fungible_works() { ); // Successfully create a token when instantiating the contract. let result_with_address = instantiate_and_create_fungible(contract, TOKEN_ID, 1); - assert_ok!(result_with_address.clone()); - assert_eq!(Assets::owner(TOKEN_ID), result_with_address.ok()); + let instantiator = result_with_address.clone().ok(); + assert_ok!(result_with_address); + assert_eq!(&Assets::owner(TOKEN_ID), &instantiator); assert!(Assets::asset_exists(TOKEN_ID)); + // Successfully emit event. + let instantiator = account_id_from_slice(instantiator.unwrap().as_ref()); + let expected = + Created { id: TOKEN_ID, creator: instantiator.clone(), admin: instantiator }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); }); } @@ -354,6 +387,9 @@ fn start_destroy_works() { assert_eq!(start_destroy(&addr, token), Err(Module { index: 52, error: [2, 0] }),); let token = assets::create(&addr, TOKEN_ID, 1); assert_ok!(start_destroy(&addr, token)); + // Successfully emit event. + let expected = DestroyStarted { token: TOKEN_ID }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); }); } @@ -393,7 +429,10 @@ fn set_metadata_works() { Err(Module { index: 52, error: [9, 0] }), ); // Set metadata successfully. - assert_ok!(set_metadata(&addr, TOKEN_ID, name, symbol, decimals)); + assert_ok!(set_metadata(&addr, TOKEN_ID, name.clone(), symbol.clone(), decimals)); + // Successfully emit event. + let expected = MetadataSet { token: TOKEN_ID, name, symbol, decimals }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); // Token is not live, i.e. frozen or being destroyed. assets::start_destroy(&addr, token); assert_eq!( @@ -427,6 +466,9 @@ fn clear_metadata_works() { assets::set_metadata(&addr, token, name, symbol, decimals); // Clear metadata successfully. assert_ok!(clear_metadata(&addr, TOKEN_ID)); + // Successfully emit event. + let expected = MetadataCleared { token: TOKEN_ID }.encode(); + assert_eq!(last_contract_event(), expected.as_slice()); // Token is not live, i.e. frozen or being destroyed. assets::start_destroy(&addr, token); assert_eq!( diff --git a/pop-api/integration-tests/src/fungibles/utils.rs b/pop-api/integration-tests/src/fungibles/utils.rs index 9b1fa984..85fb3007 100644 --- a/pop-api/integration-tests/src/fungibles/utils.rs +++ b/pop-api/integration-tests/src/fungibles/utils.rs @@ -337,3 +337,25 @@ pub(super) fn instantiate_and_create_fungible( .unwrap_or_else(|_| panic!("Contract reverted: {:?}", result)) .map(|_| address) } + +/// Get the last event from pallet contracts. +pub(super) fn last_contract_event() -> Vec { + let events = System::read_events_for_pallet::>(); + let contract_events = events + .iter() + .filter_map(|event| match event { + pallet_contracts::Event::::ContractEmitted { data, .. } => + Some(data.as_slice()), + _ => None, + }) + .collect::>(); + contract_events.last().unwrap().to_vec() +} + +/// Decodes a byte slice into an `AccountId` as defined in `primitives`. +/// +/// This is used to resolve type mismatches between the `AccountId` in the integration tests and the +/// contract environment. +pub fn account_id_from_slice(s: &[u8; 32]) -> pop_api::primitives::AccountId { + pop_api::primitives::AccountId::decode(&mut &s[..]).expect("Should be decoded to AccountId") +} diff --git a/pop-api/src/primitives.rs b/pop-api/src/primitives.rs index a3d596a5..2fcb8a95 100644 --- a/pop-api/src/primitives.rs +++ b/pop-api/src/primitives.rs @@ -1,5 +1,6 @@ use ink::env::{DefaultEnvironment, Environment}; pub use pop_primitives::*; -pub(crate) type AccountId = ::AccountId; +// Public due to integration tests crate. +pub type AccountId = ::AccountId; pub(crate) type Balance = ::Balance; diff --git a/pop-api/src/v0/fungibles.rs b/pop-api/src/v0/fungibles.rs index ecd05b76..99c1261a 100644 --- a/pop-api/src/v0/fungibles.rs +++ b/pop-api/src/v0/fungibles.rs @@ -101,7 +101,7 @@ pub mod events { /// Event emitted when a token is created. #[ink::event] - pub struct Create { + pub struct Created { /// The token identifier. #[ink(topic)] pub id: TokenId, @@ -115,7 +115,7 @@ pub mod events { /// Event emitted when a token is in the process of being destroyed. #[ink::event] - pub struct StartDestroy { + pub struct DestroyStarted { /// The token. #[ink(topic)] pub token: TokenId, @@ -123,7 +123,7 @@ pub mod events { /// Event emitted when new metadata is set for a token. #[ink::event] - pub struct SetMetadata { + pub struct MetadataSet { /// The token. #[ink(topic)] pub token: TokenId, @@ -139,7 +139,7 @@ pub mod events { /// Event emitted when metadata is cleared for a token. #[ink::event] - pub struct ClearMetadata { + pub struct MetadataCleared { /// The token. #[ink(topic)] pub token: TokenId, diff --git a/pop-api/src/v0/mod.rs b/pop-api/src/v0/mod.rs index fdc0fdba..c6153892 100644 --- a/pop-api/src/v0/mod.rs +++ b/pop-api/src/v0/mod.rs @@ -1,5 +1,3 @@ -use ink::env::chain_extension::ChainExtensionMethod; - use crate::{ build_extension_method, constants::{DISPATCH, READ_STATE},