From 4dbe88107252f7bcd1d3b3dc1c8dfabff919ab49 Mon Sep 17 00:00:00 2001 From: PierreOssun <35110271+PierreOssun@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:22:04 +0100 Subject: [PATCH] Add pallet Assets Chain Extension (#1124) * Transfer * remove dep * num in std * burn and mint tests * Added tests * fmt * Added integration tests * clean code * PR comments * PR comments --- Cargo.lock | 8 +- chain-extensions/pallet-assets/Cargo.toml | 7 + chain-extensions/pallet-assets/src/lib.rs | 415 ++----- chain-extensions/pallet-assets/src/mock.rs | 228 ++++ chain-extensions/pallet-assets/src/tests.rs | 478 ++++++++ chain-extensions/pallet-assets/src/weights.rs | 62 - chain-extensions/types/assets/Cargo.toml | 7 +- chain-extensions/types/assets/src/lib.rs | 70 +- runtime/local/src/chain_extensions.rs | 4 +- runtime/local/src/lib.rs | 2 +- runtime/shibuya/src/chain_extensions.rs | 4 +- runtime/shibuya/src/lib.rs | 2 +- .../pallet_assets_extension.json | 1039 +++++++++++++++++ .../pallet_assets_extension.wasm | Bin 0 -> 17980 bytes tests/integration/Cargo.toml | 1 + .../src/assets_chain_extensions.rs | 352 ++++++ tests/integration/src/lib.rs | 3 + 17 files changed, 2289 insertions(+), 393 deletions(-) create mode 100644 chain-extensions/pallet-assets/src/mock.rs create mode 100644 chain-extensions/pallet-assets/src/tests.rs delete mode 100644 chain-extensions/pallet-assets/src/weights.rs create mode 100644 tests/ink-contracts/pallet_assets_extension.json create mode 100644 tests/ink-contracts/pallet_assets_extension.wasm create mode 100644 tests/integration/src/assets_chain_extensions.rs diff --git a/Cargo.lock b/Cargo.lock index 01bdaedad3..aed6afc6ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,8 +404,7 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" name = "assets-chain-extension-types" version = "0.1.0" dependencies = [ - "frame-system", - "pallet-contracts", + "num_enum 0.5.11", "parity-scale-codec", "scale-info", "sp-runtime", @@ -4954,6 +4953,7 @@ dependencies = [ name = "integration-tests" version = "0.1.0" dependencies = [ + "assets-chain-extension-types", "astar-primitives", "astar-runtime", "astar-test-utils", @@ -7473,16 +7473,20 @@ name = "pallet-chain-extension-assets" version = "0.1.0" dependencies = [ "assets-chain-extension-types", + "env_logger 0.9.3", "frame-support", "frame-system", "log", "num-traits", "pallet-assets", + "pallet-balances", "pallet-contracts", "pallet-contracts-primitives", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", + "sp-io", "sp-runtime", "sp-std", ] diff --git a/chain-extensions/pallet-assets/Cargo.toml b/chain-extensions/pallet-assets/Cargo.toml index 94319eef7f..72c69bb32a 100644 --- a/chain-extensions/pallet-assets/Cargo.toml +++ b/chain-extensions/pallet-assets/Cargo.toml @@ -23,6 +23,12 @@ 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 } + [features] default = ["std"] std = [ @@ -38,4 +44,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 5c7de57c9f..ee177b840c 100644 --- a/chain-extensions/pallet-assets/src/lib.rs +++ b/chain-extensions/pallet-assets/src/lib.rs @@ -18,398 +18,231 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod weights; - -use assets_chain_extension_types::{select_origin, Origin, Outcome}; -use frame_support::traits::{ - fungibles::approvals::Inspect as ApprovalInspect, - fungibles::metadata::Inspect as MetadataInspect, +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub use assets_chain_extension_types::Command::{self, *}; +use assets_chain_extension_types::{handle_result, Outcome, LOG_TARGET}; +use frame_support::traits::fungibles::{ + approvals::Inspect as AllowanceInspect, metadata::Inspect as MetadataInspect, Inspect, }; +use frame_support::DefaultNoBound; use frame_system::RawOrigin; use pallet_assets::WeightInfo; use pallet_contracts::chain_extension::{ ChainExtension, Environment, Ext, InitState, RetVal, SysConfig, }; use parity_scale_codec::Encode; -use sp_runtime::traits::StaticLookup; +use sp_runtime::traits::{Get, StaticLookup}; use sp_runtime::DispatchError; use sp_std::marker::PhantomData; -use sp_std::vec::Vec; - -enum AssetsFunc { - Create, - Transfer, - Mint, - Burn, - BalanceOf, - TotalSupply, - Allowance, - ApproveTransfer, - CancelApproval, - TransferApproved, - SetMetadata, - MetadataName, - MetadataSymbol, - MetadataDecimals, - TransferOwnership, -} - -impl TryFrom for AssetsFunc { - type Error = DispatchError; - - fn try_from(value: u16) -> Result { - match value { - 1 => Ok(AssetsFunc::Create), - 2 => Ok(AssetsFunc::Transfer), - 3 => Ok(AssetsFunc::Mint), - 4 => Ok(AssetsFunc::Burn), - 5 => Ok(AssetsFunc::BalanceOf), - 6 => Ok(AssetsFunc::TotalSupply), - 7 => Ok(AssetsFunc::Allowance), - 8 => Ok(AssetsFunc::ApproveTransfer), - 9 => Ok(AssetsFunc::CancelApproval), - 10 => Ok(AssetsFunc::TransferApproved), - 11 => Ok(AssetsFunc::SetMetadata), - 12 => Ok(AssetsFunc::MetadataName), - 13 => Ok(AssetsFunc::MetadataSymbol), - 14 => Ok(AssetsFunc::MetadataDecimals), - 15 => Ok(AssetsFunc::TransferOwnership), - _ => Err(DispatchError::Other( - "PalletAssetsExtension: Unimplemented func_id", - )), - } - } -} +type Weight = ::WeightInfo; /// Pallet Assets chain extension. -pub struct AssetsExtension(PhantomData<(T, W)>); +#[derive(DefaultNoBound)] +pub struct AssetsExtension(PhantomData); -impl Default for AssetsExtension { - fn default() -> Self { - AssetsExtension(PhantomData) - } -} - -impl ChainExtension for AssetsExtension +impl ChainExtension for AssetsExtension where T: pallet_assets::Config + pallet_contracts::Config, ::AssetId: Copy, <::Lookup as StaticLookup>::Source: From<::AccountId>, - ::AccountId: From<[u8; 32]>, - W: weights::WeightInfo, { fn call(&mut self, env: Environment) -> Result where E: Ext, { - let func_id = env.func_id().try_into()?; let mut env = env.buf_in_buf_out(); + match env.func_id().try_into().map_err(|_| { + DispatchError::Other("Unsupported func id in Pallet Assets Chain Extension") + })? { + Transfer => { + env.charge_weight(Weight::::transfer())?; - match func_id { - AssetsFunc::Create => { - let (origin, id, admin, min_balance): ( - Origin, + let (id, target, amount): ( ::AssetId, T::AccountId, T::Balance, ) = env.read_as()?; - let base_weight = ::WeightInfo::create(); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + log::trace!( + target: LOG_TARGET, + "transfer: raw arguments: id: {:?}, to: {:?}, amount: {:?}", + id, + target, + amount + ); - let call_result = pallet_assets::Pallet::::create( - raw_origin.into(), + let call_result = pallet_assets::Pallet::::transfer( + RawOrigin::Signed(env.ext().address().clone()).into(), id.into(), - admin.into(), - min_balance, + target.into(), + amount, ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; + handle_result!(call_result); } - AssetsFunc::Transfer => { - let (origin, id, target, amount): ( - Origin, + TransferApproved => { + env.charge_weight(Weight::::transfer_approved())?; + + let (id, owner, destination, amount): ( ::AssetId, T::AccountId, + T::AccountId, T::Balance, ) = env.read_as()?; - let base_weight = ::WeightInfo::transfer(); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + log::trace!(target: LOG_TARGET, + "transfer_approved: raw arguments: id: {:?}, owner: {:?}, destination: {:?}, amount: {:?}", + id, owner, destination, amount); - let call_result = pallet_assets::Pallet::::transfer( - raw_origin.into(), + let call_result = pallet_assets::Pallet::::transfer_approved( + RawOrigin::Signed(env.ext().address().clone()).into(), id.into(), - target.into(), + owner.into(), + destination.into(), amount, ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; + handle_result!(call_result); } - AssetsFunc::Mint => { - let (origin, id, beneficiary, amount): ( - Origin, + Mint => { + env.charge_weight(Weight::::mint())?; + + let (id, beneficiary, amount): ( ::AssetId, T::AccountId, T::Balance, ) = env.read_as()?; - let base_weight = ::WeightInfo::mint(); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + log::trace!( + target: LOG_TARGET, + "mint: raw arguments: id: {:?}, beneficiary: {:?}, amount: {:?}", + id, + beneficiary, + amount + ); let call_result = pallet_assets::Pallet::::mint( - raw_origin.into(), + RawOrigin::Signed(env.ext().address().clone()).into(), id.into(), beneficiary.into(), amount, ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; + handle_result!(call_result); } - AssetsFunc::Burn => { - let (origin, id, who, amount): ( - Origin, + Burn => { + env.charge_weight(Weight::::burn())?; + + let (id, who, amount): ( ::AssetId, T::AccountId, T::Balance, ) = env.read_as()?; - let base_weight = ::WeightInfo::burn(); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + log::trace!( + target: LOG_TARGET, + "burn: raw arguments: id: {:?}, who: {:?}, amount: {:?}", + id, + who, + amount + ); let call_result = pallet_assets::Pallet::::burn( - raw_origin.into(), + RawOrigin::Signed(env.ext().address().clone()).into(), id.into(), who.into(), amount, ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; - } - AssetsFunc::BalanceOf => { - let (id, who): (::AssetId, T::AccountId) = - env.read_as()?; - - let base_weight = ::balance_of(); - env.charge_weight(base_weight)?; - - let balance = pallet_assets::Pallet::::balance(id, who); - env.write(&balance.encode(), false, None)?; + handle_result!(call_result); } - AssetsFunc::TotalSupply => { - let id: ::AssetId = env.read_as()?; - - let base_weight = ::total_supply(); - env.charge_weight(base_weight)?; + ApproveTransfer => { + env.charge_weight(Weight::::approve_transfer())?; - let total_supply = pallet_assets::Pallet::::total_supply(id); - env.write(&total_supply.encode(), false, None)?; - } - AssetsFunc::Allowance => { - let (id, owner, delegate): ( - ::AssetId, - T::AccountId, - T::AccountId, - ) = env.read_as()?; - - let base_weight = ::allowance(); - env.charge_weight(base_weight)?; - - let allowance = pallet_assets::Pallet::::allowance(id, &owner, &delegate); - env.write(&allowance.encode(), false, None)?; - } - AssetsFunc::ApproveTransfer => { - let (origin, id, delegate, amount): ( - Origin, + let (id, delegate, amount): ( ::AssetId, T::AccountId, T::Balance, ) = env.read_as()?; - let base_weight = ::WeightInfo::approve_transfer(); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + log::trace!( + target: LOG_TARGET, + "approve_transfer: raw arguments: id: {:?}, delegate: {:?}, amount: {:?}", + id, + delegate, + amount + ); let call_result = pallet_assets::Pallet::::approve_transfer( - raw_origin.into(), + RawOrigin::Signed(env.ext().address().clone()).into(), id.into(), delegate.into(), amount, ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; + handle_result!(call_result); } - AssetsFunc::CancelApproval => { - let (origin, id, delegate): ( - Origin, - ::AssetId, - T::AccountId, - ) = env.read_as()?; + BalanceOf => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; - let base_weight = ::WeightInfo::cancel_approval(); - env.charge_weight(base_weight)?; + let (id, who): (::AssetId, T::AccountId) = + env.read_as()?; - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + pallet_assets::Pallet::::balance(id, who) + .using_encoded(|r| env.write(r, false, None))?; + } + TotalSupply => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; - let call_result = pallet_assets::Pallet::::cancel_approval( - raw_origin.into(), - id.into(), - delegate.into(), - ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; + let id: ::AssetId = env.read_as()?; + + pallet_assets::Pallet::::total_supply(id) + .using_encoded(|r| env.write(r, false, None))?; } - AssetsFunc::TransferApproved => { - let (origin, id, owner, destination, amount): ( - Origin, + Allowance => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + + let (id, owner, delegate): ( ::AssetId, T::AccountId, T::AccountId, - T::Balance, ) = env.read_as()?; - let base_weight = ::WeightInfo::transfer_approved(); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); - - let call_result = pallet_assets::Pallet::::transfer_approved( - raw_origin.into(), - id.into(), - owner.into(), - destination.into(), - amount, - ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; + as AllowanceInspect>::allowance( + id, &owner, &delegate, + ) + .using_encoded(|r| env.write(r, false, None))?; } - AssetsFunc::SetMetadata => { - let (origin, id, name, symbol, decimals): ( - Origin, - ::AssetId, - Vec, - Vec, - u8, - ) = env.read_as_unbounded(env.in_len())?; - - let base_weight = ::WeightInfo::set_metadata( - name.len() as u32, - symbol.len() as u32, - ); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + MetadataName => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; - let call_result = pallet_assets::Pallet::::set_metadata( - raw_origin.into(), - id.into(), - name, - symbol, - decimals, - ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; - } - AssetsFunc::MetadataName => { let id: ::AssetId = env.read_as()?; - let base_weight = ::metadata_name(); - env.charge_weight(base_weight)?; - - let name = pallet_assets::Pallet::::name(id); - env.write(&name.encode(), false, None)?; + as MetadataInspect>::name(id) + .using_encoded(|r| env.write(r, false, None))?; } - AssetsFunc::MetadataSymbol => { - let id: ::AssetId = env.read_as()?; + MetadataSymbol => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; - let base_weight = ::metadata_symbol(); - env.charge_weight(base_weight)?; + let id: ::AssetId = env.read_as()?; - let symbol = pallet_assets::Pallet::::symbol(id); - env.write(&symbol.encode(), false, None)?; + as MetadataInspect>::symbol(id) + .using_encoded(|r| env.write(r, false, None))?; } - AssetsFunc::MetadataDecimals => { - let id: ::AssetId = env.read_as()?; + MetadataDecimals => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; - let base_weight = ::metadata_decimals(); - env.charge_weight(base_weight)?; + let id: ::AssetId = env.read_as()?; - let decimals = pallet_assets::Pallet::::decimals(id); - env.write(&decimals.encode(), false, None)?; + as MetadataInspect>::decimals(id) + .using_encoded(|r| env.write(r, false, None))?; } - AssetsFunc::TransferOwnership => { - let (origin, id, owner): ( - Origin, - ::AssetId, - T::AccountId, - ) = env.read_as()?; + MinimumBalance => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; - let base_weight = ::WeightInfo::transfer_ownership(); - env.charge_weight(base_weight)?; - - let raw_origin = select_origin!(&origin, env.ext().address().clone()); + let id: ::AssetId = env.read_as()?; - let call_result = pallet_assets::Pallet::::transfer_ownership( - raw_origin.into(), - id.into(), - owner.into(), - ); - return match call_result { - Err(e) => { - let mapped_error = Outcome::from(e); - Ok(RetVal::Converging(mapped_error as u32)) - } - Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), - }; + as Inspect>::minimum_balance(id) + .using_encoded(|r| env.write(r, false, None))?; } } diff --git a/chain-extensions/pallet-assets/src/mock.rs b/chain-extensions/pallet-assets/src/mock.rs new file mode 100644 index 0000000000..1ba7a0b8ce --- /dev/null +++ b/chain-extensions/pallet-assets/src/mock.rs @@ -0,0 +1,228 @@ +// 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::AssetsExtension; +use frame_support::traits::{AsEnsureOriginWithArg, ConstU128, Currency, Randomness}; +use frame_support::{ + parameter_types, sp_io, + traits::{ConstU32, ConstU64, Nothing}, + weights::Weight, +}; +use pallet_contracts::chain_extension::RegisteredChainExtension; +use pallet_contracts::{Config, DefaultAddressGenerator, Frame}; +use sp_core::crypto::AccountId32; +use sp_runtime::generic; +use sp_runtime::testing::H256; +use sp_runtime::traits::{BlakeTwo256, Convert, IdentityLookup, Zero}; + +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_parts(2_000_000_000_000, 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 static UnstableInterface: bool = true; + pub Schedule: pallet_contracts::Schedule = Default::default(); + pub static DepositPerByte: BalanceOf = 1; + pub const DepositPerItem: BalanceOf = 1; + pub const DefaultDepositLimit: Balance = 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 Schedule = Schedule; + type DepositPerByte = DepositPerByte; + type DepositPerItem = DepositPerItem; + type DefaultDepositLimit = DefaultDepositLimit; + 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 = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<1>; + type WeightInfo = (); +} + +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 = (); + 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_parts(100_000_000_000, 700_000); +pub const ONE: u128 = 1_000_000_000_000_000_000; + +pub const ASSET_ID: u128 = 1; + +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.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 0000000000..a6f78d6024 --- /dev/null +++ b/chain-extensions/pallet-assets/src/tests.rs @@ -0,0 +1,478 @@ +// 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 assets_chain_extension_types::selector_bytes; +use frame_support::assert_ok; +use frame_support::traits::Currency; +use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; +use pallet_contracts_primitives::{Code, ExecReturnValue}; +use parity_scale_codec::Encode; +use sp_core::crypto::AccountId32; +use sp_io::hashing::blake2_256; +use sp_runtime::DispatchError; +use std::fs; + +// Those tests use the assets chain extension example available here: +// https://github.com/AstarNetwork/ink-test-contracts +// 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(asset_id, who, amount)?; +// Ok(()) +// } + +#[test] +fn mint_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - create asset + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + ALICE, + 1 + )); + // Arrange - Give contract mint permission (Issuer role) + assert_ok!(Assets::set_team( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + addr.clone(), + ALICE, + ALICE + )); + + // Act - Mint 1000 assets to Alice + assert_ok!(mint(addr.clone(), ASSET_ID, ALICE, 1000)); + + // Assert - Alice balance is 1000 + assert_eq!(Assets::balance(ASSET_ID, ALICE), 1000); + }); +} + +#[test] +fn burn_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange - create asset and give contract mint permission (Issuer role) and burn permission (Admin role) + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + ALICE, + 1 + )); + assert_ok!(Assets::set_team( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + addr.clone(), + addr.clone(), + ALICE + )); + + // Act - Mint 1000 assets to Alice + assert_ok!(mint(addr.clone(), ASSET_ID, ALICE, 1000)); + assert_eq!(Assets::balance(ASSET_ID, ALICE), 1000); + + // Act - Burn 1000 of Alice tokens + assert_ok!(burn(addr.clone(), ASSET_ID, ALICE, 1000)); + + // Assert - Balance of Alice is then 0 + assert_eq!(Assets::balance(ASSET_ID, ALICE), 0); + }); +} + +#[test] +fn transfer_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Assert - Create, mint and transfer 1000 to contract + assert_ok!(Assets::create(RuntimeOrigin::signed(BOB), ASSET_ID, BOB, 1)); + assert_ok!(Assets::mint( + RuntimeOrigin::signed(BOB), + ASSET_ID, + BOB, + 1000 + )); + assert_ok!(Assets::transfer( + RuntimeOrigin::signed(BOB), + ASSET_ID, + addr.clone(), + 1000 + )); + + // Act - Transfer 1000 from contract to Alice + assert_ok!(transfer(addr.clone(), ASSET_ID, ALICE, 1000)); + + // Assert - Alice balance is 1000 and contract is zero + assert_eq!(Assets::balance(ASSET_ID, ALICE), 1000); + assert_eq!(Assets::balance(ASSET_ID, 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!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + ALICE, + 1 + )); + assert_ok!(Assets::mint( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + ALICE, + 1000 + )); + + // Assert - Balance and total supply is 1000 + assert_eq!( + balance_of(addr.clone(), ASSET_ID, ALICE).data[1..], + 1000u128.encode() + ); + assert_eq!( + total_supply(addr.clone(), ASSET_ID).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 and fund contract with ED + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + ALICE, + 1 + )); + assert_ok!(Assets::mint( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + addr.clone(), + 1000 + )); + let _ = Balances::deposit_creating(&addr.clone(), 1); + + // Act - approve transfer To BOB for 100 + assert_ok!(approve_transfer(addr.clone(), ASSET_ID, BOB, 100)); + + // Assert - Bob has 100 allowance + assert_eq!( + allowance(addr.clone(), ASSET_ID, 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), ASSET_ID, BOB, 1)); + assert_ok!(Assets::mint( + RuntimeOrigin::signed(BOB), + ASSET_ID, + BOB, + 1000 + )); + assert_ok!(Assets::approve_transfer( + RuntimeOrigin::signed(BOB), + ASSET_ID, + addr.clone(), + 100 + )); + + // Act - The contract transfer 100 from Bob to Alice + assert_ok!(transfer_approved(addr.clone(), ASSET_ID, BOB, ALICE, 100)); + + // Assert - Bob has 900 and Alice 100 + assert_eq!(Assets::balance(ASSET_ID, BOB), 900u128); + assert_eq!(Assets::balance(ASSET_ID, ALICE), 100u128); + }); +} + +#[test] +fn getters_works() { + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + let addr = instantiate(); + + // Arrange + // Alice creates & mint token + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + ALICE, + 1 + )); + assert_ok!(Assets::mint( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + ALICE, + 1000 + )); + assert_ok!(Assets::approve_transfer( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + BOB, + 100 + )); + assert_ok!(Assets::set_metadata( + RuntimeOrigin::signed(ALICE), + ASSET_ID, + "Token".as_bytes().to_vec(), + "TKN".as_bytes().to_vec(), + 1 + )); + + // Assert - verify state using chain extension getters + assert_eq!( + allowance(addr.clone(), ASSET_ID, ALICE, BOB).data[1..], + 100u128.encode() + ); + assert_eq!( + balance_of(addr.clone(), ASSET_ID, ALICE).data[1..], + 1000u128.encode() + ); + assert_eq!( + total_supply(addr.clone(), ASSET_ID).data[1..], + 1000u128.encode() + ); + assert_eq!(metadata_decimals(addr.clone(), ASSET_ID).data[1..], [1u8]); + assert_eq!( + metadata_name(addr.clone(), ASSET_ID).data[1..], + "Token".encode() + ); + assert_eq!( + metadata_symbol(addr.clone(), ASSET_ID).data[1..], + "TKN".encode() + ); + assert_eq!( + minimum_balance(addr.clone(), ASSET_ID).data[1..], + 1u128.encode() + ); + }); +} + +fn instantiate() -> AccountId32 { + let code = fs::read("../../tests/ink-contracts/pallet_assets_extension.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 = selector_bytes!("new").to_vec(); + Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + Some(ONE), + Code::Upload(code), + instance_selector, + vec![], + DebugInfo::Skip, + CollectEvents::UnsafeCollect, + ) + .result + .unwrap() + .account_id +} + +fn transfer( + addr: AccountId32, + asset_id: u128, + target: AccountId32, + amount: u128, +) -> Result { + let data = [ + selector_bytes!("transfer").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 = [ + selector_bytes!("transfer_approved").to_vec(), + (asset_id, owner, dest, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn mint( + addr: AccountId32, + asset_id: u128, + beneficiary: AccountId32, + amount: u128, +) -> Result { + let data = [ + selector_bytes!("mint").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 = [ + selector_bytes!("burn").to_vec(), + (asset_id, who, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn approve_transfer( + addr: AccountId32, + asset_id: u128, + delegate: AccountId32, + amount: u128, +) -> Result { + let data = [ + selector_bytes!("approve_transfer").to_vec(), + (asset_id, delegate, amount).encode(), + ] + .concat(); + do_bare_call(addr, data, 0) +} + +fn balance_of(addr: AccountId32, asset_id: u128, who: AccountId32) -> ExecReturnValue { + let data = [ + selector_bytes!("balance_of").to_vec(), + (asset_id, who).encode(), + ] + .concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn allowance( + addr: AccountId32, + asset_id: u128, + owner: AccountId32, + delegate: AccountId32, +) -> ExecReturnValue { + let data = [ + selector_bytes!("allowance").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 = [selector_bytes!("metadata_name").to_vec(), asset_id.encode()].concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn metadata_symbol(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [ + selector_bytes!("metadata_symbol").to_vec(), + asset_id.encode(), + ] + .concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn metadata_decimals(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [ + selector_bytes!("metadata_decimals").to_vec(), + asset_id.encode(), + ] + .concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn total_supply(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [selector_bytes!("total_supply").to_vec(), asset_id.encode()].concat(); + do_bare_call(addr, data, 0).unwrap() +} + +fn minimum_balance(addr: AccountId32, asset_id: u128) -> ExecReturnValue { + let data = [ + selector_bytes!("minimum_balance").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, + DebugInfo::Skip, + CollectEvents::UnsafeCollect, + Determinism::Relaxed, + ) + .result +} diff --git a/chain-extensions/pallet-assets/src/weights.rs b/chain-extensions/pallet-assets/src/weights.rs deleted file mode 100644 index 3b2913684a..0000000000 --- a/chain-extensions/pallet-assets/src/weights.rs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 . - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for pallet-assets chain-extension. -pub trait WeightInfo { - fn balance_of() -> Weight; - fn total_supply() -> Weight; - fn allowance() -> Weight; - fn metadata_name() -> Weight; - fn metadata_symbol() -> Weight; - fn metadata_decimals() -> Weight; -} - -/// Weights for pallet-assets chain-extension -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - fn balance_of() -> Weight { - T::DbWeight::get().reads(1 as u64) - } - - fn total_supply() -> Weight { - T::DbWeight::get().reads(1 as u64) - } - - fn allowance() -> Weight { - T::DbWeight::get().reads(1 as u64) - } - - fn metadata_name() -> Weight { - T::DbWeight::get().reads(1 as u64) - } - - fn metadata_symbol() -> Weight { - T::DbWeight::get().reads(1 as u64) - } - - fn metadata_decimals() -> Weight { - T::DbWeight::get().reads(1 as u64) - } -} diff --git a/chain-extensions/types/assets/Cargo.toml b/chain-extensions/types/assets/Cargo.toml index af2c61ada9..ddb1119268 100644 --- a/chain-extensions/types/assets/Cargo.toml +++ b/chain-extensions/types/assets/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "assets-chain-extension-types" version = "0.1.0" -license = "Apache-2.0" description = "Types definitions for assets chain-extension" authors.workspace = true edition.workspace = true @@ -9,19 +8,17 @@ homepage.workspace = true repository.workspace = true [dependencies] +num_enum = { workspace = true } parity-scale-codec = { workspace = true } scale-info = { workspace = true } -frame-system = { workspace = true } -pallet-contracts = { workspace = true } sp-runtime = { workspace = true } [features] default = ["std"] std = [ + "num_enum/std", "scale-info/std", "parity-scale-codec/std", - "pallet-contracts/std", "sp-runtime/std", - "frame-system/std", ] diff --git a/chain-extensions/types/assets/src/lib.rs b/chain-extensions/types/assets/src/lib.rs index 5ca4b8bfbb..884be3ad71 100644 --- a/chain-extensions/types/assets/src/lib.rs +++ b/chain-extensions/types/assets/src/lib.rs @@ -17,10 +17,30 @@ // along with Astar. If not, see . #![cfg_attr(not(feature = "std"), no_std)] -use parity_scale_codec::MaxEncodedLen; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; use parity_scale_codec::{Decode, Encode}; use sp_runtime::{DispatchError, ModuleError}; +pub const LOG_TARGET: &str = "pallet-chain-extension-assets"; + +#[repr(u16)] +#[derive(TryFromPrimitive, IntoPrimitive, Decode, Encode)] +pub enum Command { + Transfer = 0, + Mint = 1, + Burn = 2, + ApproveTransfer = 4, + TransferApproved = 5, + BalanceOf = 6, + TotalSupply = 7, + Allowance = 8, + MetadataName = 9, + MetadataSymbol = 10, + MetadataDecimals = 11, + MinimumBalance = 12, +} + #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum Outcome { @@ -43,9 +63,9 @@ pub enum Outcome { /// Minimum balance should be non-zero. MinBalanceZero = 8, /// Unable to increment the consumer reference counters on the account. Either no provider - /// reference exists to allow a non-zero balance of a non-self-sufficient asset, or the - /// maximum number of consumers has been reached. - NoProvider = 9, + /// reference exists to allow a non-zero balance of a non-self-sufficient asset, or one + /// fewer then the maximum number of consumers has been reached. + UnavailableConsumer = 9, /// Invalid metadata given. BadMetadata = 10, /// No approval exists that would allow the transfer. @@ -67,8 +87,8 @@ pub enum Outcome { IncorrectStatus = 18, /// The asset should be frozen before the given operation. NotFrozen = 19, - /// Origin Caller is not supported - OriginCannotBeCaller = 98, + /// Callback action resulted in error + CallbackFailed = 20, /// Unknown error RuntimeError = 99, } @@ -88,7 +108,7 @@ impl From for Outcome { Some("InUse") => Outcome::InUse, Some("BadWitness") => Outcome::BadWitness, Some("MinBalanceZero") => Outcome::MinBalanceZero, - Some("NoProvider") => Outcome::NoProvider, + Some("UnavailableConsumer") => Outcome::UnavailableConsumer, Some("BadMetadata") => Outcome::BadMetadata, Some("Unapproved") => Outcome::Unapproved, Some("WouldDie") => Outcome::WouldDie, @@ -99,30 +119,30 @@ impl From for Outcome { Some("AssetNotLive") => Outcome::AssetNotLive, Some("IncorrectStatus") => Outcome::IncorrectStatus, Some("NotFrozen") => Outcome::NotFrozen, + Some("CallbackFailed") => Outcome::CallbackFailed, _ => Outcome::RuntimeError, }; } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub enum Origin { - Caller, - Address, -} - -impl Default for Origin { - fn default() -> Self { - Self::Address - } +#[macro_export] +macro_rules! handle_result { + ($call_result:expr) => {{ + return match $call_result { + Err(e) => { + log::trace!(target: LOG_TARGET, "err: {:?}", e); + let mapped_error = Outcome::from(e); + Ok(RetVal::Converging(mapped_error as u32)) + } + Ok(_) => Ok(RetVal::Converging(Outcome::Success as u32)), + }; + }}; } #[macro_export] -macro_rules! select_origin { - ($origin:expr, $account:expr) => { - match $origin { - Origin::Caller => return Ok(RetVal::Converging(Outcome::OriginCannotBeCaller as u32)), - Origin::Address => RawOrigin::Signed($account), - } - }; +macro_rules! selector_bytes { + ($s:expr) => {{ + let hash = blake2_256($s.as_bytes()); + [hash[0], hash[1], hash[2], hash[3]] + }}; } diff --git a/runtime/local/src/chain_extensions.rs b/runtime/local/src/chain_extensions.rs index a368fa8d18..da32a3bc23 100644 --- a/runtime/local/src/chain_extensions.rs +++ b/runtime/local/src/chain_extensions.rs @@ -31,9 +31,7 @@ impl RegisteredChainExtension for XvmExtension RegisteredChainExtension - for AssetsExtension -{ +impl RegisteredChainExtension for AssetsExtension { const ID: u16 = 02; } diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 87be4ef2de..cbfcd9800e 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -921,7 +921,7 @@ impl pallet_contracts::Config for Runtime { type ChainExtension = ( // DappsStakingExtension, XvmExtension, - AssetsExtension>, + AssetsExtension, UnifiedAccountsExtension, ); type Schedule = Schedule; diff --git a/runtime/shibuya/src/chain_extensions.rs b/runtime/shibuya/src/chain_extensions.rs index a368fa8d18..da32a3bc23 100644 --- a/runtime/shibuya/src/chain_extensions.rs +++ b/runtime/shibuya/src/chain_extensions.rs @@ -31,9 +31,7 @@ impl RegisteredChainExtension for XvmExtension RegisteredChainExtension - for AssetsExtension -{ +impl RegisteredChainExtension for AssetsExtension { const ID: u16 = 02; } diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index 0955c63642..d618fc5e74 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -709,7 +709,7 @@ impl pallet_contracts::Config for Runtime { type WeightInfo = pallet_contracts::weights::SubstrateWeight; type ChainExtension = ( XvmExtension, - AssetsExtension>, + AssetsExtension, UnifiedAccountsExtension, ); type Schedule = Schedule; diff --git a/tests/ink-contracts/pallet_assets_extension.json b/tests/ink-contracts/pallet_assets_extension.json new file mode 100644 index 0000000000..a200592b9b --- /dev/null +++ b/tests/ink-contracts/pallet_assets_extension.json @@ -0,0 +1,1039 @@ +{ + "source": { + "hash": "0x9162ff2905afd5a515781d295a5c44e333f49f0c19f43982b84c29c4609a2098", + "language": "ink! 4.3.0", + "compiler": "rustc 1.74.0", + "build_info": { + "build_mode": "Debug", + "cargo_contract_version": "3.2.0", + "rust_toolchain": "stable-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "pallet-assets-extension", + "version": "0.1.0", + "authors": [ + "Stake Technologies " + ], + "repository": "https://github.com/AstarNetwork/ink-test-contracts", + "homepage": "https://astar.network/" + }, + "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": 4 + }, + "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": "beneficiary", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "mint", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 7 + }, + "selector": "0xcfdd9aa2" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "who", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "burn", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 7 + }, + "selector": "0xb1efc17b" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "target", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "transfer", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 7 + }, + "selector": "0x84a15da1" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "delegate", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "approve_transfer", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 7 + }, + "selector": "0x8e7c3ee9" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + }, + { + "label": "destination", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + }, + { + "label": "amount", + "type": { + "displayName": [ + "Balance" + ], + "type": 3 + } + } + ], + "default": false, + "docs": [], + "label": "transfer_approved", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 7 + }, + "selector": "0x31055975" + }, + { + "args": [ + { + "label": "asset_id", + "type": { + "displayName": [ + "u128" + ], + "type": 3 + } + }, + { + "label": "who", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + } + ], + "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": 4 + } + }, + { + "label": "delegate", + "type": { + "displayName": [ + "AccountId" + ], + "type": 4 + } + } + ], + "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 + } + } + ], + "default": false, + "docs": [], + "label": "metadata_name", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 11 + }, + "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": 11 + }, + "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 + } + } + ], + "default": false, + "docs": [], + "label": "minimum_balance", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 10 + }, + "selector": "0x1aa48863" + } + ] + }, + "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": { + "composite": { + "fields": [ + { + "type": 5, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 5, + "type": { + "def": { + "array": { + "len": 32, + "type": 6 + } + } + } + }, + { + "id": 6, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 7, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 8 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 8 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 1 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 9 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 1 + }, + { + "name": "E", + "type": 9 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 9, + "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": "UnavailableConsumer" + }, + { + "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": 20, + "name": "CallbackFailed" + }, + { + "index": 99, + "name": "RuntimeError" + }, + { + "index": 21, + "name": "UnknownStatusCode" + }, + { + "index": 22, + "name": "InvalidScaleEncoding" + } + ] + } + }, + "path": [ + "pallet_assets_extension", + "helper", + "AssetsError" + ] + } + }, + { + "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": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 12 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 12 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 12, + "type": { + "def": { + "sequence": { + "type": 6 + } + } + } + }, + { + "id": 13, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 6 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 2 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 6 + }, + { + "name": "E", + "type": 2 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 14, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 5, + "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/tests/ink-contracts/pallet_assets_extension.wasm b/tests/ink-contracts/pallet_assets_extension.wasm new file mode 100644 index 0000000000000000000000000000000000000000..04f59f6b74b353a14c548188a0171a5b9014461f GIT binary patch literal 17980 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 Pt>JkN&)4w$Mx_5A2qCyx literal 0 HcmV?d00001 diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 5bb0a19714..557300a54b 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -34,6 +34,7 @@ sp-io = { workspace = true } sp-runtime = { workspace = true } # astar dependencies +assets-chain-extension-types = { workspace = true } pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dispatch = { workspace = true } diff --git a/tests/integration/src/assets_chain_extensions.rs b/tests/integration/src/assets_chain_extensions.rs new file mode 100644 index 0000000000..8eb65a6fb3 --- /dev/null +++ b/tests/integration/src/assets_chain_extensions.rs @@ -0,0 +1,352 @@ +// 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::setup::*; +use assets_chain_extension_types::selector_bytes; +use parity_scale_codec::Encode; +use sp_io::hashing::blake2_256; + +const ASSETS_CE: &'static str = "pallet_assets_extension"; + +const ASSET_ID: u128 = 200; + +#[test] +fn mint_transfer_burn_works() { + new_test_ext().execute_with(|| { + let contract_id = deploy_wasm_contract(ASSETS_CE); + + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + ALICE.into(), + 1, + )); + + // Give to contract mint permission (Issuer role) and burn permission (Admin role) + assert_ok!(Assets::set_team( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + contract_id.clone().into(), + contract_id.clone().into(), + contract_id.clone().into(), + )); + + // Call mint to mint 1000 to contract + assert_eq!( + call_wasm_contract_method::>( + ALICE, + contract_id.clone(), + [ + selector_bytes!("mint").to_vec(), + ASSET_ID.encode(), + contract_id.encode(), + 1000u128.encode() + ] + .concat() + ), + Ok(()) + ); + + // Assert contract balance + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("balance_of").to_vec(), + ASSET_ID.encode(), + contract_id.encode() + ] + .concat() + ), + 1000u128 + ); + + // Transfer 100 from contract to Alice + assert_eq!( + call_wasm_contract_method::>( + ALICE, + contract_id.clone(), + [ + selector_bytes!("transfer").to_vec(), + ASSET_ID.encode(), + ALICE.encode(), + 100u128.encode() + ] + .concat() + ), + Ok(()) + ); + + // Assert Alice balance + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("balance_of").to_vec(), + ASSET_ID.encode(), + ALICE.encode() + ] + .concat() + ), + 100u128 + ); + + // Contract burn 50 tokens + assert_eq!( + call_wasm_contract_method::>( + ALICE, + contract_id.clone(), + [ + selector_bytes!("burn").to_vec(), + ASSET_ID.encode(), + contract_id.encode(), + 50u128.encode() + ] + .concat() + ), + Ok(()) + ); + + // // Check that Balance of Alice is reduced to 850 + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("balance_of").to_vec(), + ASSET_ID.encode(), + contract_id.encode() + ] + .concat() + ), + 850u128 + ); + + // Check that total supply has been reduced to 950 + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [selector_bytes!("total_supply").to_vec(), ASSET_ID.encode()].concat() + ), + 950u128 + ); + }); +} + +#[test] +fn approve_works() { + new_test_ext().execute_with(|| { + let contract_id = deploy_wasm_contract(ASSETS_CE); + + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + ALICE.into(), + 1, + )); + + // Contract approve Alice to spend 100 tokens + // First the contract need Existential DDeposit + assert_ok!(Balances::transfer( + RuntimeOrigin::signed(ALICE), + contract_id.clone().into(), + UNIT, + )); + + assert_eq!( + call_wasm_contract_method::>( + ALICE, + contract_id.clone(), + [ + selector_bytes!("approve_transfer").to_vec(), + ASSET_ID.encode(), + ALICE.encode(), + 100u128.encode() + ] + .concat() + ), + Ok(()) + ); + + // Check that Allowance of Alice is 100 + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("allowance").to_vec(), + ASSET_ID.encode(), + contract_id.encode(), + ALICE.encode() + ] + .concat() + ), + 100u128 + ); + }); +} + +#[test] +fn getters_works() { + new_test_ext().execute_with(|| { + let contract_id = deploy_wasm_contract(ASSETS_CE); + + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + ALICE.into(), + 1, + )); + + assert_ok!(Assets::set_metadata( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + "Token".encode(), + "TKN".encode(), + 1 + )); + + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("metadata_decimals").to_vec(), + ASSET_ID.encode() + ] + .concat() + ), + 1u8 + ); + + assert_eq!( + call_wasm_contract_method::>( + ALICE, + contract_id.clone(), + [selector_bytes!("metadata_name").to_vec(), ASSET_ID.encode()].concat() + ), + "Token".encode() + ); + + assert_eq!( + call_wasm_contract_method::>( + ALICE, + contract_id.clone(), + [ + selector_bytes!("metadata_symbol").to_vec(), + ASSET_ID.encode() + ] + .concat() + ), + "TKN".encode() + ); + + // Check Minimum Balance + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("minimum_balance").to_vec(), + ASSET_ID.encode() + ] + .concat() + ), + 1u128 + ); + }); +} + +#[test] +fn transfer_approved_works() { + new_test_ext().execute_with(|| { + let contract_id = deploy_wasm_contract(ASSETS_CE); + + assert_ok!(Assets::create( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + ALICE.into(), + 1, + )); + + // Mint 1000 tokens to Alice + assert_ok!(Assets::mint( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + ALICE.into(), + 1000 + )); + + // Alice approve the contract to spend 100 on her behalf + assert_ok!(Assets::approve_transfer( + RuntimeOrigin::signed(ALICE), + ASSET_ID.into(), + contract_id.clone().into(), + 100 + )); + + // The contract transfer 100 tokens from Alice to itself + assert_eq!( + call_wasm_contract_method::>( + ALICE, + contract_id.clone(), + [ + selector_bytes!("transfer_approved").to_vec(), + ASSET_ID.encode(), + ALICE.encode(), + contract_id.encode(), + 100u128.encode() + ] + .concat() + ), + Ok(()) + ); + + // Check that contract received the 100 and that Alice balance is 900 + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("balance_of").to_vec(), + ASSET_ID.encode(), + contract_id.encode() + ] + .concat() + ), + 100u128 + ); + + assert_eq!( + call_wasm_contract_method::( + ALICE, + contract_id.clone(), + [ + selector_bytes!("balance_of").to_vec(), + ASSET_ID.encode(), + ALICE.encode() + ] + .concat() + ), + 900u128 + ); + }); +} diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 43b2d9843b..7de23893ae 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -45,3 +45,6 @@ mod dispatch_precompile_filter_old; #[cfg(feature = "shibuya")] mod unified_accounts; + +#[cfg(any(feature = "shibuya"))] +mod assets_chain_extensions;