diff --git a/Cargo.toml b/Cargo.toml index 3eb8697f..112896e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,6 +97,7 @@ sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0 frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } +sp-keystore = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39", default-features = false } # (native) sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.39" } diff --git a/chain-extensions/pallet-assets/Cargo.toml b/chain-extensions/pallet-assets/Cargo.toml index 94319eef..6456d8f6 100644 --- a/chain-extensions/pallet-assets/Cargo.toml +++ b/chain-extensions/pallet-assets/Cargo.toml @@ -23,6 +23,13 @@ sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } +[dev-dependencies] +env_logger = "0.9" +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +sp-io = { workspace = true } +sp-keystore = { workspace = true } + [features] default = ["std"] std = [ @@ -38,4 +45,5 @@ std = [ "sp-runtime/std", "pallet-assets/std", "assets-chain-extension-types/std", + "pallet-balances/std", ] diff --git a/chain-extensions/pallet-assets/src/lib.rs b/chain-extensions/pallet-assets/src/lib.rs index f4ff4695..7f15a272 100644 --- a/chain-extensions/pallet-assets/src/lib.rs +++ b/chain-extensions/pallet-assets/src/lib.rs @@ -18,6 +18,10 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; +#[cfg(test)] +pub mod tests; pub mod weights; use assets_chain_extension_types::{select_origin, Origin, Outcome}; diff --git a/chain-extensions/pallet-assets/src/mock.rs b/chain-extensions/pallet-assets/src/mock.rs new file mode 100644 index 00000000..2bdf6ac1 --- /dev/null +++ b/chain-extensions/pallet-assets/src/mock.rs @@ -0,0 +1,241 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::weights::SubstrateWeight; +use crate::{weights, AssetsExtension}; +use frame_support::traits::{AsEnsureOriginWithArg, ConstU128, Currency, Randomness}; +use frame_support::{ + parameter_types, sp_io, + traits::{ConstU32, ConstU64, Nothing}, + weights::Weight, +}; +use pallet_assets::AssetsCallback; +use pallet_contracts::chain_extension::RegisteredChainExtension; +use pallet_contracts::{Config, DefaultAddressGenerator, Frame}; +use parity_scale_codec::Encode; +use sp_core::crypto::AccountId32; +use sp_io::storage; +use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_runtime::generic; +use sp_runtime::testing::H256; +use sp_runtime::traits::{BlakeTwo256, Convert, IdentityLookup, Zero}; +use std::sync::Arc; + +pub type BlockNumber = u32; +pub type Balance = u128; +pub type AssetId = u128; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + Weight::from_ref_time(2_000_000_000_000).set_proof_size(u64::MAX), + ); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u32; + type BlockNumber = BlockNumber; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = generic::Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const DeletionWeightLimit: Weight = Weight::from_ref_time(500_000_000_000); + pub static UnstableInterface: bool = true; + pub Schedule: pallet_contracts::Schedule = Default::default(); + pub static DepositPerByte: BalanceOf = 1; + pub const DepositPerItem: BalanceOf = 1; +} + +pub struct DummyDeprecatedRandomness; +impl Randomness for DummyDeprecatedRandomness { + fn random(_: &[u8]) -> (H256, BlockNumber) { + (Default::default(), Zero::zero()) + } +} + +impl pallet_contracts::Config for Test { + type Time = Timestamp; + type Randomness = DummyDeprecatedRandomness; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type CallFilter = Nothing; + type CallStack = [Frame; 5]; + type WeightPrice = Self; + type WeightInfo = (); + type ChainExtension = AssetsExtension>; + type DeletionQueueDepth = ConstU32<128>; + type DeletionWeightLimit = DeletionWeightLimit; + type Schedule = Schedule; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type AddressGenerator = DefaultAddressGenerator; + type MaxCodeLen = ConstU32<{ 123 * 1024 }>; + type MaxStorageKeyLen = ConstU32<128>; + type UnsafeUnstableInterface = UnstableInterface; + type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; +} + +impl RegisteredChainExtension for AssetsExtension { + const ID: u16 = 02; +} + +parameter_types! { + pub static ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1>; + type WeightInfo = (); +} + +pub struct AssetsCallbackHandle; +impl AssetsCallback for AssetsCallbackHandle { + fn created(_id: &AssetId, _owner: &AccountId32) { + storage::set(b"asset_created", &().encode()); + } + + fn destroyed(_id: &AssetId) { + storage::set(b"asset_destroyed", &().encode()); + } +} + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetId; + type AssetIdParameter = u128; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type WeightInfo = (); + type CallbackHandle = AssetsCallbackHandle; + type Extra = (); + type RemoveItemsLimit = ConstU32<5>; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Assets: pallet_assets::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, + } +); + +pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); +pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); +pub const GAS_LIMIT: Weight = Weight::from_ref_time(100_000_000_000).set_proof_size(700000u64); +pub const ONE: u128 = 1_000_000_000_000_000_000; + +impl Convert> for Test { + fn convert(w: Weight) -> BalanceOf { + w.ref_time().into() + } +} + +pub struct ExtBuilder { + existential_deposit: u64, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: ExistentialDeposit::get(), + } + } +} + +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + } + pub fn build(self) -> sp_io::TestExternalities { + use env_logger::{Builder, Env}; + let env = Env::new().default_filter_or("runtime=debug"); + let _ = Builder::from_env(env).is_test(true).try_init(); + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + pallet_balances::GenesisConfig:: { balances: vec![] } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} diff --git a/chain-extensions/pallet-assets/src/tests.rs b/chain-extensions/pallet-assets/src/tests.rs new file mode 100644 index 00000000..255e64e2 --- /dev/null +++ b/chain-extensions/pallet-assets/src/tests.rs @@ -0,0 +1,499 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::mock::*; +use frame_support::assert_ok; +use frame_support::traits::fungibles::roles::Inspect; +use frame_support::traits::Currency; +use pallet_contracts::Determinism; +use pallet_contracts_primitives::{Code, ExecReturnValue, ReturnFlags}; +use parity_scale_codec::Encode; +use sp_core::crypto::AccountId32; +use sp_runtime::DispatchError; +use std::fs; + +// Those tests use the contract scheduler_example avilable here: +// https://github.com/AstarNetwork/chain-extension-contracts/blob/main/examples/assets +// It maps chain extension functions to ink! callable messages +// ex: +// #[ink(message)] +// pub fn burn(&mut self, asset_id: u128, who: AccountId, amount: Balance) -> Result<(), AssetsError> { +// AssetsExtension::burn(Origin::Caller, asset_id, who, amount)?; +// Ok(()) +// } + +#[test] +fn create_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Act - Create asset + assert_ok!(create(addr.clone(), 1, 1)); + + // Assert - Contract is the owner + assert_eq!(Assets::owner(1), Some(addr.into())); + }); +} + +#[test] +fn mint_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - create asset + assert_ok!(create(addr.clone(), 1, 1)); + + // Act - Mint 1000 assets to Alice + assert_ok!(mint(addr.clone(), 1, ALICE, 1000)); + + // Assert - Alice balance is 1000 + assert_eq!(Assets::balance(1, ALICE), 1000); + }); +} + +#[test] +fn burn_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - Create & mint 1000 to Alice + assert_ok!(create(addr.clone(), 1, 1)); + assert_ok!(mint(addr.clone(), 1, ALICE, 1000)); + + // Act - Burn 1000 of Alice tokens + assert_ok!(burn(addr.clone(), 1, ALICE, 1000)); + + // Assert - Balance of Alice is then 0 + assert_eq!(Assets::balance(1, ALICE), 0); + }); +} + +#[test] +fn transfer_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Assert - Create & mint 1000 to contract + assert_ok!(create(addr.clone(), 1, 1)); + assert_ok!(mint(addr.clone(), 1, addr.clone(), 1000)); + + // Act - Tranfer 1000 from contract to Alice + assert_ok!(transfer(addr.clone(), 1, ALICE, 1000)); + + // Assert - Alice balance is 1000 and contract is zero + assert_eq!(Assets::balance(1, ALICE), 1000); + assert_eq!(Assets::balance(1, addr.clone()), 0); + }); +} + +#[test] +fn balance_of_and_total_supply() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - create & mint 1000 to Alice + assert_ok!(create(addr.clone(), 1, 1)); + assert_ok!(mint(addr.clone(), 1, ALICE, 1000)); + + // Assert - Balance and total supply is 1000 + assert_eq!( + balance_of(addr.clone(), 1, ALICE).data[1..], + 1000u128.encode() + ); + assert_eq!(total_supply(addr.clone(), 1).data[1..], 1000u128.encode()); + }); +} + +#[test] +fn approve_transfer_and_check_allowance() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - Create and mint 1000 to contract + assert_ok!(create(addr.clone(), 1, 1)); + assert_ok!(mint(addr.clone(), 1, addr.clone(), 1000)); + + // Act - approve transfer To BOB for 100 + assert_ok!(approve_transfer(addr.clone(), 1, BOB, 100)); + + // Assert - Bob has 100 allowance + assert_eq!( + allowance(addr.clone(), 1, addr.clone(), BOB).data[1..], + 100u128.encode() + ); + }); +} + +#[test] +fn approve_transfer_and_transfer_balance() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange + // As transfer_approved() can only be called on behalf of the contract + // Bob creates & mint token to himself + // and approve the contract to spend his assets + assert_ok!(Assets::create(RuntimeOrigin::signed(BOB), 1, BOB, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(BOB), 1, BOB, 1000)); + assert_ok!(Assets::approve_transfer( + RuntimeOrigin::signed(BOB), + 1, + addr.clone(), + 100 + )); + + // Act - The contract transfer 100 from Alice to Bob + assert_ok!(transfer_approved(addr.clone(), 1, BOB, ALICE, 100)); + + // Assert - Bob has 900 and Alice 100 + assert_eq!(balance_of(addr.clone(), 1, BOB).data[1..], 900u128.encode()); + assert_eq!( + balance_of(addr.clone(), 1, ALICE).data[1..], + 100u128.encode() + ); + }); +} + +#[test] +fn cancel_approval_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - Create and mint 1000 to contract + // and approve Bob to spend 100 + assert_ok!(create(addr.clone(), 1, 1)); + assert_ok!(mint(addr.clone(), 1, addr.clone(), 1000)); + assert_ok!(approve_transfer(addr.clone(), 1, BOB, 100)); + + // Act - cancel approval + assert_ok!(cancel_approval(addr.clone(), 1, BOB)); + + // Assert - Bob allowance is 0 + assert_eq!( + allowance(addr.clone(), 1, addr.clone(), BOB).data[1..], + 0u128.encode() + ); + }); +} + +#[test] +fn set_metadata_and_checks() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - create + assert_ok!(create(addr.clone(), 1, 1)); + + // Act - set metadata + assert_ok!(set_metadata( + addr.clone(), + 1, + "Name".as_bytes().to_vec(), + "SYMB".as_bytes().to_vec(), + 18 + )); + + // Assert - metadata Name, Symbol & decimal is correct + assert_eq!(metadata_name(addr.clone(), 1).data[1..], "Name".encode()); + assert_eq!(metadata_symbol(addr.clone(), 1).data[1..], "SYMB".encode()); + assert_eq!(metadata_decimals(addr.clone(), 1).data[1..], 18u8.encode()); + }); +} + +#[test] +fn transfer_ownership_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - create token - owner is contract + assert_ok!(create(addr.clone(), 1, 1)); + assert_eq!(Assets::owner(1), Some(addr.clone())); + + // Act - transfer ownership to Alice + assert_ok!(transfer_ownership(addr.clone(), 1, ALICE)); + + // Assert - Alice is the owner + assert_eq!(Assets::owner(1), Some(ALICE)); + }); +} + +#[test] +fn cannot_make_tx_on_behalf_of_caller() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Assert + // When calling chan extensio with Orgin::Caller + // it reverts + assert_eq!( + create_caller(addr.clone(), 1, 1).unwrap(), + ExecReturnValue { + flags: ReturnFlags::REVERT, + data: [0, 1, 98].into() + } + ); + }); +} + +fn instantiate() -> AccountId32 { + let code = fs::read("./test-contract/asset_wrapper.wasm").expect("could not read .wasm file"); + let _ = Balances::deposit_creating(&ALICE, ONE * 1000); + let _ = Balances::deposit_creating(&BOB, ONE * 1000); + let instance_selector: Vec = [0x9b, 0xae, 0x9d, 0x5e].to_vec(); + Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(code), + instance_selector, + vec![], + false, + ) + .result + .unwrap() + .account_id +} + +fn create( + addr: AccountId32, + asset_id: u128, + min_balance: u128, +) -> Result { + let data = [ + [0xab, 0x70, 0x0a, 0x1b].to_vec(), + (asset_id, min_balance).encode(), + ] + .concat(); + do_bare_call(addr, data, ONE) +} + +fn create_caller( + addr: AccountId32, + asset_id: u128, + min_balance: u128, +) -> Result { + let data = [ + [0x7f, 0xb0, 0xf9, 0xbb].to_vec(), + (asset_id, min_balance).encode(), + ] + .concat(); + do_bare_call(addr, data, ONE) +} + +fn mint( + addr: AccountId32, + asset_id: u128, + beneficiary: AccountId32, + amount: u128, +) -> Result { + let data = [ + [0xcf, 0xdd, 0x9a, 0xa2].to_vec(), + (asset_id, beneficiary, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn burn( + addr: AccountId32, + asset_id: u128, + who: AccountId32, + amount: u128, +) -> Result { + let data = [ + [0xb1, 0xef, 0xc1, 0x7b].to_vec(), + (asset_id, who, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn transfer( + addr: AccountId32, + asset_id: u128, + target: AccountId32, + amount: u128, +) -> Result { + let data = [ + [0x84, 0xa1, 0x5d, 0xa1].to_vec(), + (asset_id, target, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn transfer_approved( + addr: AccountId32, + asset_id: u128, + owner: AccountId32, + dest: AccountId32, + amount: u128, +) -> Result { + let data = [ + [0x31, 0x05, 0x59, 0x75].to_vec(), + (asset_id, owner, dest, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn approve_transfer( + addr: AccountId32, + asset_id: u128, + delegate: AccountId32, + amount: u128, +) -> Result { + let data = [ + [0x8e, 0x7c, 0x3e, 0xe9].to_vec(), + (asset_id, delegate, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn cancel_approval( + addr: AccountId32, + asset_id: u128, + delegate: AccountId32, +) -> Result { + let data = [ + [0x31, 0x7c, 0x8e, 0x29].to_vec(), + (asset_id, delegate).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn transfer_ownership( + addr: AccountId32, + asset_id: u128, + owner: AccountId32, +) -> Result { + let data = [ + [0x10, 0x7e, 0x33, 0xea].to_vec(), + (asset_id, owner).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn set_metadata( + addr: AccountId32, + asset_id: u128, + name: Vec, + symbol: Vec, + decimals: u8, +) -> Result { + let data = [ + [0x0b, 0x78, 0x7b, 0xb5].to_vec(), + (asset_id, name, symbol, decimals).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn balance_of(addr: AccountId32, asset_id: u128, who: AccountId32) -> ExecReturnValue { + let data = [[0x0f, 0x75, 0x5a, 0x56].to_vec(), (asset_id, who).encode()].concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn total_supply(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [[0xdb, 0x63, 0x75, 0xa8].to_vec(), asset_id.encode()].concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn allowance( + addr: AccountId32, + asset_id: u128, + owner: AccountId32, + delegate: AccountId32, +) -> ExecReturnValue { + let data = [ + [0x6a, 0x00, 0x16, 0x5e].to_vec(), + (asset_id, owner, delegate).encode(), + ] + .concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn metadata_name(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [[0xf5, 0xcd, 0xdb, 0xc1].to_vec(), asset_id.encode()].concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn metadata_symbol(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [[0x7c, 0xdc, 0xaf, 0xc1].to_vec(), asset_id.encode()].concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn metadata_decimals(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [[0x25, 0x54, 0x47, 0x3b].to_vec(), asset_id.encode()].concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn do_bare_call( + addr: AccountId32, + input: Vec, + value: u128, +) -> Result { + Contracts::bare_call( + ALICE, + addr.into(), + value.into(), + GAS_LIMIT, + None, + input, + false, + Determinism::Deterministic, + ) + .result +} diff --git a/chain-extensions/pallet-assets/test-contract/asset_wrapper.json b/chain-extensions/pallet-assets/test-contract/asset_wrapper.json new file mode 100644 index 00000000..f9a6f1b5 --- /dev/null +++ b/chain-extensions/pallet-assets/test-contract/asset_wrapper.json @@ -0,0 +1,1203 @@ +{ + "source": { + "hash": "0xe355795885a65d3d6235679e5f71239c7404471d1573c86e60ee838701490ed1", + "language": "ink! 4.2.0", + "compiler": "rustc 1.69.0-nightly", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "2.1.0", + "rust_toolchain": "nightly-x86_64-unknown-linux-gnu", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "asset_wrapper", + "version": "0.1.0", + "authors": [ + "Stake Technologies " + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "default": false, + "docs": [], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" + ], + "type": 0 + }, + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 7 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 3 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 16 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 17 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 14 + }, + "maxEventTopics": 4, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 15 + } + }, + "events": [], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 2 + }, + "messages": [ + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "min_balance", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "create", + "mutates": true, + "payable": true, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0xab700a1b" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "beneficiary", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "mint", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0xcfdd9aa2" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "who", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "burn", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0xb1efc17b" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "target", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "transfer", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0x84a15da1" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "who", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + } + ], + "default": false, + "docs": [], + "label": "balance_of", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 10 + }, + "selector": "0x0f755a56" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "total_supply", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 10 + }, + "selector": "0xdb6375a8" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + }, + { + "label": "delegate", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + } + ], + "default": false, + "docs": [], + "label": "allowance", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 10 + }, + "selector": "0x6a00165e" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "delegate", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "approve_transfer", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0x8e7c3ee9" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "delegate", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + } + ], + "default": false, + "docs": [], + "label": "cancel_approval", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0x317c8e29" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + }, + { + "label": "destination", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "transfer_approved", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0x31055975" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "name", + "type": { + "displayName": [ + "Vec" + ], + "type": 11 + } + }, + { + "label": "symbol", + "type": { + "displayName": [ + "Vec" + ], + "type": 11 + } + }, + { + "label": "decimals", + "type": { + "displayName": [ + "u8" + ], + "type": 9 + } + } + ], + "default": false, + "docs": [], + "label": "set_metadata", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0x0b787bb5" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "metadata_name", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0xf5cddbc1" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "metadata_symbol", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0x7cdcafc1" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "metadata_decimals", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 13 + }, + "selector": "0x2554473b" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 7 + } + } + ], + "default": false, + "docs": [], + "label": "transfer_ownership", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0x107e33ea" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "min_balance", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "create_caller", + "mutates": true, + "payable": true, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 4 + }, + "selector": "0x7fb0f9bb" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [], + "name": "Mock" + } + }, + "root_key": "0x00000000" + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 1 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 1 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 2, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 3, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 4, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 5 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 5 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 5, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 1 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 6 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 1 + }, + { + "name": "E", + "type": 6 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 6, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "BalanceLow" + }, + { + "index": 2, + "name": "NoAccount" + }, + { + "index": 3, + "name": "NoPermission" + }, + { + "index": 4, + "name": "Unknown" + }, + { + "index": 5, + "name": "Frozen" + }, + { + "index": 6, + "name": "InUse" + }, + { + "index": 7, + "name": "BadWitness" + }, + { + "index": 8, + "name": "MinBalanceZero" + }, + { + "index": 9, + "name": "NoProvider" + }, + { + "index": 10, + "name": "BadMetadata" + }, + { + "index": 11, + "name": "Unapproved" + }, + { + "index": 12, + "name": "WouldDie" + }, + { + "index": 13, + "name": "AlreadyExists" + }, + { + "index": 14, + "name": "NoDeposit" + }, + { + "index": 15, + "name": "WouldBurn" + }, + { + "index": 16, + "name": "LiveAsset" + }, + { + "index": 17, + "name": "AssetNotLive" + }, + { + "index": 18, + "name": "IncorrectStatus" + }, + { + "index": 19, + "name": "NotFrozen" + }, + { + "index": 98, + "name": "OriginCannotBeCaller" + }, + { + "index": 99, + "name": "RuntimeError" + }, + { + "index": 21, + "name": "UnknownStatusCode" + }, + { + "index": 22, + "name": "InvalidScaleEncoding" + } + ] + } + }, + "path": [ + "assets_extension", + "AssetsError" + ] + } + }, + { + "id": 7, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 8, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "array": { + "len": 32, + "type": 9 + } + } + } + }, + { + "id": 9, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 10, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 11, + "type": { + "def": { + "sequence": { + "type": 9 + } + } + } + }, + { + "id": 12, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 11 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 11 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 13, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 9 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 9 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 14, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 8, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 15, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 16, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 17, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": "4" +} \ No newline at end of file diff --git a/chain-extensions/pallet-assets/test-contract/asset_wrapper.wasm b/chain-extensions/pallet-assets/test-contract/asset_wrapper.wasm new file mode 100644 index 00000000..555f80d5 Binary files /dev/null and b/chain-extensions/pallet-assets/test-contract/asset_wrapper.wasm differ