diff --git a/pop-api/examples/balance-transfer/Cargo.toml b/pop-api/examples/balance-transfer/Cargo.toml deleted file mode 100644 index 2a12e532..00000000 --- a/pop-api/examples/balance-transfer/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -authors = [ "[your_name] <[your_email]>" ] -edition = "2021" -name = "balance_transfer" -version = "0.1.0" - -[dependencies] -ink = { version = "5.0.0", default-features = false } -pop-api = { path = "../../../pop-api", default-features = false } -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] -ink_e2e = "5.0.0" - -[lib] -path = "lib.rs" - -[features] -default = [ "std" ] -e2e-tests = [ ] -ink-as-dependency = [ ] -std = [ - "ink/std", - "pop-api/std", - "scale-info/std", - "scale/std", -] diff --git a/pop-api/examples/balance-transfer/lib.rs b/pop-api/examples/balance-transfer/lib.rs deleted file mode 100644 index e75c15b9..00000000 --- a/pop-api/examples/balance-transfer/lib.rs +++ /dev/null @@ -1,135 +0,0 @@ -// DEPRECATED -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use pop_api::balances::*; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum ContractError { - BalancesError(Error), -} - -impl From for ContractError { - fn from(value: Error) -> Self { - ContractError::BalancesError(value) - } -} - -#[ink::contract] -mod balance_transfer { - use super::*; - - #[ink(storage)] - #[derive(Default)] - pub struct BalanceTransfer; - - impl BalanceTransfer { - #[ink(constructor, payable)] - pub fn new() -> Self { - ink::env::debug_println!("BalanceTransfer::new"); - Default::default() - } - - #[ink(message)] - pub fn transfer( - &mut self, - receiver: AccountId, - value: Balance, - ) -> Result<(), ContractError> { - ink::env::debug_println!( - "BalanceTransfer::transfer: \nreceiver: {:?}, \nvalue: {:?}", - receiver, - value - ); - - transfer_keep_alive(receiver, value)?; - - ink::env::debug_println!("BalanceTransfer::transfer end"); - Ok(()) - } - } - - #[cfg(all(test, feature = "e2e-tests"))] - mod e2e_tests { - use super::*; - use ink_e2e::{ChainBackend, ContractsBackend}; - - use ink::{ - env::{test::default_accounts, DefaultEnvironment}, - primitives::AccountId, - }; - - type E2EResult = Result>; - - /// The base number of indivisible units for balances on the - /// `substrate-contracts-node`. - const UNIT: Balance = 1_000_000_000_000; - - /// The contract will be given 1000 tokens during instantiation. - const CONTRACT_BALANCE: Balance = 1_000 * UNIT; - - /// The receiver will get enough funds to have the required existential deposit. - /// - /// If your chain has this threshold higher, increase the transfer value. - const TRANSFER_VALUE: Balance = 1 / 10 * UNIT; - - /// An amount that is below the existential deposit, so that a transfer to an - /// empty account fails. - /// - /// Must not be zero, because such an operation would be a successful no-op. - const INSUFFICIENT_TRANSFER_VALUE: Balance = 1; - - /// Positive case scenario: - /// - the call is valid - /// - the call execution succeeds - #[ink_e2e::test] - async fn transfer_with_call_runtime_works( - mut client: Client, - ) -> E2EResult<()> { - // given - let mut constructor = RuntimeCallerRef::new(); - let contract = client - .instantiate("call-runtime", &ink_e2e::alice(), &mut constructor) - .value(CONTRACT_BALANCE) - .submit() - .await - .expect("instantiate failed"); - let mut call_builder = contract.call_builder::(); - - let accounts = default_accounts::(); - - let receiver: AccountId = accounts.bob; - - let sender_balance_before = client - .free_balance(accounts.alice) - .await - .expect("Failed to get account balance"); - let receiver_balance_before = - client.free_balance(receiver).await.expect("Failed to get account balance"); - - // when - let transfer_message = call_builder.transfer(receiver, TRANSFER_VALUE); - - let call_res = client - .call(&ink_e2e::alice(), &transfer_message) - .submit() - .await - .expect("call failed"); - - assert!(call_res.return_value().is_ok()); - - // then - let sender_balance_after = client - .free_balance(accounts.alice) - .await - .expect("Failed to get account balance"); - let receiver_balance_after = - client.free_balance(receiver).await.expect("Failed to get account balance"); - - assert_eq!(contract_balance_before, contract_balance_after + TRANSFER_VALUE); - assert_eq!(receiver_balance_before, receiver_balance_after - TRANSFER_VALUE); - - Ok(()) - } - } -} diff --git a/pop-api/examples/fungibles/Cargo.toml b/pop-api/examples/fungibles/Cargo.toml index 0b79e1b2..4caf80a1 100644 --- a/pop-api/examples/fungibles/Cargo.toml +++ b/pop-api/examples/fungibles/Cargo.toml @@ -5,8 +5,27 @@ name = "fungibles" version = "0.1.0" [dependencies] -ink = { version = "5.0.0", default-features = false } -pop-api = { path = "../../../pop-api", default-features = false, features = [ "fungibles" ] } +ink = { version = "=5.0.0", default-features = false, features = [ "ink-debug" ] } +pop-api = { path = "../../../pop-api", default-features = false, features = [ + "fungibles", +] } +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] +env_logger = { version = "0.11.3" } +serde_json = "1.0.114" + +# Local +drink = { path = "../../../../pop-drink/drink" } +pop-sandbox = { path = "../../../pop-sandbox", default-features = false } + +# Substrate +frame-support = { version = "36.0.0", default-features = false } [lib] path = "lib.rs" @@ -18,4 +37,7 @@ ink-as-dependency = [ ] std = [ "ink/std", "pop-api/std", + "pop-sandbox/std", + "scale-info/std", + "scale/std", ] diff --git a/pop-api/examples/fungibles/lib.rs b/pop-api/examples/fungibles/lib.rs index 11eafe21..24a647e4 100644 --- a/pop-api/examples/fungibles/lib.rs +++ b/pop-api/examples/fungibles/lib.rs @@ -1,108 +1,254 @@ +// TODO: If `admin` in `create` is different than the contract address, `NoPermission` thrown for +// mint, burn +// TODO: `Fungibles::decrease_allowance()` saturating_sub the value, it should +// `checked_sub` and throw an error instead. Hence, we don't have to handle `InsufficientAllowance` +// on the contract side. +// TODO: `InsufficientBalance` case is not returned in the `burn` pallet api if the `value` exceeds +// the minted value. In `decrease_balance` method of `pallet-assets`, it is also `saturating_sub`. + #![cfg_attr(not(feature = "std"), no_std, no_main)] -use ink::prelude::vec::Vec; +use ink::prelude::{string::String, vec::Vec}; use pop_api::{ - fungibles::{self as api}, primitives::TokenId, - StatusCode, + v0::fungibles::{ + self as api, + events::{Approval, Created, Transfer}, + traits::{Psp22, Psp22Burnable, Psp22Metadata, Psp22Mintable}, + PSP22Error, + }, }; -pub type Result = core::result::Result; +#[cfg(test)] +mod tests; +#[cfg(test)] +mod utils; #[ink::contract] mod fungibles { use super::*; #[ink(storage)] - #[derive(Default)] - pub struct Fungibles; + pub struct Fungibles { + id: TokenId, + } impl Fungibles { + /// Instantiate the contract and wrap around an existing token. + /// + /// # Parameters + /// * - `token` - The token. #[ink(constructor, payable)] - pub fn new() -> Self { - Default::default() + pub fn existing(id: TokenId) -> Result { + // Make sure token exists. + if !api::token_exists(id).unwrap_or_default() { + return Err(PSP22Error::Custom(String::from("Unknown"))); + } + let contract = Self { id }; + Ok(contract) } - #[ink(message)] - pub fn total_supply(&self, token: TokenId) -> Result { - api::total_supply(token) + /// Instantiate the contract and create a new token. The token identifier will be stored + /// in contract's storage. + /// + /// # Parameters + /// * - `id` - The identifier of the token. + /// * - `admin` - The account that will administer the token. + /// * - `min_balance` - The minimum balance required for accounts holding this token. + #[ink(constructor, payable)] + pub fn new(id: TokenId, min_balance: Balance) -> Result { + let mut contract = Self { id }; + let contract_id = contract.env().account_id(); + api::create(id, contract_id, min_balance).map_err(PSP22Error::from)?; + contract + .env() + .emit_event(Created { id, creator: contract_id, admin: contract_id }); + Ok(contract) } + } + impl Psp22 for Fungibles { + /// Returns the total token supply. #[ink(message)] - pub fn balance_of(&self, token: TokenId, owner: AccountId) -> Result { - api::balance_of(token, owner) + fn total_supply(&self) -> Balance { + api::total_supply(self.id).unwrap_or_default() } + /// Returns the account balance for the specified `owner` #[ink(message)] - pub fn allowance( - &self, - token: TokenId, - owner: AccountId, - spender: AccountId, - ) -> Result { - api::allowance(token, owner, spender) + fn balance_of(&self, owner: AccountId) -> Balance { + api::balance_of(self.id, owner).unwrap_or_default() } + /// Returns the amount which `spender` is still allowed to withdraw from `owner` #[ink(message)] - pub fn transfer(&mut self, token: TokenId, to: AccountId, value: Balance) -> Result<()> { - api::transfer(token, to, value) + fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance { + api::allowance(self.id, owner, spender).unwrap_or_default() } + /// Transfers `value` amount of tokens from the caller's account to account `to` + /// with additional `data` in unspecified format. #[ink(message)] - pub fn transfer_from( + fn transfer( &mut self, - token: TokenId, - from: AccountId, to: AccountId, value: Balance, _data: Vec, - ) -> Result<()> { - api::transfer_from(token, from, to, value) + ) -> Result<(), PSP22Error> { + let caller = self.env().caller(); + // No-op if the caller and `to` is the same address or `value` is zero, returns success + // and no events are emitted. + if caller == to || value == 0 { + return Ok(()); + } + + api::transfer(self.id, to, value).map_err(PSP22Error::from)?; + self.env().emit_event(Transfer { from: Some(caller), to: Some(to), value }); + Ok(()) } + /// Transfers `value` tokens on the behalf of `from` to the account `to` + /// with additional `data` in unspecified format. #[ink(message)] - pub fn approve( + fn transfer_from( &mut self, - token: TokenId, - spender: AccountId, + from: AccountId, + to: AccountId, value: Balance, - ) -> Result<()> { - api::approve(token, spender, value) + _data: Vec, + ) -> Result<(), PSP22Error> { + let caller = self.env().caller(); + // No-op if `from` and `to` is the same address or `value` is zero, returns success and + // no events are emitted. + if from == to || value == 0 { + return Ok(()); + } + + // If `from` and the caller are different addresses, a successful transfer results + // in decreased allowance by `from` to the caller and an `Approval` event with + // the new allowance amount is emitted. + api::transfer_from(self.id, from, to, value).map_err(PSP22Error::from)?; + // Emit events. + self.env().emit_event(Transfer { from: Some(caller), to: Some(to), value }); + self.env().emit_event(Approval { + owner: from, + spender: caller, + value: self.allowance(from, caller), + }); + Ok(()) } + /// Allows `spender` to withdraw from the caller's account multiple times, up to + /// the total amount of `value`. #[ink(message)] - pub fn increase_allowance( + fn approve(&mut self, spender: AccountId, value: Balance) -> Result<(), PSP22Error> { + let caller = self.env().caller(); + // No-op if the caller and `spender` is the same address, returns success and no events + // are emitted. + if caller == spender { + return Ok(()); + } + + api::approve(self.id, spender, value).map_err(PSP22Error::from)?; + self.env().emit_event(Approval { owner: caller, spender, value }); + Ok(()) + } + + /// Increases by `value` the allowance granted to `spender` by the caller. + #[ink(message)] + fn increase_allowance( &mut self, - token: TokenId, spender: AccountId, value: Balance, - ) -> Result<()> { - api::increase_allowance(token, spender, value) + ) -> Result<(), PSP22Error> { + let caller = self.env().caller(); + // No-op if the caller and `spender` is the same address or `value` is zero, returns + // success and no events are emitted. + if caller == spender || value == 0 { + return Ok(()); + } + + api::increase_allowance(self.id, spender, value).map_err(PSP22Error::from)?; + let allowance = self.allowance(caller, spender); + self.env().emit_event(Approval { owner: caller, spender, value: allowance }); + Ok(()) } + /// Decreases by `value` the allowance granted to `spender` by the caller. #[ink(message)] - pub fn decrease_allowance( + fn decrease_allowance( &mut self, - token: TokenId, spender: AccountId, value: Balance, - ) -> Result<()> { - api::decrease_allowance(token, spender, value) + ) -> Result<(), PSP22Error> { + let caller = self.env().caller(); + // No-op if the caller and `spender` is the same address or `value` is zero, returns + // success and no events are emitted. + if caller == spender || value == 0 { + return Ok(()); + } + // Reverts with `InsufficientAllowance` if `spender` and the caller are different + // addresses and the `value` exceeds the allowance granted by the caller to + // `spender`. + let allowance = self.allowance(caller, spender); + if allowance < value { + return Err(PSP22Error::InsufficientAllowance); + } + + api::decrease_allowance(self.id, spender, value).map_err(PSP22Error::from)?; + let allowance = self.allowance(caller, spender); + self.env().emit_event(Approval { owner: caller, spender, value: allowance }); + Ok(()) + } + } + + impl Psp22Metadata for Fungibles { + /// Returns the token name. + #[ink(message)] + fn token_name(&self) -> Option { + api::token_name(self.id).ok().and_then(|v| String::from_utf8(v).ok()) } + /// Returns the token symbol. #[ink(message)] - pub fn token_name(&self, token: TokenId) -> Result> { - api::token_name(token) + fn token_symbol(&self) -> Option { + api::token_symbol(self.id).ok().and_then(|v| String::from_utf8(v).ok()) } + /// Returns the token decimals. #[ink(message)] - pub fn token_symbol(&self, token: TokenId) -> Result> { - api::token_symbol(token) + fn token_decimals(&self) -> u8 { + api::token_decimals(self.id).unwrap_or_default() } + } + + impl Psp22Mintable for Fungibles { + /// Mints `value` tokens to the senders account. + #[ink(message)] + fn mint(&mut self, account: AccountId, value: Balance) -> Result<(), PSP22Error> { + if value == 0 { + return Ok(()); + } + api::mint(self.id, account, value).map_err(PSP22Error::from)?; + self.env().emit_event(Transfer { from: None, to: Some(account), value }); + Ok(()) + } + } + impl Psp22Burnable for Fungibles { + /// Burns `value` tokens from the senders account. #[ink(message)] - pub fn token_decimals(&self, token: TokenId) -> Result { - api::token_decimals(token) + fn burn(&mut self, account: AccountId, value: Balance) -> Result<(), PSP22Error> { + if value == 0 { + return Ok(()); + } + let balance = self.balance_of(account); + if balance < value { + return Err(PSP22Error::InsufficientBalance); + } + api::burn(self.id, account, value).map_err(PSP22Error::from)?; + self.env().emit_event(Transfer { from: Some(account), to: None, value }); + Ok(()) } } } diff --git a/pop-api/examples/fungibles/tests.rs b/pop-api/examples/fungibles/tests.rs new file mode 100644 index 00000000..16d1907f --- /dev/null +++ b/pop-api/examples/fungibles/tests.rs @@ -0,0 +1,433 @@ +use drink::{ + sandbox_api::assets_api::AssetsAPI, + session::{error::SessionError, Session}, +}; +use frame_support::assert_ok; +use pop_api::{ + primitives::TokenId, + v0::fungibles::events::{Approval, Created, Transfer}, +}; +use pop_sandbox::{Balance, DevnetSandbox as Sandbox, ALICE, BOB}; +use scale::Encode; +use utils::*; + +use super::*; + +const TOKEN_ID: TokenId = 1; +const TOKEN_MIN_BALANCE: Balance = 10_000; + +#[drink::contract_bundle_provider] +enum BundleProvider {} + +#[drink::test(sandbox = Sandbox)] +fn new_constructor_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + // Token exists after the deployment. + assert!(session.sandbox().asset_exists(&TOKEN_ID)); + let expected = Created { + id: TOKEN_ID, + creator: account_id_from_slice(contract.as_ref()), + admin: account_id_from_slice(contract.as_ref()), + } + .encode(); + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); // Successfully emit event. + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn existing_constructor_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + + // Fails to deploy contract with a non-existing token ID. + assert!( + deploy_with_existing_constructor(&mut session, BundleProvider::local()?, TOKEN_ID).is_err() + ); + expect_deployment_reverted(&session, PSP22Error::Custom(String::from("Unknown"))); + + // Successfully deploy contract with an existing token ID. + let actor = session.get_actor(); + session.sandbox().create(&TOKEN_ID, &actor, TOKEN_MIN_BALANCE).unwrap(); + deploy_with_existing_constructor(&mut session, BundleProvider::local()?, TOKEN_ID)?; + + assert!(session.sandbox().asset_exists(&TOKEN_ID)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + assert_eq!(session.sandbox().total_supply(&TOKEN_ID), 0); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn balance_of_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + // Mint tokens. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &ALICE, AMOUNT)); + + // Successfully return a correct balance. + assert_eq!(balance_of(&mut session, ALICE), AMOUNT); + assert_eq!(balance_of(&mut session, ALICE), session.sandbox().balance_of(&TOKEN_ID, &ALICE)); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn mint_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + + // Successfully mint tokens. + assert_ok!(mint(&mut session, ALICE, 0)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + assert_eq!(session.sandbox().total_supply(&TOKEN_ID), 0); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &ALICE), 0); + + session.set_actor(contract.clone()); + // Mint tokens. + assert_ok!(mint(&mut session, ALICE, AMOUNT)); + let expected = + Transfer { from: None, to: Some(account_id_from_slice(ALICE.as_ref())), value: AMOUNT } + .encode(); + // A `Transfer` event is emitted. + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); + + assert_eq!(session.sandbox().total_supply(&TOKEN_ID), AMOUNT); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &ALICE), AMOUNT); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn burn_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + // Mint tokens. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &ALICE, AMOUNT)); + + // No-op. + assert_ok!(burn(&mut session, ALICE, 0)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // Failed with `InsufficientBalance`. + expect_call_reverted( + &mut session, + BURN, + vec![ALICE.to_string(), (AMOUNT * 2).to_string()], + PSP22Error::InsufficientBalance, + ); + + // Successfully burn tokens. + assert_ok!(burn(&mut session, ALICE, 1)); + let expected = + Transfer { from: Some(account_id_from_slice(ALICE.as_ref())), to: None, value: 1 }.encode(); + // A `Transfer` event is emitted. + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); + + assert_eq!(session.sandbox().total_supply(&TOKEN_ID), AMOUNT - 1); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &ALICE), AMOUNT - 1); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn transfer_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + const TRANSFERRED: Balance = TOKEN_MIN_BALANCE; + // Mint tokens. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &contract.clone(), AMOUNT)); + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &BOB, AMOUNT)); + // Check balance of accounts. + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &contract.clone()), AMOUNT); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &BOB), AMOUNT); + + session.set_actor(contract.clone()); + // No-op if `value` is zero. + assert_ok!(transfer(&mut session, ALICE, 0)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // No-op if the caller and `to` is the same address, returns success and no events are emitted. + assert_ok!(transfer(&mut session, contract.clone(), TRANSFERRED)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // Failed with `InsufficientBalance`. + let data = serde_json::to_string::<[u8; 0]>(&[]).unwrap(); + expect_call_reverted( + &mut session, + TRANSFER, + vec![BOB.to_string(), (AMOUNT + 1).to_string(), data], + PSP22Error::InsufficientBalance, + ); + // Make sure balance is not changed. + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &contract), AMOUNT); + + // Successfully transfer tokens from `contract` to `account`. + assert_ok!(transfer(&mut session, BOB, TRANSFERRED)); + let expected = Transfer { + from: Some(account_id_from_slice(contract.clone().as_ref())), + to: Some(account_id_from_slice(BOB.as_ref())), + value: TRANSFERRED, + } + .encode(); + // A `Transfer` event is emitted. + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); + + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &contract), AMOUNT - TRANSFERRED); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &BOB), AMOUNT + TRANSFERRED); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn transfer_from_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + // Mint tokens. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &ALICE, AMOUNT)); + // Approve `contract` to spend `ALICE` tokens. + assert_ok!(session.sandbox().approve(&TOKEN_ID, &ALICE, &contract.clone(), AMOUNT * 2)); + assert_eq!(session.sandbox().allowance(&TOKEN_ID, &ALICE, &contract.clone()), AMOUNT * 2); + // Check balance of accounts. + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &contract), 0); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &ALICE), AMOUNT); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &BOB), 0); + + session.set_actor(contract.clone()); + // Failed with `InsufficientAllowance`. + let data = serde_json::to_string::<[u8; 0]>(&[]).unwrap(); + expect_call_reverted( + &mut session, + TRANSFER_FROM, + vec![ALICE.to_string(), contract.clone().to_string(), (AMOUNT * 2 + 1).to_string(), data], + PSP22Error::InsufficientAllowance, + ); + // Make sure balances are unchaged. + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &contract), 0); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &ALICE), AMOUNT); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &BOB), 0); + + session.set_actor(contract.clone()); + // No-op if `value` is zero. + assert_ok!(transfer_from(&mut session, ALICE, BOB, 0)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // No-op if the `from` and `to` is the same address, returns success and no events are emitted. + assert_ok!(transfer_from(&mut session, ALICE, ALICE, AMOUNT / 2)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // Failed with `InsufficientBalance`. + let data = serde_json::to_string::<[u8; 0]>(&[]).unwrap(); + expect_call_reverted( + &mut session, + TRANSFER_FROM, + vec![ALICE.to_string(), contract.clone().to_string(), (AMOUNT + 1).to_string(), data], + PSP22Error::InsufficientBalance, + ); + // Make sure balances are unchaged. + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &contract), 0); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &ALICE), AMOUNT); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &BOB), 0); + + session.set_actor(contract.clone()); + // Successfully transfer from `owner`. + assert_ok!(transfer_from(&mut session, ALICE, BOB, AMOUNT / 2)); + // Successfully emit event. + let expected = Approval { + owner: account_id_from_slice(ALICE.as_ref()), + spender: account_id_from_slice(contract.clone().as_ref()), + value: AMOUNT + AMOUNT / 2, + } + .encode(); + // An `Approval` event is emitted. + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); + assert_eq!( + session.sandbox().allowance(&TOKEN_ID, &ALICE, &contract.clone()), + AMOUNT + AMOUNT / 2 + ); + // Check balance of accounts. + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &contract), 0); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &ALICE), AMOUNT / 2); + assert_eq!(session.sandbox().balance_of(&TOKEN_ID, &BOB), AMOUNT / 2); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn allowance_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + // Mint tokens and approve. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &contract.clone(), AMOUNT)); + assert_ok!(session.sandbox().approve(&TOKEN_ID, &contract.clone(), &ALICE, AMOUNT / 2)); + + // Successfully return a correct allowance. + assert_eq!(allowance(&mut session, contract, ALICE), AMOUNT / 2); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn approve_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + // Mint tokens. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &contract.clone(), AMOUNT)); + + session.set_actor(contract.clone()); + // No-op if the caller and `spender` is the same address, returns success and no events are + // emitted. + assert_ok!(approve(&mut session, contract.clone(), AMOUNT / 2)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // Successfully approve. + assert_ok!(approve(&mut session, ALICE, AMOUNT / 2)); + let expected = Approval { + owner: account_id_from_slice(contract.clone().as_ref()), + spender: account_id_from_slice(ALICE.as_ref()), + value: AMOUNT / 2, + } + .encode(); + // An `Approval` event is emitted. + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); + assert_eq!(session.sandbox().allowance(&TOKEN_ID, &contract, &ALICE), AMOUNT / 2); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn increase_allowance_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + // Mint tokens and approve. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &contract.clone(), AMOUNT)); + assert_ok!(session.sandbox().approve(&TOKEN_ID, &contract.clone(), &ALICE, AMOUNT / 2)); + + session.set_actor(contract.clone()); + // No-op if the caller and `spender` is the same address, returns success and no events are + // emitted. + assert_ok!(increase_allowance(&mut session, contract.clone(), AMOUNT / 2)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // Successfully increase allowance. + assert_ok!(increase_allowance(&mut session, ALICE, AMOUNT / 2)); + let expected = Approval { + owner: account_id_from_slice(contract.clone().as_ref()), + spender: account_id_from_slice(ALICE.as_ref()), + value: AMOUNT, + } + .encode(); + // An `Approval` event is emitted. + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); + assert_eq!(session.sandbox().allowance(&TOKEN_ID, &contract, &ALICE), AMOUNT); + Ok(()) +} + +#[drink::test(sandbox = Sandbox)] +fn decrease_allowance_works(mut session: Session) -> Result<(), Box> { + let _ = env_logger::try_init(); + // Deploy a new contract. + let contract = deploy_with_new_constructor( + &mut session, + BundleProvider::local()?, + TOKEN_ID, + TOKEN_MIN_BALANCE, + )?; + + const AMOUNT: Balance = TOKEN_MIN_BALANCE * 4; + // Mint tokens. + assert_ok!(session.sandbox().mint_into(&TOKEN_ID, &contract.clone(), AMOUNT)); + assert_ok!(session.sandbox().approve(&TOKEN_ID, &contract.clone(), &ALICE, AMOUNT / 2)); + + session.set_actor(contract.clone()); + // No-op if the caller and `spender` is the same address, returns success and no events are + // emitted. + assert_ok!(decrease_allowance(&mut session, contract.clone(), AMOUNT / 2)); + assert_eq!(last_contract_event(&session), None); // No event emitted. + + // Failed with `InsufficientAllowance`. + expect_call_reverted( + &mut session, + DECREASE_ALLOWANCE, + vec![ALICE.to_string(), AMOUNT.to_string()], + PSP22Error::InsufficientAllowance, + ); + // Make sure allowance is unchaged. + assert_eq!(session.sandbox().allowance(&TOKEN_ID, &contract, &ALICE), AMOUNT / 2); + + // Successfully decrease allowance. + assert_ok!(decrease_allowance(&mut session, ALICE, 1)); + // Successfully emit event. + let expected = Approval { + owner: account_id_from_slice(contract.clone().as_ref()), + spender: account_id_from_slice(ALICE.as_ref()), + value: AMOUNT / 2 - 1, + } + .encode(); + // An `Approval` event is emitted. + assert_eq!(last_contract_event(&session).unwrap(), expected.as_slice()); + assert_eq!(session.sandbox().allowance(&TOKEN_ID, &contract, &ALICE), AMOUNT / 2 - 1); + Ok(()) +} diff --git a/pop-api/examples/fungibles/utils.rs b/pop-api/examples/fungibles/utils.rs new file mode 100644 index 00000000..e3ee1281 --- /dev/null +++ b/pop-api/examples/fungibles/utils.rs @@ -0,0 +1,206 @@ +// A set of helper methods to test the contract calls. + +use drink::{ + session::{bundle::ContractBundle, error::SessionError, Session, NO_SALT}, + DispatchError, +}; +use pop_api::primitives::{AccountId, TokenId}; +use pop_sandbox::{AccountId32, Balance, DevnetSandbox as Sandbox, INIT_VALUE}; +use scale::{Decode, Encode}; + +use super::*; + +// PSP22 functions. +pub(super) const ALLOWANCE: &str = "Psp22::allowance"; +pub(super) const BALANCE_OF: &str = "Psp22::balance_of"; +pub(super) const TOTAL_SUPPLY: &str = "Psp22::total_supply"; +pub(super) const TRANSFER: &str = "Psp22::transfer"; +pub(super) const TRANSFER_FROM: &str = "Psp22::transfer_from"; +pub(super) const APPROVE: &str = "Psp22::approve"; +pub(super) const INCREASE_ALLOWANCE: &str = "Psp22::increase_allowance"; +pub(super) const DECREASE_ALLOWANCE: &str = "Psp22::decrease_allowance"; +// PSP22Metadata functions. +pub(super) const TOKEN_NAME: &str = "Psp22Metadata::token_name"; +pub(super) const TOKEN_SYMBOL: &str = "Psp22Metadata::token_symbol"; +pub(super) const TOKEN_DECIMALS: &str = "Psp22Metadata::token_decimals"; +// PSP22Mintable functions. +pub(super) const MINT: &str = "Psp22Mintable::mint"; +// PSP22Burnable functions. +pub(super) const BURN: &str = "Psp22Burnable::burn"; + +/// This is used to resolve type mismatches between the `AccountId` in the quasi testing environment +/// and the contract environment. +pub(super) fn account_id_from_slice(s: &[u8; 32]) -> AccountId { + AccountId::decode(&mut &s[..]).expect("Should be decoded to AccountId") +} + +/// Get the last event from pallet contracts. +pub(super) fn last_contract_event(session: &Session) -> Option> { + session.record().last_event_batch().contract_events().last().cloned() +} + +/// Execute a contract method and expect CallReverted error to be returned. +pub(super) fn expect_call_reverted( + session: &mut Session, + function: &str, + params: Vec, + err: PSP22Error, +) { + let call = session.call::(function, ¶ms, None); + match call { + Err(SessionError::CallReverted(error)) => { + assert_eq!(error[1..], Err::<(), PSP22Error>(err).encode()) + }, + _ => panic!("Expect call reverted"), + } +} + +/// Return the last deployment result data. +pub(super) fn expect_deployment_reverted(session: &Session, err: PSP22Error) { + let result = session.record().last_deploy_result().result.clone(); + let error = result.unwrap().result.data; + assert_eq!(error[1..], Err::<(), PSP22Error>(err).encode()) +} + +// Call a contract method and decode the returned data. +pub(super) fn decoded_call( + session: &mut Session, + func_name: &str, + input: Vec, + endowment: Option, +) -> Result> { + session.call(func_name, &input, endowment)??; + Ok(session.record().last_call_return_decoded::()??) +} + +// Check if the event emitted correctly. +pub(super) fn assert_event(session: &mut Session, event: Vec) { + let contract_events = session.record().last_event_batch().contract_events(); + let last_event = contract_events.last().unwrap().to_vec(); + assert_eq!(last_event, event.as_slice()); +} + +// Test methods for deployment with constructor function. + +pub(super) fn deploy_with_new_constructor( + session: &mut Session, + bundle: ContractBundle, + id: TokenId, + min_balance: Balance, +) -> Result { + session.deploy_bundle( + bundle, + "new", + &[id.to_string(), min_balance.to_string()], + NO_SALT, + Some(INIT_VALUE), + ) +} + +pub(super) fn deploy_with_existing_constructor( + session: &mut Session, + bundle: ContractBundle, + id: TokenId, +) -> Result { + session.deploy_bundle(bundle, "existing", &[id.to_string()], NO_SALT, Some(INIT_VALUE)) +} + +// Test methods for `PSP22`. + +pub(super) fn total_supply(session: &mut Session) -> Balance { + decoded_call::(session, TOTAL_SUPPLY, vec![], None).unwrap() +} + +pub(super) fn balance_of(session: &mut Session, owner: AccountId32) -> Balance { + decoded_call::(session, BALANCE_OF, vec![owner.to_string()], None).unwrap() +} + +pub(super) fn allowance( + session: &mut Session, + owner: AccountId32, + spender: AccountId32, +) -> Balance { + decoded_call::(session, ALLOWANCE, vec![owner.to_string(), spender.to_string()], None) + .unwrap() +} + +pub(super) fn transfer( + session: &mut Session, + to: AccountId32, + amount: Balance, +) -> Result<(), Box> { + let data = serde_json::to_string::<[u8; 0]>(&[]).unwrap(); + Ok(session.call(TRANSFER, &vec![to.to_string(), amount.to_string(), data], None)??) +} + +pub(super) fn transfer_from( + session: &mut Session, + from: AccountId32, + to: AccountId32, + amount: Balance, +) -> Result<(), Box> { + let data = serde_json::to_string::<[u8; 0]>(&[]).unwrap(); + Ok(session.call( + TRANSFER_FROM, + &vec![from.to_string(), to.to_string(), amount.to_string(), data], + None, + )??) +} + +pub(super) fn approve( + session: &mut Session, + spender: AccountId32, + value: Balance, +) -> Result<(), Box> { + Ok(session.call(APPROVE, &vec![spender.to_string(), value.to_string()], None)??) +} + +pub(super) fn increase_allowance( + session: &mut Session, + spender: AccountId32, + value: Balance, +) -> Result<(), Box> { + Ok(session.call(INCREASE_ALLOWANCE, &vec![spender.to_string(), value.to_string()], None)??) +} + +pub(super) fn decrease_allowance( + session: &mut Session, + spender: AccountId32, + value: Balance, +) -> Result<(), Box> { + Ok(session.call(DECREASE_ALLOWANCE, &vec![spender.to_string(), value.to_string()], None)??) +} + +// Test methods for `PSP22Metadata``. + +pub(super) fn token_name(session: &mut Session) -> String { + decoded_call::(session, TOKEN_NAME, vec![], None).unwrap() +} + +pub(super) fn token_symbol(session: &mut Session) -> String { + decoded_call::(session, TOKEN_SYMBOL, vec![], None).unwrap() +} + +pub(super) fn token_decimals(session: &mut Session) -> u8 { + decoded_call::(session, TOKEN_DECIMALS, vec![], None).unwrap() +} + +// Test methods for `PSP22Mintable``. + +pub(super) fn mint( + session: &mut Session, + account: AccountId32, + amount: Balance, +) -> Result<(), Box> { + Ok(session.call(MINT, &vec![account.to_string(), amount.to_string()], None)??) +} + +// Test methods for `PSP22MPsp22Burnablentable``. + +pub(super) fn burn( + session: &mut Session, + account: AccountId32, + amount: Balance, +) -> Result<(), Box> { + Ok(session.call(BURN, &vec![account.to_string(), amount.to_string()], None)??) +} diff --git a/pop-api/examples/nfts/Cargo.toml b/pop-api/examples/nfts/Cargo.toml deleted file mode 100644 index ef50b7ec..00000000 --- a/pop-api/examples/nfts/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -authors = [ "[your_name] <[your_email]>" ] -edition = "2021" -name = "nfts" -version = "0.1.0" - -[dependencies] -ink = { version = "5.0.0", default-features = false } -pop-api = { path = "../../../pop-api", default-features = false } -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = [ "derive" ] } -scale-info = { version = "2.6", default-features = false, features = [ "derive" ], optional = true } - -[lib] -path = "lib.rs" - -[features] -default = [ "std" ] -e2e-tests = [ ] -ink-as-dependency = [ ] -std = [ - "ink/std", - "pop-api/std", - "scale-info/std", - "scale/std", -] diff --git a/pop-api/examples/nfts/lib.rs b/pop-api/examples/nfts/lib.rs deleted file mode 100644 index 0cd0f313..00000000 --- a/pop-api/examples/nfts/lib.rs +++ /dev/null @@ -1,117 +0,0 @@ -// DEPRECATED -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -use pop_api::nfts::*; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum ContractError { - InvalidCollection, - ItemAlreadyExists, - NftsError(Error), - NotOwner, -} - -impl From for ContractError { - fn from(value: Error) -> Self { - ContractError::NftsError(value) - } -} - -#[ink::contract] -mod nfts { - use super::*; - - #[ink(storage)] - #[derive(Default)] - pub struct Nfts; - - impl Nfts { - #[ink(constructor, payable)] - pub fn new() -> Self { - ink::env::debug_println!("Nfts::new"); - Default::default() - } - - #[ink(message)] - pub fn create_nft_collection(&self) -> Result<(), ContractError> { - ink::env::debug_println!("Nfts::create_nft_collection: collection creation started."); - let admin = Self::env().caller(); - let item_settings = ItemSettings(BitFlags::from(ItemSetting::Transferable)); - - let mint_settings = MintSettings { - mint_type: MintType::Issuer, - price: Some(0), - start_block: Some(0), - end_block: Some(0), - default_item_settings: item_settings, - }; - - let config = CollectionConfig { - settings: CollectionSettings(BitFlags::from(CollectionSetting::TransferableItems)), - max_supply: None, - mint_settings, - }; - pop_api::nfts::create(admin, config)?; - ink::env::debug_println!( - "Nfts::create_nft_collection: collection created successfully." - ); - Ok(()) - } - - #[ink(message)] - pub fn mint_nft( - &mut self, - collection_id: u32, - item_id: u32, - receiver: AccountId, - ) -> Result<(), ContractError> { - ink::env::debug_println!( - "Nfts::mint: collection_id: {:?} item_id {:?} receiver: {:?}", - collection_id, - item_id, - receiver - ); - - // Check if item already exists (demo purposes only, unnecessary as would expect check in mint call) - if item(collection_id, item_id)?.is_some() { - return Err(ContractError::ItemAlreadyExists); - } - - // mint api - mint(collection_id, item_id, receiver)?; - ink::env::debug_println!("Nfts::mint: item minted successfully"); - - // check owner - match owner(collection_id, item_id)? { - Some(owner) if owner == receiver => { - ink::env::debug_println!("Nfts::mint success: minted item belongs to receiver"); - }, - _ => { - return Err(ContractError::NotOwner); - }, - } - - ink::env::debug_println!("Nfts::mint end"); - Ok(()) - } - - #[ink(message)] - pub fn read_collection(&self, collection_id: u32) -> Result<(), ContractError> { - ink::env::debug_println!("Nfts::read_collection: collection_id: {:?}", collection_id); - let collection = pop_api::nfts::collection(collection_id)?; - ink::env::debug_println!("Nfts::read_collection: collection: {:?}", collection); - Ok(()) - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - Nfts::new(); - } - } -} diff --git a/pop-api/examples/place-spot-order/Cargo.toml b/pop-api/examples/place-spot-order/Cargo.toml deleted file mode 100644 index f523bea7..00000000 --- a/pop-api/examples/place-spot-order/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -authors = [ "[your_name] <[your_email]>" ] -edition = "2021" -name = "spot_order" -version = "0.1.0" - -[dependencies] -ink = { version = "5.0.0", default-features = false } -pop-api = { path = "../../../pop-api", default-features = false } -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = [ "derive" ] } -scale-info = { version = "2.6", default-features = false, features = [ "derive" ], optional = true } - -[lib] -path = "lib.rs" - -[features] -default = [ "std" ] -e2e-tests = [ ] -ink-as-dependency = [ ] -std = [ - "ink/std", - "pop-api/std", - "scale-info/std", - "scale/std", -] diff --git a/pop-api/examples/place-spot-order/lib.rs b/pop-api/examples/place-spot-order/lib.rs deleted file mode 100644 index 965917d1..00000000 --- a/pop-api/examples/place-spot-order/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ -// DEPRECATED -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod spot_order { - - #[ink(storage)] - #[derive(Default)] - pub struct SpotOrder; - - impl SpotOrder { - #[ink(constructor, payable)] - pub fn new() -> Self { - ink::env::debug_println!("SpotOrder::new"); - Default::default() - } - - #[ink(message)] - pub fn place_spot_order(&mut self, max_amount: Balance, para_id: u32) { - ink::env::debug_println!( - "SpotOrder::place_spot_order: max_amount {:?} para_id: {:?} ", - max_amount, - para_id, - ); - - #[allow(unused_variables)] - let res = pop_api::cross_chain::coretime::place_spot_order(max_amount, para_id); - ink::env::debug_println!("SpotOrder::place_spot_order: res {:?} ", res,); - - ink::env::debug_println!("SpotOrder::place_spot_order end"); - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[ink::test] - fn default_works() { - SpotOrder::new(); - } - } -} diff --git a/pop-api/examples/read-runtime-state/Cargo.toml b/pop-api/examples/read-runtime-state/Cargo.toml deleted file mode 100644 index f5464730..00000000 --- a/pop-api/examples/read-runtime-state/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -authors = [ "[your_name] <[your_email]>" ] -edition = "2021" -name = "read_relay_blocknumber" -version = "0.1.0" - -[dependencies] -ink = { version = "5.0.0", default-features = false } -pop-api = { path = "../../../pop-api", default-features = false } -scale = { package = "parity-scale-codec", version = "3", default-features = false, features = [ "derive" ] } -scale-info = { version = "2.6", default-features = false, features = [ "derive" ], optional = true } - -[lib] -path = "lib.rs" - -[features] -default = [ "std" ] -e2e-tests = [ ] -ink-as-dependency = [ ] -std = [ - "ink/std", - "pop-api/std", - "scale-info/std", - "scale/std", -] diff --git a/pop-api/examples/read-runtime-state/lib.rs b/pop-api/examples/read-runtime-state/lib.rs deleted file mode 100644 index 092e9f2f..00000000 --- a/pop-api/examples/read-runtime-state/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -// DEPRECATED -#![cfg_attr(not(feature = "std"), no_std, no_main)] - -#[ink::contract] -mod read_relay_blocknumber { - use pop_api::primitives::storage_keys::{ - ParachainSystemKeys::LastRelayChainBlockNumber, RuntimeStateKeys::ParachainSystem, - }; - - #[ink(event)] - pub struct RelayBlockNumberRead { - value: BlockNumber, - } - - #[ink(storage)] - #[derive(Default)] - pub struct ReadRelayBlockNumber; - - impl ReadRelayBlockNumber { - #[ink(constructor, payable)] - pub fn new() -> Self { - ink::env::debug_println!("ReadRelayBlockNumber::new"); - Default::default() - } - - #[ink(message)] - pub fn read_relay_block_number(&self) { - let result = - pop_api::state::read::(ParachainSystem(LastRelayChainBlockNumber)); - ink::env::debug_println!("Last relay block number read by contract: {:?}", result); - self.env().emit_event(RelayBlockNumberRead { - value: result.expect("Failed to read relay block number."), - }); - } - } -} diff --git a/pop-api/src/v0/fungibles/errors.rs b/pop-api/src/v0/fungibles/errors.rs index a5c548a6..12bc596c 100644 --- a/pop-api/src/v0/fungibles/errors.rs +++ b/pop-api/src/v0/fungibles/errors.rs @@ -1,4 +1,5 @@ -//! A set of errors for use in smart contracts that interact with the fungibles api. This includes errors compliant to standards. +//! A set of errors for use in smart contracts that interact with the fungibles api. This includes +//! errors compliant to standards. use super::*; use ink::prelude::string::{String, ToString}; @@ -114,8 +115,10 @@ mod tests { }, StatusCode, }; - use ink::prelude::string::String; - use ink::scale::{Decode, Encode}; + use ink::{ + prelude::string::String, + scale::{Decode, Encode}, + }; fn error_into_status_code(error: Error) -> StatusCode { let mut encoded_error = error.encode();