From 8a8abfd2ea9c929e3d6a49f30e04e17d68324be2 Mon Sep 17 00:00:00 2001 From: Gabriel Facco de Arruda Date: Thu, 24 Aug 2023 22:23:45 -0300 Subject: [PATCH 1/7] feat: Initial implementation of NFT Origins Pallet --- Cargo.toml | 3 +- pallet-nft-origins/Cargo.toml | 58 +++++++++++++++ pallet-nft-origins/src/chains.rs | 6 ++ pallet-nft-origins/src/lib.rs | 95 ++++++++++++++++++++++++ pallet-nft-origins/src/location.rs | 93 +++++++++++++++++++++++ pallet-nft-origins/src/origin.rs | 23 ++++++ pallet-nft-origins/src/xcm_converters.rs | 53 +++++++++++++ 7 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 pallet-nft-origins/Cargo.toml create mode 100644 pallet-nft-origins/src/chains.rs create mode 100644 pallet-nft-origins/src/lib.rs create mode 100644 pallet-nft-origins/src/location.rs create mode 100644 pallet-nft-origins/src/origin.rs create mode 100644 pallet-nft-origins/src/xcm_converters.rs diff --git a/Cargo.toml b/Cargo.toml index e00d1e58..d0b1da23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ members = [ "INV4/pallet-inv4", "OCIF/staking", "pallet-checked-inflation", - "pallet-rings" + "pallet-rings", + "pallet-nft-origins", ] diff --git a/pallet-nft-origins/Cargo.toml b/pallet-nft-origins/Cargo.toml new file mode 100644 index 00000000..1067698e --- /dev/null +++ b/pallet-nft-origins/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = 'pallet-nft-origins' +authors = ['InvArchitects '] +description = '' +edition = '2021' +homepage = 'https://invarch.network' +license = 'GPLv3' +repository = 'https://github.com/InvArch/InvArch-Frames/' +version = '0.1.0-dev' + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { version = "0.4.14", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } +scale-info = { version = "2.1.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.140", features = ["derive"], optional = true } + +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } + +pallet-xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.43" } +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.43" } +xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.43" } + +frame-benchmarking = { git = 'https://github.com/paritytech/substrate.git', default-features = false, optional = true, branch = "polkadot-v0.9.43" } + +[dev-dependencies] +xcm-builder = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.43" } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-arithmetic/std", + "sp-io/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "xcm/std", + "xcm-builder/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallet-nft-origins/src/chains.rs b/pallet-nft-origins/src/chains.rs new file mode 100644 index 00000000..7f344b0a --- /dev/null +++ b/pallet-nft-origins/src/chains.rs @@ -0,0 +1,6 @@ +use crate::location::Chain; +use xcm::latest::Junction; + +pub trait ChainVerifier { + fn get_chain_from_verifier(para_id_part: u32, verifier_part: Junction) -> Option; +} diff --git a/pallet-nft-origins/src/lib.rs b/pallet-nft-origins/src/lib.rs new file mode 100644 index 00000000..ed9819bb --- /dev/null +++ b/pallet-nft-origins/src/lib.rs @@ -0,0 +1,95 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod chains; +pub mod location; +pub mod origin; +pub mod xcm_converters; + +pub use chains::ChainVerifier; +pub use location::{Chain, Collection, Nft, NftLocation}; +pub use origin::NftOrigin; +pub use xcm_converters::*; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use crate::{chains::ChainVerifier, origin::NftOrigin}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_xcm::Config { + type Chains: ChainVerifier; + + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::origin] + pub type Origin = NftOrigin; + + #[pallet::error] + pub enum Error { + SendingFailed, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + NftCalledTestFunction { + nft_location: crate::location::NftLocation, + }, + } + + #[pallet::call] + impl Pallet + where + Result::RuntimeOrigin>: + From<::RuntimeOrigin>, + { + #[pallet::call_index(0)] + #[pallet::weight(1)] + pub fn test_nft_location(nft: OriginFor) -> DispatchResult { + let nft_location = + crate::origin::ensure_nft::::RuntimeOrigin>(nft)?; + + Self::deposit_event(Event::::NftCalledTestFunction { nft_location }); + + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(1)] + pub fn test_send_xcm_as( + o: OriginFor, + verifier: xcm::latest::Junction, + collection: xcm::latest::Junction, + nft: xcm::latest::Junction, + call: sp_std::vec::Vec, + ) -> DispatchResult { + let interior = xcm::latest::Junctions::X3(verifier, collection, nft); + + let dest = xcm::latest::MultiLocation { + parents: 1, + interior: xcm::latest::Junctions::X1(xcm::latest::Junction::Parachain(2125)), + }; + + let message = xcm::latest::Xcm(sp_std::vec![xcm::latest::Instruction::Transact { + origin_kind: xcm::latest::OriginKind::Native, + require_weight_at_most: xcm::latest::Weight::from_parts(50000000, 10000), + call: as From>>::from(call), + }]); + + pallet_xcm::Pallet::::send_xcm(interior, dest, message) + .map_err(|_| Error::::SendingFailed)?; + + Ok(()) + } + } +} diff --git a/pallet-nft-origins/src/location.rs b/pallet-nft-origins/src/location.rs new file mode 100644 index 00000000..0826a4b2 --- /dev/null +++ b/pallet-nft-origins/src/location.rs @@ -0,0 +1,93 @@ +use crate::{ + chains::ChainVerifier, + pallet::{self, Origin}, + Config, +}; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; +use frame_support::{error::BadOrigin, RuntimeDebug}; +use scale_info::TypeInfo; +use sp_io::hashing::blake2_256; +use sp_runtime::traits::{AtLeast32BitUnsigned, TrailingZeroInput}; +use xcm::latest::Junction; + +#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +pub enum Chain { + /// Relay chain + Relay, + /// Parachain with ParaId + Parachain(u32), +} + +#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +pub enum Collection { + /// Pallet based NFT collection + Id(u128), + /// EVM based NFT collection + Contract20([u8; 20]), + /// WASM based NFT collection + Contract32([u8; 32]), +} + +#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +pub enum Nft { + /// Integer NFT id + Id(u128), + /// 20 bytes NFT id + Key20([u8; 20]), + /// 32 bytes NFT id + Key32([u8; 32]), +} + +#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +pub struct NftLocation { + /// Chain where the collection and NFT originate + pub chain: Chain, + /// NFT collection + pub collection: Collection, + /// Specific NFT + pub nft: Nft, +} + +impl NftLocation { + pub fn new( + para_id: u32, + verifier_junction: Junction, + collection: Collection, + nft: Nft, + ) -> Option { + <::Chains as ChainVerifier>::get_chain_from_verifier( + para_id, + verifier_junction, + ) + .map(|chain| NftLocation { + chain, + collection, + nft, + }) + } + + pub fn derive_account(&self) -> AccountId { + let chain = match self.chain { + Chain::Relay => "relay".encode(), + Chain::Parachain(para_id) => ["para-".encode(), para_id.encode()].concat(), + }; + + let collection = match self.collection { + Collection::Id(id) => ["id-".encode(), id.encode()].concat(), + Collection::Contract20(key) => ["contract20-".encode(), key.encode()].concat(), + Collection::Contract32(key) => ["contract32-".encode(), key.encode()].concat(), + }; + + let nft = match self.nft { + Nft::Id(id) => ["id-".encode(), id.encode()].concat(), + Nft::Key20(key) => ["key20-".encode(), key.encode()].concat(), + Nft::Key32(key) => ["key32-".encode(), key.encode()].concat(), + }; + + let entropy = (chain, collection, nft).using_encoded(blake2_256); + + AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } +} diff --git a/pallet-nft-origins/src/origin.rs b/pallet-nft-origins/src/origin.rs new file mode 100644 index 00000000..d191d95c --- /dev/null +++ b/pallet-nft-origins/src/origin.rs @@ -0,0 +1,23 @@ +use crate::{ + location::NftLocation, + pallet::{self, Origin}, + Config, +}; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; +use frame_support::{error::BadOrigin, RuntimeDebug}; +use scale_info::TypeInfo; +use sp_runtime::traits::AtLeast32BitUnsigned; + +#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +pub struct NftOrigin(pub NftLocation); + +pub fn ensure_nft(o: OuterOrigin) -> Result +where + OuterOrigin: Into>, +{ + match o.into() { + Ok(NftOrigin(internal)) => Ok(internal), + _ => Err(BadOrigin), + } +} diff --git a/pallet-nft-origins/src/xcm_converters.rs b/pallet-nft-origins/src/xcm_converters.rs new file mode 100644 index 00000000..40a1894b --- /dev/null +++ b/pallet-nft-origins/src/xcm_converters.rs @@ -0,0 +1,53 @@ +use crate::{ + chains::ChainVerifier, + location::{Collection, Nft, NftLocation}, + origin::NftOrigin, + Config, +}; +use core::marker::PhantomData; +use frame_support::traits::OriginTrait; +use xcm::latest::{Junction, Junctions::X4, MultiLocation, OriginKind}; +use xcm_executor::traits::ConvertOrigin; + +pub struct NftMultiLocationAsOrigin(PhantomData<(RuntimeOrigin, T)>); + +impl, T: Config> + ConvertOrigin for NftMultiLocationAsOrigin +{ + fn convert_origin( + origin: impl Into, + kind: OriginKind, + ) -> Result { + let origin = origin.into(); + log::trace!(target: "xcm::origin_conversion", "ParentAsSuperuser origin: {:?}, kind: {:?}", origin, kind); + + match (kind, origin) { + ( + OriginKind::Native, + MultiLocation { + // Parents will match from the perspective of the relay or one of it's child parachains. + parents: 0 | 1, + interior: + X4( + Junction::Parachain(para_id), + verifier_junction, + Junction::AccountKey20 { + network: None, + key: collection_key, + }, + Junction::GeneralIndex(nft_id), + ), + }, + ) => NftLocation::new::( + para_id, + verifier_junction, + Collection::Contract20(collection_key), + Nft::Id(nft_id), + ) + .map(|location| NftOrigin(location).into()) + .ok_or(origin), + + (_, origin) => Err(origin), + } + } +} From ada818cd27d277160f6aadea13c8e10dd8277be4 Mon Sep 17 00:00:00 2001 From: Gabriel Facco de Arruda Date: Fri, 25 Aug 2023 11:57:28 -0300 Subject: [PATCH 2/7] refactor: Improved nft account derivation --- pallet-nft-origins/src/location.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pallet-nft-origins/src/location.rs b/pallet-nft-origins/src/location.rs index 0826a4b2..f43f93e7 100644 --- a/pallet-nft-origins/src/location.rs +++ b/pallet-nft-origins/src/location.rs @@ -9,12 +9,12 @@ use frame_support::{error::BadOrigin, RuntimeDebug}; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::traits::{AtLeast32BitUnsigned, TrailingZeroInput}; -use xcm::latest::Junction; +use xcm::latest::{Junction, NetworkId}; #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] pub enum Chain { /// Relay chain - Relay, + Relay(NetworkId), /// Parachain with ParaId Parachain(u32), } @@ -49,6 +49,8 @@ pub struct NftLocation { pub nft: Nft, } +use sp_std::boxed::Box; + impl NftLocation { pub fn new( para_id: u32, @@ -69,23 +71,23 @@ impl NftLocation { pub fn derive_account(&self) -> AccountId { let chain = match self.chain { - Chain::Relay => "relay".encode(), - Chain::Parachain(para_id) => ["para-".encode(), para_id.encode()].concat(), + Chain::Relay(network_id) => (b"relay", network_id).encode(), + Chain::Parachain(para_id) => (b"para", para_id).encode(), }; let collection = match self.collection { - Collection::Id(id) => ["id-".encode(), id.encode()].concat(), - Collection::Contract20(key) => ["contract20-".encode(), key.encode()].concat(), - Collection::Contract32(key) => ["contract32-".encode(), key.encode()].concat(), + Collection::Id(id) => (b"id", id).encode(), + Collection::Contract20(key) => (b"contract20", key).encode(), + Collection::Contract32(key) => (b"contract32", key).encode(), }; let nft = match self.nft { - Nft::Id(id) => ["id-".encode(), id.encode()].concat(), - Nft::Key20(key) => ["key20-".encode(), key.encode()].concat(), - Nft::Key32(key) => ["key32-".encode(), key.encode()].concat(), + Nft::Id(id) => (b"id", id).encode(), + Nft::Key20(key) => (b"key20", key).encode(), + Nft::Key32(key) => (b"key32", key).encode(), }; - let entropy = (chain, collection, nft).using_encoded(blake2_256); + let entropy = blake2_256(&[chain, collection, nft].concat()); AccountId::decode(&mut TrailingZeroInput::new(entropy.as_ref())) .expect("infinite length input; no invalid inputs for type; qed") From bbb82e28afec6faa0f2e986ccbda6e737b5342b7 Mon Sep 17 00:00:00 2001 From: Gabriel Facco de Arruda Date: Mon, 28 Aug 2023 13:56:51 -0300 Subject: [PATCH 3/7] feat: Nft verifier origin for single DescendOrigin XCM --- pallet-nft-origins/src/lib.rs | 76 ++++++++++++++++++++++-- pallet-nft-origins/src/location.rs | 26 ++++++-- pallet-nft-origins/src/origin.rs | 24 +++++--- pallet-nft-origins/src/xcm_converters.rs | 48 ++++++++++++--- 4 files changed, 149 insertions(+), 25 deletions(-) diff --git a/pallet-nft-origins/src/lib.rs b/pallet-nft-origins/src/lib.rs index ed9819bb..78f50892 100644 --- a/pallet-nft-origins/src/lib.rs +++ b/pallet-nft-origins/src/lib.rs @@ -14,15 +14,33 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { - use crate::{chains::ChainVerifier, origin::NftOrigin}; - use frame_support::pallet_prelude::*; + use crate::{ + chains::ChainVerifier, + location::{Collection, Nft, NftLocation}, + origin::NftOrigin, + }; + use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::*, + }; use frame_system::pallet_prelude::*; + use sp_std::boxed::Box; #[pallet::config] pub trait Config: frame_system::Config + pallet_xcm::Config { type Chains: ChainVerifier; type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type RuntimeCall: Parameter + + Dispatchable< + RuntimeOrigin = ::RuntimeOrigin, + PostInfo = PostDispatchInfo, + > + GetDispatchInfo + + From> + + IsType<::RuntimeCall>; + + type RuntimeOrigin: From + From<::RuntimeOrigin>; } #[pallet::pallet] @@ -66,8 +84,8 @@ pub mod pallet { #[pallet::call_index(1)] #[pallet::weight(1)] - pub fn test_send_xcm_as( - o: OriginFor, + pub fn test_send_xcm_as_nft( + _: OriginFor, verifier: xcm::latest::Junction, collection: xcm::latest::Junction, nft: xcm::latest::Junction, @@ -91,5 +109,55 @@ pub mod pallet { Ok(()) } + + #[pallet::call_index(2)] + #[pallet::weight(1)] + pub fn test_send_xcm_as_verifier( + _: OriginFor, + verifier: xcm::latest::Junction, + call: sp_std::vec::Vec, + ) -> DispatchResult { + let interior = xcm::latest::Junctions::X1(verifier); + + let dest = xcm::latest::MultiLocation { + parents: 1, + interior: xcm::latest::Junctions::X1(xcm::latest::Junction::Parachain(2125)), + }; + + let message = xcm::latest::Xcm(sp_std::vec![xcm::latest::Instruction::Transact { + origin_kind: xcm::latest::OriginKind::Native, + require_weight_at_most: xcm::latest::Weight::from_parts(50000000, 10000), + call: as From>>::from(call), + }]); + + pallet_xcm::Pallet::::send_xcm(interior, dest, message) + .map_err(|_| Error::::SendingFailed)?; + + Ok(()) + } + + #[pallet::call_index(3)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + ( + dispatch_info.weight, + dispatch_info.class, + ) + })] + pub fn dispatch_as_nft( + verifier: OriginFor, + collection: Collection, + nft: Nft, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + let chain = crate::origin::ensure_verifier::< + T, + ::RuntimeOrigin, + >(verifier)?; + + let nft_location = NftLocation::new(chain, collection, nft); + + (*call).dispatch(NftOrigin::Nft(nft_location).into()) + } } } diff --git a/pallet-nft-origins/src/location.rs b/pallet-nft-origins/src/location.rs index f43f93e7..7c4e4332 100644 --- a/pallet-nft-origins/src/location.rs +++ b/pallet-nft-origins/src/location.rs @@ -13,12 +13,22 @@ use xcm::latest::{Junction, NetworkId}; #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] pub enum Chain { - /// Relay chain Relay(NetworkId), - /// Parachain with ParaId Parachain(u32), } +impl Chain { + pub fn new_parachain_verified( + para_id: u32, + verifier_junction: Junction, + ) -> Option { + <::Chains as ChainVerifier>::get_chain_from_verifier( + para_id, + verifier_junction, + ) + } +} + #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] pub enum Collection { /// Pallet based NFT collection @@ -49,10 +59,8 @@ pub struct NftLocation { pub nft: Nft, } -use sp_std::boxed::Box; - impl NftLocation { - pub fn new( + pub fn new_verified( para_id: u32, verifier_junction: Junction, collection: Collection, @@ -69,6 +77,14 @@ impl NftLocation { }) } + pub fn new(chain: Chain, collection: Collection, nft: Nft) -> Self { + Self { + chain, + collection, + nft, + } + } + pub fn derive_account(&self) -> AccountId { let chain = match self.chain { Chain::Relay(network_id) => (b"relay", network_id).encode(), diff --git a/pallet-nft-origins/src/origin.rs b/pallet-nft-origins/src/origin.rs index d191d95c..6c5e0cdc 100644 --- a/pallet-nft-origins/src/origin.rs +++ b/pallet-nft-origins/src/origin.rs @@ -1,23 +1,33 @@ use crate::{ - location::NftLocation, - pallet::{self, Origin}, - Config, + location::{Chain, NftLocation}, + pallet, Config, }; use codec::{Decode, Encode, MaxEncodedLen}; -use core::marker::PhantomData; use frame_support::{error::BadOrigin, RuntimeDebug}; use scale_info::TypeInfo; -use sp_runtime::traits::AtLeast32BitUnsigned; #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] -pub struct NftOrigin(pub NftLocation); +pub enum NftOrigin { + Nft(NftLocation), + Verifier(Chain), +} pub fn ensure_nft(o: OuterOrigin) -> Result where OuterOrigin: Into>, { match o.into() { - Ok(NftOrigin(internal)) => Ok(internal), + Ok(NftOrigin::Nft(nft_location)) => Ok(nft_location), + _ => Err(BadOrigin), + } +} + +pub fn ensure_verifier(o: OuterOrigin) -> Result +where + OuterOrigin: Into>, +{ + match o.into() { + Ok(NftOrigin::Verifier(chain)) => Ok(chain), _ => Err(BadOrigin), } } diff --git a/pallet-nft-origins/src/xcm_converters.rs b/pallet-nft-origins/src/xcm_converters.rs index 40a1894b..a22a82da 100644 --- a/pallet-nft-origins/src/xcm_converters.rs +++ b/pallet-nft-origins/src/xcm_converters.rs @@ -1,18 +1,21 @@ use crate::{ - chains::ChainVerifier, - location::{Collection, Nft, NftLocation}, + location::{Chain, Collection, Nft, NftLocation}, origin::NftOrigin, Config, }; use core::marker::PhantomData; use frame_support::traits::OriginTrait; -use xcm::latest::{Junction, Junctions::X4, MultiLocation, OriginKind}; +use xcm::latest::{ + Junction, + Junctions::{X2, X4}, + MultiLocation, OriginKind, +}; use xcm_executor::traits::ConvertOrigin; -pub struct NftMultiLocationAsOrigin(PhantomData<(RuntimeOrigin, T)>); +pub struct NftMultilocationAsOrigin(PhantomData<(RuntimeOrigin, T)>); impl, T: Config> - ConvertOrigin for NftMultiLocationAsOrigin + ConvertOrigin for NftMultilocationAsOrigin { fn convert_origin( origin: impl Into, @@ -25,8 +28,7 @@ impl, T: Config> ( OriginKind::Native, MultiLocation { - // Parents will match from the perspective of the relay or one of it's child parachains. - parents: 0 | 1, + parents: 1, interior: X4( Junction::Parachain(para_id), @@ -38,16 +40,44 @@ impl, T: Config> Junction::GeneralIndex(nft_id), ), }, - ) => NftLocation::new::( + ) => NftLocation::new_verified::( para_id, verifier_junction, Collection::Contract20(collection_key), Nft::Id(nft_id), ) - .map(|location| NftOrigin(location).into()) + .map(|location| NftOrigin::Nft(location).into()) .ok_or(origin), (_, origin) => Err(origin), } } } + +pub struct VerifierMultilocationAsOrigin(PhantomData<(RuntimeOrigin, T)>); + +impl, T: Config> + ConvertOrigin for VerifierMultilocationAsOrigin +{ + fn convert_origin( + origin: impl Into, + kind: OriginKind, + ) -> Result { + let origin = origin.into(); + log::trace!(target: "xcm::origin_conversion", "ParentAsSuperuser origin: {:?}, kind: {:?}", origin, kind); + + match (kind, origin) { + ( + OriginKind::Native, + MultiLocation { + parents: 1, + interior: X2(Junction::Parachain(para_id), verifier_junction), + }, + ) => Chain::new_parachain_verified::(para_id, verifier_junction) + .map(|chain| NftOrigin::Verifier(chain).into()) + .ok_or(origin), + + (_, origin) => Err(origin), + } + } +} From 785f18d83f3cd417754f2786a28db45f0f492fe1 Mon Sep 17 00:00:00 2001 From: Gabriel Facco de Arruda Date: Thu, 31 Aug 2023 16:20:11 -0300 Subject: [PATCH 4/7] refactor: Improvements and U256 integer NFTs --- pallet-nft-origins/Cargo.toml | 2 + pallet-nft-origins/src/chains.rs | 4 +- pallet-nft-origins/src/lib.rs | 107 ++++++++++++++++------- pallet-nft-origins/src/location.rs | 55 ++++++------ pallet-nft-origins/src/origin.rs | 6 +- pallet-nft-origins/src/xcm_converters.rs | 23 +++-- 6 files changed, 120 insertions(+), 77 deletions(-) diff --git a/pallet-nft-origins/Cargo.toml b/pallet-nft-origins/Cargo.toml index 1067698e..7d1489ec 100644 --- a/pallet-nft-origins/Cargo.toml +++ b/pallet-nft-origins/Cargo.toml @@ -29,6 +29,8 @@ pallet-xcm = { git = "https://github.com/paritytech/polkadot", default-features xcm = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.43" } xcm-executor = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "release-v0.9.43" } +primitive-types = { version = "0.12.1", default-features = false } + frame-benchmarking = { git = 'https://github.com/paritytech/substrate.git', default-features = false, optional = true, branch = "polkadot-v0.9.43" } [dev-dependencies] diff --git a/pallet-nft-origins/src/chains.rs b/pallet-nft-origins/src/chains.rs index 7f344b0a..4035a274 100644 --- a/pallet-nft-origins/src/chains.rs +++ b/pallet-nft-origins/src/chains.rs @@ -1,6 +1,6 @@ -use crate::location::Chain; +use crate::location::Parachain; use xcm::latest::Junction; pub trait ChainVerifier { - fn get_chain_from_verifier(para_id_part: u32, verifier_part: Junction) -> Option; + fn get_chain_from_verifier(para_id_part: u32, verifier_part: Junction) -> Option; } diff --git a/pallet-nft-origins/src/lib.rs b/pallet-nft-origins/src/lib.rs index 78f50892..a84c523b 100644 --- a/pallet-nft-origins/src/lib.rs +++ b/pallet-nft-origins/src/lib.rs @@ -6,7 +6,7 @@ pub mod origin; pub mod xcm_converters; pub use chains::ChainVerifier; -pub use location::{Chain, Collection, Nft, NftLocation}; +pub use location::{Collection, Nft, NftLocation, Parachain}; pub use origin::NftOrigin; pub use xcm_converters::*; @@ -16,7 +16,7 @@ pub use pallet::*; pub mod pallet { use crate::{ chains::ChainVerifier, - location::{Collection, Nft, NftLocation}, + location::{Collection, Nft, NftLocation, Parachain}, origin::NftOrigin, }; use frame_support::{ @@ -40,9 +40,19 @@ pub mod pallet { + From> + IsType<::RuntimeCall>; + type RegisteredCalls: Parameter + + Dispatchable< + RuntimeOrigin = ::RuntimeOrigin, + PostInfo = PostDispatchInfo, + > + GetDispatchInfo; + type RuntimeOrigin: From + From<::RuntimeOrigin>; } + #[pallet::storage] + #[pallet::getter(fn get_registered_chain)] + pub type RegisteredChains = StorageMap<_, Twox128, xcm::latest::Junction, Parachain>; + #[pallet::pallet] pub struct Pallet(_); @@ -54,9 +64,6 @@ pub mod pallet { SendingFailed, } - #[pallet::hooks] - impl Hooks> for Pallet {} - #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { @@ -72,6 +79,68 @@ pub mod pallet { From<::RuntimeOrigin>, { #[pallet::call_index(0)] + #[pallet::weight({ + let dispatch_info = call.get_dispatch_info(); + ( + dispatch_info.weight, + dispatch_info.class, + ) + })] + pub fn dispatch_as_nft( + verifier: OriginFor, + collection: Collection, + nft: Nft, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + let chain = crate::origin::ensure_verifier::< + T, + ::RuntimeOrigin, + >(verifier)?; + + let nft_location = NftLocation::new(chain, collection, nft); + + (*call).dispatch(NftOrigin::Nft(nft_location).into()) + } + + #[pallet::call_index(1)] + #[pallet::weight({ + let dispatch_info = registered_call.get_dispatch_info(); + ( + dispatch_info.weight, + dispatch_info.class, + ) + })] + pub fn dispatch_registered_call_as_nft( + verifier: OriginFor, + collection: Collection, + nft: Nft, + registered_call: Box<::RegisteredCalls>, + ) -> DispatchResultWithPostInfo { + let chain = crate::origin::ensure_verifier::< + T, + ::RuntimeOrigin, + >(verifier)?; + + let nft_location = NftLocation::new(chain, collection, nft); + + (*registered_call).dispatch(NftOrigin::Nft(nft_location).into()) + } + + #[pallet::call_index(2)] + #[pallet::weight(1)] + pub fn set_registered_chain( + _: OriginFor, + verifier: xcm::latest::Junction, + chain: Option, + ) -> DispatchResult { + RegisteredChains::::set(verifier, chain); + + Ok(()) + } + + // \/ TEST CALLS \/ + + #[pallet::call_index(90)] #[pallet::weight(1)] pub fn test_nft_location(nft: OriginFor) -> DispatchResult { let nft_location = @@ -82,7 +151,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(1)] + #[pallet::call_index(91)] #[pallet::weight(1)] pub fn test_send_xcm_as_nft( _: OriginFor, @@ -110,7 +179,7 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(2)] + #[pallet::call_index(92)] #[pallet::weight(1)] pub fn test_send_xcm_as_verifier( _: OriginFor, @@ -135,29 +204,5 @@ pub mod pallet { Ok(()) } - - #[pallet::call_index(3)] - #[pallet::weight({ - let dispatch_info = call.get_dispatch_info(); - ( - dispatch_info.weight, - dispatch_info.class, - ) - })] - pub fn dispatch_as_nft( - verifier: OriginFor, - collection: Collection, - nft: Nft, - call: Box<::RuntimeCall>, - ) -> DispatchResultWithPostInfo { - let chain = crate::origin::ensure_verifier::< - T, - ::RuntimeOrigin, - >(verifier)?; - - let nft_location = NftLocation::new(chain, collection, nft); - - (*call).dispatch(NftOrigin::Nft(nft_location).into()) - } } } diff --git a/pallet-nft-origins/src/location.rs b/pallet-nft-origins/src/location.rs index 7c4e4332..c8c91c83 100644 --- a/pallet-nft-origins/src/location.rs +++ b/pallet-nft-origins/src/location.rs @@ -1,23 +1,16 @@ -use crate::{ - chains::ChainVerifier, - pallet::{self, Origin}, - Config, -}; +use crate::{chains::ChainVerifier, Config}; use codec::{Decode, Encode, MaxEncodedLen}; -use core::marker::PhantomData; -use frame_support::{error::BadOrigin, RuntimeDebug}; +use frame_support::RuntimeDebug; +use primitive_types::U256; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; -use sp_runtime::traits::{AtLeast32BitUnsigned, TrailingZeroInput}; -use xcm::latest::{Junction, NetworkId}; +use sp_runtime::traits::TrailingZeroInput; +use xcm::latest::Junction; #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] -pub enum Chain { - Relay(NetworkId), - Parachain(u32), -} +pub struct Parachain(pub u32); -impl Chain { +impl Parachain { pub fn new_parachain_verified( para_id: u32, verifier_junction: Junction, @@ -27,32 +20,40 @@ impl Chain { verifier_junction, ) } + + pub const fn para_id(&self) -> u32 { + self.0 + } } #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +#[repr(u8)] pub enum Collection { /// Pallet based NFT collection - Id(u128), + Id(u128) = 0, /// EVM based NFT collection - Contract20([u8; 20]), + Contract20([u8; 20]) = 1, /// WASM based NFT collection - Contract32([u8; 32]), + Contract32([u8; 32]) = 2, } #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +#[repr(u8)] pub enum Nft { - /// Integer NFT id - Id(u128), + /// U128 NFT id + U128Id(u128) = 0, + /// U256 NFT id + U256Id(U256) = 1, /// 20 bytes NFT id - Key20([u8; 20]), + Key20([u8; 20]) = 2, /// 32 bytes NFT id - Key32([u8; 32]), + Key32([u8; 32]) = 3, } #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] pub struct NftLocation { /// Chain where the collection and NFT originate - pub chain: Chain, + pub chain: Parachain, /// NFT collection pub collection: Collection, /// Specific NFT @@ -77,7 +78,7 @@ impl NftLocation { }) } - pub fn new(chain: Chain, collection: Collection, nft: Nft) -> Self { + pub fn new(chain: Parachain, collection: Collection, nft: Nft) -> Self { Self { chain, collection, @@ -86,10 +87,7 @@ impl NftLocation { } pub fn derive_account(&self) -> AccountId { - let chain = match self.chain { - Chain::Relay(network_id) => (b"relay", network_id).encode(), - Chain::Parachain(para_id) => (b"para", para_id).encode(), - }; + let chain = (b"para", self.chain.para_id()).encode(); let collection = match self.collection { Collection::Id(id) => (b"id", id).encode(), @@ -98,7 +96,8 @@ impl NftLocation { }; let nft = match self.nft { - Nft::Id(id) => (b"id", id).encode(), + Nft::U128Id(id) => (b"u128id", id).encode(), + Nft::U256Id(id) => (b"u256id", id).encode(), Nft::Key20(key) => (b"key20", key).encode(), Nft::Key32(key) => (b"key32", key).encode(), }; diff --git a/pallet-nft-origins/src/origin.rs b/pallet-nft-origins/src/origin.rs index 6c5e0cdc..2ed0d978 100644 --- a/pallet-nft-origins/src/origin.rs +++ b/pallet-nft-origins/src/origin.rs @@ -1,5 +1,5 @@ use crate::{ - location::{Chain, NftLocation}, + location::{NftLocation, Parachain}, pallet, Config, }; use codec::{Decode, Encode, MaxEncodedLen}; @@ -9,7 +9,7 @@ use scale_info::TypeInfo; #[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] pub enum NftOrigin { Nft(NftLocation), - Verifier(Chain), + Verifier(Parachain), } pub fn ensure_nft(o: OuterOrigin) -> Result @@ -22,7 +22,7 @@ where } } -pub fn ensure_verifier(o: OuterOrigin) -> Result +pub fn ensure_verifier(o: OuterOrigin) -> Result where OuterOrigin: Into>, { diff --git a/pallet-nft-origins/src/xcm_converters.rs b/pallet-nft-origins/src/xcm_converters.rs index a22a82da..c8894cbf 100644 --- a/pallet-nft-origins/src/xcm_converters.rs +++ b/pallet-nft-origins/src/xcm_converters.rs @@ -1,5 +1,5 @@ use crate::{ - location::{Chain, Collection, Nft, NftLocation}, + location::{Collection, Nft, NftLocation, Parachain}, origin::NftOrigin, Config, }; @@ -7,7 +7,7 @@ use core::marker::PhantomData; use frame_support::traits::OriginTrait; use xcm::latest::{ Junction, - Junctions::{X2, X4}, + Junctions::{X1, X2, X4}, MultiLocation, OriginKind, }; use xcm_executor::traits::ConvertOrigin; @@ -44,7 +44,7 @@ impl, T: Config> para_id, verifier_junction, Collection::Contract20(collection_key), - Nft::Id(nft_id), + Nft::U128Id(nft_id), ) .map(|location| NftOrigin::Nft(location).into()) .ok_or(origin), @@ -64,20 +64,17 @@ impl, T: Config> kind: OriginKind, ) -> Result { let origin = origin.into(); - log::trace!(target: "xcm::origin_conversion", "ParentAsSuperuser origin: {:?}, kind: {:?}", origin, kind); + log::trace!(target: "xcm::origin_conversion", "VerifierMultilocationAsOrigin origin: {:?}, kind: {:?}", origin, kind); - match (kind, origin) { - ( - OriginKind::Native, - MultiLocation { - parents: 1, - interior: X2(Junction::Parachain(para_id), verifier_junction), - }, - ) => Chain::new_parachain_verified::(para_id, verifier_junction) + match origin { + MultiLocation { + parents: 1, + interior: X2(Junction::Parachain(para_id), verifier_junction), + } => Parachain::new_parachain_verified::(para_id, verifier_junction) .map(|chain| NftOrigin::Verifier(chain).into()) .ok_or(origin), - (_, origin) => Err(origin), + _ => Err(origin), } } } From c4d400f229a878847ddc22aa53a54f53c84b2209 Mon Sep 17 00:00:00 2001 From: Gabriel Facco de Arruda Date: Mon, 4 Sep 2023 13:13:56 -0300 Subject: [PATCH 5/7] feat: NFT Origins in INV4 --- INV4/pallet-inv4/Cargo.toml | 1 + INV4/pallet-inv4/src/fee_handling.rs | 2 +- INV4/pallet-inv4/src/lib.rs | 28 ++++++++++--- INV4/pallet-inv4/src/multisig.rs | 61 ++++++++++++++++++++-------- INV4/pallet-inv4/src/voting.rs | 11 +++-- pallet-nft-origins/src/lib.rs | 10 +++++ pallet-nft-origins/src/location.rs | 16 ++++++-- 7 files changed, 98 insertions(+), 31 deletions(-) diff --git a/INV4/pallet-inv4/Cargo.toml b/INV4/pallet-inv4/Cargo.toml index 1be254b1..6f8d72b5 100644 --- a/INV4/pallet-inv4/Cargo.toml +++ b/INV4/pallet-inv4/Cargo.toml @@ -24,6 +24,7 @@ log = { version = "0.4.14", default-features = false } # InvArch dependencies primitives = { package = "invarch-primitives", path = "../../primitives", default-features = false } +pallet-nft-origins = { path = "../../pallet-nft-origins", default-features = false } sp-io = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } diff --git a/INV4/pallet-inv4/src/fee_handling.rs b/INV4/pallet-inv4/src/fee_handling.rs index 79e6e45f..5a63cfcb 100644 --- a/INV4/pallet-inv4/src/fee_handling.rs +++ b/INV4/pallet-inv4/src/fee_handling.rs @@ -43,7 +43,7 @@ pub trait MultisigFeeHandler { fn handle_creation_fee( imbalance: FeeAssetNegativeImbalance< - >::NegativeImbalance, + <::Currency as Currency>::NegativeImbalance, Credit, >, ); diff --git a/INV4/pallet-inv4/src/lib.rs b/INV4/pallet-inv4/src/lib.rs index f5073e09..a18008e8 100644 --- a/INV4/pallet-inv4/src/lib.rs +++ b/INV4/pallet-inv4/src/lib.rs @@ -51,6 +51,7 @@ pub mod pallet { use crate::{ fee_handling::MultisigFeeHandler, + multisig::{MultisigMember, MultisigMemberOf}, voting::{Tally, VoteRecord}, }; @@ -87,7 +88,9 @@ pub mod pallet { pub type CallOf = ::RuntimeCall; #[pallet::config] - pub trait Config: frame_system::Config + pallet_balances::Config { + pub trait Config: + frame_system::Config + pallet_balances::Config + pallet_nft_origins::Config + { /// The IPS Pallet Events type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The IPS ID type @@ -251,7 +254,7 @@ pub mod pallet { MultisigVoteAdded { core_id: T::CoreId, executor_account: T::AccountId, - voter: T::AccountId, + voter: MultisigMemberOf, votes_added: VoteRecord, current_votes: Tally, call_hash: T::Hash, @@ -269,7 +272,7 @@ pub mod pallet { MultisigExecuted { core_id: T::CoreId, executor_account: T::AccountId, - voter: T::AccountId, + voter: MultisigMemberOf, call_hash: T::Hash, call: CallOf, result: DispatchResult, @@ -321,6 +324,7 @@ pub mod pallet { INV4Origin::CoreId, ::AccountId>, ::RuntimeOrigin, >: From<::RuntimeOrigin>, + Result::RuntimeOrigin>: From<::RuntimeOrigin>, <::Currency as Currency<::AccountId>>::Balance: Sum, { /// Create IP (Intellectual Property) Set (IPS) @@ -354,7 +358,7 @@ pub mod pallet { pub fn token_mint( origin: OriginFor, amount: BalanceOf, - target: T::AccountId, + target: MultisigMemberOf, ) -> DispatchResult { Pallet::::inner_token_mint(origin, amount, target) } @@ -365,7 +369,7 @@ pub mod pallet { pub fn token_burn( origin: OriginFor, amount: BalanceOf, - target: T::AccountId, + target: MultisigMemberOf, ) -> DispatchResult { Pallet::::inner_token_burn(origin, amount, target) } @@ -395,6 +399,20 @@ pub mod pallet { call_hash: T::Hash, aye: bool, ) -> DispatchResultWithPostInfo { + let caller = MultisigMember::AccountId(ensure_signed(caller)?); + Pallet::::inner_vote_multisig(caller, core_id, call_hash, aye) + } + + #[pallet::call_index(10)] + #[pallet::weight(1)] + pub fn nft_vote_multisig( + origin: OriginFor, + core_id: T::CoreId, + call_hash: T::Hash, + aye: bool, + ) -> DispatchResultWithPostInfo { + let caller = MultisigMember::Nft(pallet_nft_origins::origin::ensure_nft::>(origin)?); + Pallet::::inner_vote_multisig(caller, core_id, call_hash, aye) } diff --git a/INV4/pallet-inv4/src/multisig.rs b/INV4/pallet-inv4/src/multisig.rs index 26a8f0d4..77a72699 100644 --- a/INV4/pallet-inv4/src/multisig.rs +++ b/INV4/pallet-inv4/src/multisig.rs @@ -5,6 +5,7 @@ use crate::{ util::derive_core_account, voting::{Tally, Vote}, }; +use codec::Decode; use core::{ convert::{TryFrom, TryInto}, iter::Sum, @@ -34,7 +35,7 @@ pub type BoundedCallBytes = BoundedVec::MaxCallSize>; #[derive(Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo, PartialEq, Eq)] pub struct MultisigOperation { pub tally: TallyOf, - pub original_caller: AccountId, + pub original_caller: MultisigMember, pub actual_call: Call, pub metadata: Option, pub fee_asset: FeeAsset, @@ -47,6 +48,25 @@ pub type MultisigOperationOf = MultisigOperation< BoundedVec::MaxMetadata>, >; +#[derive( + Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo, PartialEq, Eq, PartialOrd, Ord, +)] +pub enum MultisigMember { + AccountId(AccountId), + Nft(pallet_nft_origins::location::NftLocation), +} + +impl MultisigMember { + pub fn account(self) -> AccountId { + match self { + Self::AccountId(account_id) => account_id, + Self::Nft(nft_location) => nft_location.derive_account::(), + } + } +} + +pub type MultisigMemberOf = MultisigMember<::AccountId>; + impl Pallet where Result< @@ -59,16 +79,18 @@ where pub(crate) fn inner_token_mint( origin: OriginFor, amount: BalanceOf, - target: T::AccountId, + target: MultisigMemberOf, ) -> DispatchResult { let core_origin = ensure_multisig::>(origin)?; let core_id = core_origin.id; - T::AssetsProvider::mint_into(core_id, &target, amount)?; + let target_account = target.account(); + + T::AssetsProvider::mint_into(core_id, &target_account, amount)?; Self::deposit_event(Event::Minted { core_id, - target, + target: target_account, amount, }); @@ -79,14 +101,16 @@ where pub(crate) fn inner_token_burn( origin: OriginFor, amount: BalanceOf, - target: T::AccountId, + target: MultisigMemberOf, ) -> DispatchResult { let core_origin = ensure_multisig::>(origin)?; let core_id = core_origin.id; + let target_account = target.account(); + T::AssetsProvider::burn_from( core_id, - &target, + &target_account, amount, Precision::Exact, Fortitude::Polite, @@ -94,7 +118,7 @@ where Self::deposit_event(Event::Burned { core_id, - target, + target: target_account, amount, }); @@ -140,7 +164,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: MultisigMember::AccountId(owner), call_hash, call: *call, result: dispatch_result.map(|_| ()).map_err(|e| e.error), @@ -160,12 +184,12 @@ where owner_balance, Zero::zero(), BoundedBTreeMap::try_from(BTreeMap::from([( - owner.clone(), + MultisigMember::AccountId(owner.clone()), Vote::Aye(owner_balance), )])) .map_err(|_| Error::::MaxCallersExceeded)?, ), - original_caller: owner.clone(), + original_caller: MultisigMember::AccountId(owner.clone()), actual_call: bounded_call, metadata, fee_asset, @@ -190,15 +214,16 @@ where /// Vote on a multisig transaction that has not been executed yet pub(crate) fn inner_vote_multisig( - caller: OriginFor, + caller: MultisigMemberOf, core_id: T::CoreId, call_hash: T::Hash, aye: bool, ) -> DispatchResultWithPostInfo { Multisig::::try_mutate_exists(core_id, call_hash, |data| { - let owner = ensure_signed(caller.clone())?; + //let owner = ensure_signed(caller.clone())?; + let caller_account = caller.clone().account(); - let voter_balance: BalanceOf = T::AssetsProvider::balance(core_id, &owner); + let voter_balance: BalanceOf = T::AssetsProvider::balance(core_id, &caller_account); ensure!(!voter_balance.is_zero(), Error::::NoPermission); @@ -216,7 +241,7 @@ where old_data .tally - .process_vote(owner.clone(), Some(new_vote_record))?; + .process_vote(caller.clone(), Some(new_vote_record))?; let support = old_data.tally.support(core_id); let approval = old_data.tally.approval(core_id); @@ -242,7 +267,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: caller, call_hash, call: decoded_call, result: dispatch_result.map(|_| ()).map_err(|e| e.error), @@ -257,7 +282,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: caller, votes_added: new_vote_record, current_votes: old_data.tally, call_hash, @@ -279,7 +304,9 @@ where let mut old_data = data.take().ok_or(Error::::MultisigCallNotFound)?; - let old_vote = old_data.tally.process_vote(owner.clone(), None)?; + let old_vote = old_data + .tally + .process_vote(MultisigMember::AccountId(owner.clone()), None)?; *data = Some(old_data.clone()); diff --git a/INV4/pallet-inv4/src/voting.rs b/INV4/pallet-inv4/src/voting.rs index d2d24da8..8685055d 100644 --- a/INV4/pallet-inv4/src/voting.rs +++ b/INV4/pallet-inv4/src/voting.rs @@ -1,4 +1,7 @@ -use crate::{origin::INV4Origin, BalanceOf, Config, CoreStorage, Error, Multisig, Pallet}; +use crate::{ + multisig::MultisigMemberOf, origin::INV4Origin, BalanceOf, Config, CoreStorage, Error, + Multisig, Pallet, +}; use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use core::marker::PhantomData; use frame_support::{ @@ -33,7 +36,7 @@ pub type Core = ::CoreId; pub struct Tally { pub ayes: Votes, pub nays: Votes, - pub records: BoundedBTreeMap>, T::MaxCallers>, + pub records: BoundedBTreeMap, Vote>, T::MaxCallers>, dummy: PhantomData, } @@ -41,7 +44,7 @@ impl Tally { pub fn from_parts( ayes: Votes, nays: Votes, - records: BoundedBTreeMap>, T::MaxCallers>, + records: BoundedBTreeMap, Vote>, T::MaxCallers>, ) -> Self { Tally { ayes, @@ -53,7 +56,7 @@ impl Tally { pub fn process_vote( &mut self, - account: T::AccountId, + account: MultisigMemberOf, maybe_vote: Option>>, ) -> Result>, DispatchError> { let votes = if let Some(vote) = maybe_vote { diff --git a/pallet-nft-origins/src/lib.rs b/pallet-nft-origins/src/lib.rs index a84c523b..ff1b3c8e 100644 --- a/pallet-nft-origins/src/lib.rs +++ b/pallet-nft-origins/src/lib.rs @@ -204,5 +204,15 @@ pub mod pallet { Ok(()) } + + #[pallet::call_index(93)] + #[pallet::weight(1)] + pub fn test_dispatch_locally_as_nft( + _: OriginFor, + nft_location: NftLocation, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + (*call).dispatch(NftOrigin::Nft(nft_location).into()) + } } } diff --git a/pallet-nft-origins/src/location.rs b/pallet-nft-origins/src/location.rs index c8c91c83..042f69a5 100644 --- a/pallet-nft-origins/src/location.rs +++ b/pallet-nft-origins/src/location.rs @@ -7,7 +7,9 @@ use sp_io::hashing::blake2_256; use sp_runtime::traits::TrailingZeroInput; use xcm::latest::Junction; -#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] pub struct Parachain(pub u32); impl Parachain { @@ -26,7 +28,9 @@ impl Parachain { } } -#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] #[repr(u8)] pub enum Collection { /// Pallet based NFT collection @@ -37,7 +41,9 @@ pub enum Collection { Contract32([u8; 32]) = 2, } -#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] #[repr(u8)] pub enum Nft { /// U128 NFT id @@ -50,7 +56,9 @@ pub enum Nft { Key32([u8; 32]) = 3, } -#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] pub struct NftLocation { /// Chain where the collection and NFT originate pub chain: Parachain, From f90e870b77b4c40b89e67a65bf7b0fde72ebfbc5 Mon Sep 17 00:00:00 2001 From: Gabriel Facco de Arruda Date: Tue, 5 Sep 2023 16:24:48 -0300 Subject: [PATCH 6/7] feat: extrinsic functions for remaining nft calls --- INV4/pallet-inv4/src/fee_handling.rs | 5 ++- INV4/pallet-inv4/src/lib.rs | 61 ++++++++++++++++++++------ INV4/pallet-inv4/src/multisig.rs | 64 ++++++++++++++-------------- INV4/pallet-inv4/src/origin.rs | 30 ++++++++++++- 4 files changed, 111 insertions(+), 49 deletions(-) diff --git a/INV4/pallet-inv4/src/fee_handling.rs b/INV4/pallet-inv4/src/fee_handling.rs index 5a63cfcb..56876bab 100644 --- a/INV4/pallet-inv4/src/fee_handling.rs +++ b/INV4/pallet-inv4/src/fee_handling.rs @@ -11,9 +11,10 @@ use sp_runtime::{ }; #[derive(Clone, TypeInfo, Encode, Decode, MaxEncodedLen, Debug, PartialEq, Eq)] +#[repr(u8)] pub enum FeeAsset { - TNKR, - KSM, + TNKR = 0, + KSM = 1, } pub enum FeeAssetNegativeImbalance { diff --git a/INV4/pallet-inv4/src/lib.rs b/INV4/pallet-inv4/src/lib.rs index a18008e8..09e5f47a 100644 --- a/INV4/pallet-inv4/src/lib.rs +++ b/INV4/pallet-inv4/src/lib.rs @@ -52,6 +52,7 @@ pub mod pallet { use crate::{ fee_handling::MultisigFeeHandler, multisig::{MultisigMember, MultisigMemberOf}, + origin::{ensure_multisig_member_account_id, ensure_multisig_member_nft}, voting::{Tally, VoteRecord}, }; @@ -229,13 +230,13 @@ pub mod pallet { /// IP Tokens were minted Minted { core_id: T::CoreId, - target: T::AccountId, + target: MultisigMemberOf, amount: BalanceOf, }, /// IP Tokens were burned Burned { core_id: T::CoreId, - target: T::AccountId, + target: MultisigMemberOf, amount: BalanceOf, }, /// A vote to execute a call has begun. The call needs more votes to pass. @@ -244,7 +245,7 @@ pub mod pallet { MultisigVoteStarted { core_id: T::CoreId, executor_account: T::AccountId, - voter: T::AccountId, + voter: MultisigMemberOf, votes_added: VoteRecord, call_hash: T::Hash, }, @@ -262,7 +263,7 @@ pub mod pallet { MultisigVoteWithdrawn { core_id: T::CoreId, executor_account: T::AccountId, - voter: T::AccountId, + voter: MultisigMemberOf, votes_removed: VoteRecord, call_hash: T::Hash, }, @@ -382,25 +383,43 @@ pub mod pallet { ) )] pub fn operate_multisig( - caller: OriginFor, + origin: OriginFor, + core_id: T::CoreId, + metadata: Option>, + fee_asset: FeeAsset, + call: Box<::RuntimeCall>, + ) -> DispatchResultWithPostInfo { + let member = ensure_multisig_member_account_id::>(origin)?; + + Pallet::::inner_operate_multisig(member, core_id, metadata, fee_asset, call) + } + + #[pallet::call_index(12)] + #[pallet::weight(1)] + pub fn nft_operate_multisig( + origin: OriginFor, core_id: T::CoreId, metadata: Option>, fee_asset: FeeAsset, call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { - Pallet::::inner_operate_multisig(caller, core_id, metadata, fee_asset, call) + let member = ensure_multisig_member_nft::>(origin)?; + + Pallet::::inner_operate_multisig(member, core_id, metadata, fee_asset, call) } #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::vote_multisig())] pub fn vote_multisig( - caller: OriginFor, + origin: OriginFor, core_id: T::CoreId, call_hash: T::Hash, aye: bool, ) -> DispatchResultWithPostInfo { - let caller = MultisigMember::AccountId(ensure_signed(caller)?); - Pallet::::inner_vote_multisig(caller, core_id, call_hash, aye) + let member = ensure_multisig_member_account_id::>(origin)?; + //let caller = MultisigMember::AccountId(ensure_signed(caller)?); + + Pallet::::inner_vote_multisig(member, core_id, call_hash, aye) } #[pallet::call_index(10)] @@ -411,19 +430,35 @@ pub mod pallet { call_hash: T::Hash, aye: bool, ) -> DispatchResultWithPostInfo { - let caller = MultisigMember::Nft(pallet_nft_origins::origin::ensure_nft::>(origin)?); + let member = ensure_multisig_member_nft::>(origin)?; - Pallet::::inner_vote_multisig(caller, core_id, call_hash, aye) + //let caller = MultisigMember::Nft(pallet_nft_origins::origin::ensure_nft::>(origin)?); + + Pallet::::inner_vote_multisig(member, core_id, call_hash, aye) } #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::withdraw_vote_multisig())] pub fn withdraw_vote_multisig( - caller: OriginFor, + origin: OriginFor, + core_id: T::CoreId, + call_hash: T::Hash, + ) -> DispatchResultWithPostInfo { + let member = ensure_multisig_member_account_id::>(origin)?; + + Pallet::::inner_withdraw_vote_multisig(member, core_id, call_hash) + } + + #[pallet::call_index(11)] + #[pallet::weight(1)] + pub fn nft_withdraw_vote_multisig( + origin: OriginFor, core_id: T::CoreId, call_hash: T::Hash, ) -> DispatchResultWithPostInfo { - Pallet::::inner_withdraw_vote_multisig(caller, core_id, call_hash) + let member = ensure_multisig_member_nft::>(origin)?; + + Pallet::::inner_withdraw_vote_multisig(member, core_id, call_hash) } #[pallet::call_index(6)] diff --git a/INV4/pallet-inv4/src/multisig.rs b/INV4/pallet-inv4/src/multisig.rs index 77a72699..9437afea 100644 --- a/INV4/pallet-inv4/src/multisig.rs +++ b/INV4/pallet-inv4/src/multisig.rs @@ -19,7 +19,7 @@ use frame_support::{ }, BoundedBTreeMap, }; -use frame_system::{ensure_signed, pallet_prelude::*}; +use frame_system::pallet_prelude::*; use sp_runtime::{ traits::{Hash, Zero}, Perbill, @@ -84,13 +84,13 @@ where let core_origin = ensure_multisig::>(origin)?; let core_id = core_origin.id; - let target_account = target.account(); + let target_account = target.clone().account(); T::AssetsProvider::mint_into(core_id, &target_account, amount)?; Self::deposit_event(Event::Minted { core_id, - target: target_account, + target, amount, }); @@ -106,7 +106,7 @@ where let core_origin = ensure_multisig::>(origin)?; let core_id = core_origin.id; - let target_account = target.account(); + let target_account = target.clone().account(); T::AssetsProvider::burn_from( core_id, @@ -118,7 +118,7 @@ where Self::deposit_event(Event::Burned { core_id, - target: target_account, + target, amount, }); @@ -127,17 +127,19 @@ where /// Initiates a multisig transaction. If `caller` has enough votes, execute `call` immediately, otherwise a vote begins. pub(crate) fn inner_operate_multisig( - caller: OriginFor, + member: MultisigMemberOf, core_id: T::CoreId, metadata: Option>, fee_asset: FeeAsset, call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { - let owner = ensure_signed(caller)?; + //let owner = ensure_signed(caller)?; - let owner_balance: BalanceOf = T::AssetsProvider::balance(core_id, &owner); + let member_account = member.clone().account(); - ensure!(!owner_balance.is_zero(), Error::::NoPermission); + let member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member_account); + + ensure!(!member_balance.is_zero(), Error::::NoPermission); let (minimum_support, _) = Pallet::::minimum_support_and_required_approval(core_id) .ok_or(Error::::CoreNotFound)?; @@ -153,7 +155,7 @@ where ); // If `caller` has enough balance to meet/exeed the threshold, then go ahead and execute the `call` now. - if Perbill::from_rational(owner_balance, total_issuance) >= minimum_support { + if Perbill::from_rational(member_balance, total_issuance) >= minimum_support { let dispatch_result = crate::dispatch::dispatch_call::(core_id, &fee_asset, *call.clone()); @@ -164,7 +166,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: MultisigMember::AccountId(owner), + voter: member, call_hash, call: *call, result: dispatch_result.map(|_| ()).map_err(|e| e.error), @@ -181,15 +183,15 @@ where call_hash, MultisigOperation { tally: Tally::from_parts( - owner_balance, + member_balance, Zero::zero(), BoundedBTreeMap::try_from(BTreeMap::from([( - MultisigMember::AccountId(owner.clone()), - Vote::Aye(owner_balance), + member.clone(), + Vote::Aye(member_balance), )])) .map_err(|_| Error::::MaxCallersExceeded)?, ), - original_caller: MultisigMember::AccountId(owner.clone()), + original_caller: member.clone(), actual_call: bounded_call, metadata, fee_asset, @@ -203,8 +205,8 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, - votes_added: Vote::Aye(owner_balance), + voter: member, + votes_added: Vote::Aye(member_balance), call_hash, }); } @@ -214,18 +216,18 @@ where /// Vote on a multisig transaction that has not been executed yet pub(crate) fn inner_vote_multisig( - caller: MultisigMemberOf, + member: MultisigMemberOf, core_id: T::CoreId, call_hash: T::Hash, aye: bool, ) -> DispatchResultWithPostInfo { Multisig::::try_mutate_exists(core_id, call_hash, |data| { //let owner = ensure_signed(caller.clone())?; - let caller_account = caller.clone().account(); + let member_account = member.clone().account(); - let voter_balance: BalanceOf = T::AssetsProvider::balance(core_id, &caller_account); + let member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member_account); - ensure!(!voter_balance.is_zero(), Error::::NoPermission); + ensure!(!member_balance.is_zero(), Error::::NoPermission); let mut old_data = data.take().ok_or(Error::::MultisigCallNotFound)?; @@ -234,14 +236,14 @@ where .ok_or(Error::::CoreNotFound)?; let new_vote_record = if aye { - Vote::Aye(voter_balance) + Vote::Aye(member_balance) } else { - Vote::Nay(voter_balance) + Vote::Nay(member_balance) }; old_data .tally - .process_vote(caller.clone(), Some(new_vote_record))?; + .process_vote(member.clone(), Some(new_vote_record))?; let support = old_data.tally.support(core_id); let approval = old_data.tally.approval(core_id); @@ -267,7 +269,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: caller, + voter: member, call_hash, call: decoded_call, result: dispatch_result.map(|_| ()).map_err(|e| e.error), @@ -282,7 +284,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: caller, + voter: member, votes_added: new_vote_record, current_votes: old_data.tally, call_hash, @@ -295,18 +297,16 @@ where /// Withdraw vote from an ongoing multisig operation pub(crate) fn inner_withdraw_vote_multisig( - caller: OriginFor, + member: MultisigMemberOf, core_id: T::CoreId, call_hash: T::Hash, ) -> DispatchResultWithPostInfo { Multisig::::try_mutate_exists(core_id, call_hash, |data| { - let owner = ensure_signed(caller.clone())?; + //let owner = ensure_signed(caller.clone())?; let mut old_data = data.take().ok_or(Error::::MultisigCallNotFound)?; - let old_vote = old_data - .tally - .process_vote(MultisigMember::AccountId(owner.clone()), None)?; + let old_vote = old_data.tally.process_vote(member.clone(), None)?; *data = Some(old_data.clone()); @@ -317,7 +317,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: member, votes_removed: old_vote, call_hash, }); diff --git a/INV4/pallet-inv4/src/origin.rs b/INV4/pallet-inv4/src/origin.rs index b8ecbd7b..5054eb06 100644 --- a/INV4/pallet-inv4/src/origin.rs +++ b/INV4/pallet-inv4/src/origin.rs @@ -1,11 +1,11 @@ -use core::marker::PhantomData; - use crate::{ + multisig::{MultisigMember, MultisigMemberOf}, pallet::{self, Origin}, util::derive_core_account, Config, }; use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; use frame_support::{error::BadOrigin, RuntimeDebug}; use scale_info::TypeInfo; use sp_runtime::traits::AtLeast32BitUnsigned; @@ -65,3 +65,29 @@ where _ => Err(BadOrigin), } } + +pub fn ensure_multisig_member_account_id( + o: OuterOrigin, +) -> Result, BadOrigin> +where + OuterOrigin: Into, OuterOrigin>>, +{ + if let Ok(frame_system::Origin::::Signed(account_id)) = o.into() { + Ok(MultisigMember::<::AccountId>::AccountId(account_id)) + } else { + Err(BadOrigin) + } +} + +pub fn ensure_multisig_member_nft( + o: OuterOrigin, +) -> Result, BadOrigin> +where + OuterOrigin: Into>, +{ + if let Ok(pallet_nft_origins::Origin::Nft(nft)) = o.into() { + Ok(MultisigMember::<::AccountId>::Nft(nft)) + } else { + Err(BadOrigin) + } +} From cdc7cece4c59aecd1d0161fa6e4a11c75e6e1ac3 Mon Sep 17 00:00:00 2001 From: Gabriel Facco de Arruda Date: Mon, 18 Sep 2023 13:38:14 +0100 Subject: [PATCH 7/7] feat: New core assets pallet forked from ORML's tokens pallet --- Cargo.toml | 1 + INV4/pallet-inv4/src/inv4_core.rs | 33 +- INV4/pallet-inv4/src/lib.rs | 22 +- INV4/pallet-inv4/src/multisig.rs | 41 +- pallet-core-assets/Cargo.toml | 45 ++ pallet-core-assets/LICENSE | 201 ++++++++ pallet-core-assets/README.md | 1 + pallet-core-assets/src/lib.rs | 816 ++++++++++++++++++++++++++++++ pallet-core-assets/src/weights.rs | 66 +++ 9 files changed, 1201 insertions(+), 25 deletions(-) create mode 100644 pallet-core-assets/Cargo.toml create mode 100644 pallet-core-assets/LICENSE create mode 100644 pallet-core-assets/README.md create mode 100644 pallet-core-assets/src/lib.rs create mode 100644 pallet-core-assets/src/weights.rs diff --git a/Cargo.toml b/Cargo.toml index d0b1da23..943f97b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ members = [ "pallet-checked-inflation", "pallet-rings", "pallet-nft-origins", + "pallet-core-assets", ] diff --git a/INV4/pallet-inv4/src/inv4_core.rs b/INV4/pallet-inv4/src/inv4_core.rs index ee08d99e..d7047e65 100644 --- a/INV4/pallet-inv4/src/inv4_core.rs +++ b/INV4/pallet-inv4/src/inv4_core.rs @@ -1,6 +1,7 @@ use super::pallet::*; use crate::{ fee_handling::{FeeAsset, FeeAssetNegativeImbalance, MultisigFeeHandler}, + multisig::{MultisigMember, MultisigMemberOf}, origin::{ensure_multisig, INV4Origin}, util::derive_core_account, }; @@ -54,7 +55,11 @@ where let seed_balance = ::CoreSeedBalance::get(); - T::AssetsProvider::mint_into(current_id, &creator, seed_balance)?; + T::AssetsProvider::mint_into( + current_id, + &MultisigMember::AccountId(creator.clone()), + seed_balance, + )?; let info = CoreInfo { account: core_account.clone(), @@ -143,6 +148,32 @@ where }) } + pub(crate) fn inner_set_frozen(origin: OriginFor, frozen: bool) -> DispatchResult { + let core_origin = ensure_multisig::>(origin)?; + let core_id = core_origin.id; + + if frozen { + , + >>::set_freeze( + core_id, + // None of the other arguments matter, the implementation expects set_freeze to freeze the whole asset. + &(), + &MultisigMember::AccountId(core_origin.to_account_id()), + Default::default(), + ) + } else { + , + >>::thaw( + core_id, + // None of the other arguments matter, the implementation expects thaw to thaw the whole asset. + &(), + &MultisigMember::AccountId(core_origin.to_account_id()), + ) + } + } + pub fn is_asset_frozen(core_id: T::CoreId) -> Option { CoreStorage::::get(core_id).map(|c| c.frozen_tokens) } diff --git a/INV4/pallet-inv4/src/lib.rs b/INV4/pallet-inv4/src/lib.rs index 09e5f47a..3db578eb 100644 --- a/INV4/pallet-inv4/src/lib.rs +++ b/INV4/pallet-inv4/src/lib.rs @@ -51,7 +51,7 @@ pub mod pallet { use crate::{ fee_handling::MultisigFeeHandler, - multisig::{MultisigMember, MultisigMemberOf}, + multisig::MultisigMemberOf, origin::{ensure_multisig_member_account_id, ensure_multisig_member_nft}, voting::{Tally, VoteRecord}, }; @@ -146,8 +146,13 @@ pub mod pallet { #[pallet::constant] type KSMAssetId: Get<<::Tokens as Inspect<::AccountId>>::AssetId>; - type AssetsProvider: fungibles::Inspect, AssetId = Self::CoreId> - + fungibles::Mutate; // + fungibles::Transfer; + type AssetsProvider: fungibles::Inspect< + MultisigMemberOf, + Balance = BalanceOf, + AssetId = Self::CoreId, + > + fungibles::Mutate, AssetId = Self::CoreId> + + fungibles::InspectFreeze, Id = ()> + + fungibles::MutateFreeze, AssetId = Self::CoreId>; type Tokens: Balanced + Inspect; @@ -207,7 +212,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn core_members)] pub type CoreMembers = - StorageDoubleMap<_, Blake2_128Concat, T::CoreId, Blake2_128Concat, T::AccountId, ()>; + StorageDoubleMap<_, Blake2_128Concat, T::CoreId, Blake2_128Concat, MultisigMemberOf, ()>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] @@ -483,5 +488,14 @@ pub mod pallet { ) -> DispatchResult { Pallet::::inner_set_parameters(origin, metadata, minimum_support, required_approval, frozen_tokens) } + + #[pallet::call_index(13)] + #[pallet::weight(1)] + pub fn set_frozen( + origin: OriginFor, + frozen: bool, + ) -> DispatchResult { + Pallet::::inner_set_frozen(origin, frozen) + } } } diff --git a/INV4/pallet-inv4/src/multisig.rs b/INV4/pallet-inv4/src/multisig.rs index 9437afea..858cf3f9 100644 --- a/INV4/pallet-inv4/src/multisig.rs +++ b/INV4/pallet-inv4/src/multisig.rs @@ -21,7 +21,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use sp_runtime::{ - traits::{Hash, Zero}, + traits::{Hash, IdentifyAccount, Zero}, Perbill, }; use sp_std::{boxed::Box, collections::btree_map::BTreeMap}; @@ -56,6 +56,20 @@ pub enum MultisigMember { Nft(pallet_nft_origins::location::NftLocation), } +impl From for MultisigMember { + fn from(a: AccountId) -> Self { + Self::AccountId(a) + } +} + +impl IdentifyAccount for MultisigMember { + type AccountId = AccountId; + + fn into_account(self) -> AccountId { + self.account() + } +} + impl MultisigMember { pub fn account(self) -> AccountId { match self { @@ -84,9 +98,7 @@ where let core_origin = ensure_multisig::>(origin)?; let core_id = core_origin.id; - let target_account = target.clone().account(); - - T::AssetsProvider::mint_into(core_id, &target_account, amount)?; + T::AssetsProvider::mint_into(core_id, &target, amount)?; Self::deposit_event(Event::Minted { core_id, @@ -106,11 +118,9 @@ where let core_origin = ensure_multisig::>(origin)?; let core_id = core_origin.id; - let target_account = target.clone().account(); - T::AssetsProvider::burn_from( core_id, - &target_account, + &target, amount, Precision::Exact, Fortitude::Polite, @@ -133,11 +143,7 @@ where fee_asset: FeeAsset, call: Box<::RuntimeCall>, ) -> DispatchResultWithPostInfo { - //let owner = ensure_signed(caller)?; - - let member_account = member.clone().account(); - - let member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member_account); + let member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member); ensure!(!member_balance.is_zero(), Error::::NoPermission); @@ -222,10 +228,7 @@ where aye: bool, ) -> DispatchResultWithPostInfo { Multisig::::try_mutate_exists(core_id, call_hash, |data| { - //let owner = ensure_signed(caller.clone())?; - let member_account = member.clone().account(); - - let member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member_account); + let member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member); ensure!(!member_balance.is_zero(), Error::::NoPermission); @@ -302,8 +305,6 @@ where call_hash: T::Hash, ) -> DispatchResultWithPostInfo { Multisig::::try_mutate_exists(core_id, call_hash, |data| { - //let owner = ensure_signed(caller.clone())?; - let mut old_data = data.take().ok_or(Error::::MultisigCallNotFound)?; let old_vote = old_data.tally.process_vote(member.clone(), None)?; @@ -340,11 +341,11 @@ where Ok(().into()) } - pub fn add_member(core_id: &T::CoreId, member: &T::AccountId) { + pub fn add_member(core_id: &T::CoreId, member: &MultisigMemberOf) { CoreMembers::::insert(core_id, member, ()) } - pub fn remove_member(core_id: &T::CoreId, member: &T::AccountId) { + pub fn remove_member(core_id: &T::CoreId, member: &MultisigMemberOf) { CoreMembers::::remove(core_id, member) } } diff --git a/pallet-core-assets/Cargo.toml b/pallet-core-assets/Cargo.toml new file mode 100644 index 00000000..e929495c --- /dev/null +++ b/pallet-core-assets/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "pallet-core-assets" +license = "Apache-2.0" +version = "0.0.1" +edition = "2021" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } + +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.43" } + +num-traits = { version = "0.2.14", default-features = false } + +[dev-dependencies] +pallet-elections-phragmen = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +pallet-treasury = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/pallet-core-assets/LICENSE b/pallet-core-assets/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/pallet-core-assets/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pallet-core-assets/README.md b/pallet-core-assets/README.md new file mode 100644 index 00000000..3e0526f5 --- /dev/null +++ b/pallet-core-assets/README.md @@ -0,0 +1 @@ +## Forked from [ORML's Token module](https://github.com/open-web3-stack/open-runtime-module-library/tree/master/tokens) diff --git a/pallet-core-assets/src/lib.rs b/pallet-core-assets/src/lib.rs new file mode 100644 index 00000000..2e91d953 --- /dev/null +++ b/pallet-core-assets/src/lib.rs @@ -0,0 +1,816 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::unused_unit)] +#![allow(clippy::comparison_chain)] + +use codec::MaxEncodedLen; +use frame_support::{ + ensure, + pallet_prelude::*, + traits::{ + tokens::{ + fungibles, DepositConsequence, Fortitude, Precision, Preservation, Provenance, + WithdrawConsequence, + }, + ConstBool, DefensiveSaturating, + }, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{ + AtLeast32BitUnsigned, CheckedAdd, CheckedSub, IdentifyAccount, MaybeSerializeDeserialize, + Member, Saturating, StaticLookup, Zero, + }, + ArithmeticError, DispatchError, DispatchResult, FixedPointOperand, TokenError, +}; +use sp_std::prelude::*; + +mod weights; +pub use weights::WeightInfo; + +pub use module::*; + +#[frame_support::pallet] +pub mod module { + + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type AccountId: Parameter + + Member + + Ord + + MaxEncodedLen + + From<::AccountId> + + IdentifyAccount::AccountId>; + + type Lookup: sp_runtime::traits::StaticLookup::AccountId>; + + /// The balance type + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Default + + Copy + + MaybeSerializeDeserialize + + MaxEncodedLen + + FixedPointOperand; + + /// The currency ID type + type CurrencyId: Parameter + + Member + + Copy + + MaybeSerializeDeserialize + + Ord + + TypeInfo + + MaxEncodedLen; + + /// Weight information for extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The balance is too low + BalanceTooLow, + /// Cannot convert Amount into Balance type + AmountIntoBalanceFailed, + /// Failed because liquidity restrictions due to locking + LiquidityRestrictions, + /// Transfer/payment would kill account + KeepAlive, + /// Beneficiary account must pre-exist + DeadAccount, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// An account was created with some free balance. + Endowed { + currency_id: T::CurrencyId, + who: ::AccountId, + amount: T::Balance, + }, + /// Transfer succeeded. + Transfer { + currency_id: T::CurrencyId, + from: ::AccountId, + to: ::AccountId, + amount: T::Balance, + }, + /// A balance was set by root. + BalanceSet { + currency_id: T::CurrencyId, + who: ::AccountId, + balance: T::Balance, + }, + /// The total issuance of an currency has been set + TotalIssuanceSet { + currency_id: T::CurrencyId, + amount: T::Balance, + }, + /// Some balances were withdrawn (e.g. pay for transaction fee) + Withdrawn { + currency_id: T::CurrencyId, + who: ::AccountId, + amount: T::Balance, + }, + /// Some balances were slashed (e.g. due to mis-behavior) + Slashed { + currency_id: T::CurrencyId, + who: ::AccountId, + free_amount: T::Balance, + reserved_amount: T::Balance, + }, + /// Deposited some balance into an account + Deposited { + currency_id: T::CurrencyId, + who: ::AccountId, + amount: T::Balance, + }, + } + + /// The total issuance of a token type. + #[pallet::storage] + #[pallet::getter(fn total_issuance)] + pub type TotalIssuance = + StorageMap<_, Twox64Concat, T::CurrencyId, T::Balance, ValueQuery>; + + /// The balance of a token type under an account. + /// + /// NOTE: If the total is ever zero, decrease account ref account. + /// + /// NOTE: This is only used in the case that this module is used to store + /// balances. + #[pallet::storage] + #[pallet::getter(fn accounts)] + pub type Accounts = StorageDoubleMap< + _, + Blake2_128Concat, + ::AccountId, + Twox64Concat, + T::CurrencyId, + T::Balance, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn accounts_by_currency)] + pub type AccountsByCurrency = StorageDoubleMap< + _, + Blake2_128Concat, + T::CurrencyId, + Blake2_128Concat, + ::AccountId, + (), + >; + + #[pallet::storage] + #[pallet::getter(fn is_frozen)] + pub type Frozen = + StorageMap<_, Twox64Concat, T::CurrencyId, bool, ValueQuery, ConstBool>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// Transfer some liquid free balance to another account. + /// + /// `transfer` will set the `FreeBalance` of the sender and receiver. + /// It will decrease the total issuance of the system by the + /// `TransferFee`. If the sender's account is below the existential + /// deposit as a result of the transfer, the account will be reaped. + /// + /// The dispatch origin for this call must be `Signed` by the + /// transactor. + /// + /// - `dest`: The recipient of the transfer. + /// - `currency_id`: currency type. + /// - `amount`: free balance amount to tranfer. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::transfer())] + pub fn transfer( + origin: OriginFor, + dest: <::Lookup as StaticLookup>::Source, + currency_id: T::CurrencyId, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + let from = ensure_signed(origin)?; + let to = ::Lookup::lookup(dest)?; + Self::do_transfer( + currency_id, + &::AccountId::from(from), + &to, + amount, + ) + } + + /// Transfer all remaining balance to the given account. + /// + /// NOTE: This function only attempts to transfer _transferable_ + /// balances. This means that any locked, reserved, or existential + /// deposits (when `keep_alive` is `true`), will not be transferred by + /// this function. To ensure that this function results in a killed + /// account, you might need to prepare the account by removing any + /// reference counters, storage deposits, etc... + /// + /// The dispatch origin for this call must be `Signed` by the + /// transactor. + /// + /// - `dest`: The recipient of the transfer. + /// - `currency_id`: currency type. + /// - `keep_alive`: A boolean to determine if the `transfer_all` + /// operation should send all of the funds the account has, causing + /// the sender account to be killed (false), or transfer everything + /// except at least the existential deposit, which will guarantee to + /// keep the sender account alive (true). + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::transfer_all())] + pub fn transfer_all( + origin: OriginFor, + dest: <::Lookup as StaticLookup>::Source, + currency_id: T::CurrencyId, + ) -> DispatchResult { + let from = ensure_signed(origin)?; + let to = ::Lookup::lookup(dest)?; + let preservation = Preservation::Expendable; + + let from = ::AccountId::from(from); + + let reducible_balance = ::AccountId, + >>::reducible_balance( + currency_id, &from, preservation, Fortitude::Polite + ); + >::transfer( + currency_id, + &from, + &to, + reducible_balance, + preservation, + ) + .map(|_| ()) + } + + /// Exactly as `transfer`, except the origin must be root and the source + /// account may be specified. + /// + /// The dispatch origin for this call must be _Root_. + /// + /// - `source`: The sender of the transfer. + /// - `dest`: The recipient of the transfer. + /// - `currency_id`: currency type. + /// - `amount`: free balance amount to tranfer. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::force_transfer())] + pub fn force_transfer( + origin: OriginFor, + source: <::Lookup as StaticLookup>::Source, + dest: <::Lookup as StaticLookup>::Source, + currency_id: T::CurrencyId, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let from = ::Lookup::lookup(source)?; + let to = ::Lookup::lookup(dest)?; + Self::do_transfer(currency_id, &from, &to, amount) + } + + /// Set the balances of a given account. + /// + /// This will alter `FreeBalance` and `ReservedBalance` in storage. it + /// will also decrease the total issuance of the system + /// (`TotalIssuance`). If the new free or reserved balance is below the + /// existential deposit, it will reap the `AccountInfo`. + /// + /// The dispatch origin for this call is `root`. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::set_balance())] + pub fn set_balance( + origin: OriginFor, + who: <::Lookup as StaticLookup>::Source, + currency_id: T::CurrencyId, + #[pallet::compact] new_balance: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let who = ::Lookup::lookup(who)?; + + Self::try_mutate_account(&who, currency_id, |account, _| -> DispatchResult { + let old_balance = account.clone(); + + *account = new_balance; + + if new_balance > old_balance { + TotalIssuance::::try_mutate(currency_id, |t| -> DispatchResult { + *t = t + .checked_add(&(new_balance.defensive_saturating_sub(old_balance))) + .ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + } else if new_balance < old_balance { + TotalIssuance::::try_mutate(currency_id, |t| -> DispatchResult { + *t = t + .checked_sub(&(old_balance.defensive_saturating_sub(new_balance))) + .ok_or(ArithmeticError::Underflow)?; + Ok(()) + })?; + } + + Self::deposit_event(Event::BalanceSet { + currency_id, + who: who.clone(), + balance: new_balance, + }); + Ok(()) + })?; + + Ok(()) + } + } +} + +impl Pallet { + pub(crate) fn deposit_consequence( + currency_id: T::CurrencyId, + amount: T::Balance, + balance: &T::Balance, + ) -> DepositConsequence { + if amount.is_zero() { + return DepositConsequence::Success; + } + + if TotalIssuance::::get(currency_id) + .checked_add(&amount) + .is_none() + { + return DepositConsequence::Overflow; + } + + match balance.checked_add(&amount) { + Some(x) => x, + None => return DepositConsequence::Overflow, + }; + + DepositConsequence::Success + } + + pub(crate) fn withdraw_consequence( + currency_id: T::CurrencyId, + amount: T::Balance, + balance: &T::Balance, + ) -> WithdrawConsequence { + if amount.is_zero() { + return WithdrawConsequence::Success; + } + + if TotalIssuance::::get(currency_id) + .checked_sub(&amount) + .is_none() + { + return WithdrawConsequence::Underflow; + } + + match balance.checked_sub(&amount) { + Some(x) => x, + None => return WithdrawConsequence::BalanceLow, + }; + + WithdrawConsequence::Success + } + + // Ensure that an account can withdraw from their free balance given any + // existing withdrawal restrictions like locks and vesting balance. + // Is a no-op if amount to be withdrawn is zero. + pub(crate) fn ensure_can_withdraw( + currency_id: T::CurrencyId, + who: &::AccountId, + amount: T::Balance, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + + Self::free_balance(currency_id, who) + .checked_sub(&amount) + .ok_or(Error::::BalanceTooLow)?; + + Ok(()) + } + + pub(crate) fn try_mutate_account( + who: &::AccountId, + currency_id: T::CurrencyId, + f: impl FnOnce(&mut T::Balance, bool) -> sp_std::result::Result, + ) -> sp_std::result::Result { + Accounts::::try_mutate_exists(who, currency_id, |maybe_account| { + let existed = maybe_account.is_some(); + let mut balance = maybe_account.take().unwrap_or_default(); + f(&mut balance, existed).map(move |result| { + let maybe_endowed = if !existed { Some(balance) } else { None }; + *maybe_account = Some(balance); + + (maybe_endowed, existed, maybe_account.is_some(), result) + }) + }) + .map(|(maybe_endowed, existed, exists, result)| { + if existed && !exists { + AccountsByCurrency::::remove(currency_id, who.clone()); + } else if !existed && exists { + AccountsByCurrency::::insert(currency_id, who.clone(), ()); + } + + if let Some(endowed) = maybe_endowed { + Self::deposit_event(Event::Endowed { + currency_id, + who: who.clone(), + amount: endowed, + }); + } + + result + }) + } + + /// Transfer some free balance from `from` to `to`. Ensure from_account + /// allow death or new balance will not be reaped, and ensure + /// to_account will not be removed dust. + /// + /// Is a no-op if value to be transferred is zero or the `from` is the same + /// as `to`. + pub(crate) fn do_transfer( + currency_id: T::CurrencyId, + from: &::AccountId, + to: &::AccountId, + amount: T::Balance, + ) -> DispatchResult { + if amount.is_zero() || from == to { + return Ok(()); + } + + if Frozen::::get(currency_id) { + return Err(sp_runtime::DispatchError::Token( + sp_runtime::TokenError::Frozen, + )); + } + + Self::try_mutate_account(to, currency_id, |to_account, _existed| -> DispatchResult { + Self::try_mutate_account( + from, + currency_id, + |from_account, _existed| -> DispatchResult { + *from_account = from_account + .checked_sub(&amount) + .ok_or(Error::::BalanceTooLow)?; + *to_account = to_account + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + + Self::ensure_can_withdraw(currency_id, from, amount)?; + + Ok(()) + }, + )?; + Ok(()) + })?; + + Self::deposit_event(Event::Transfer { + currency_id, + from: from.clone(), + to: to.clone(), + amount, + }); + Ok(()) + } + + /// Withdraw some free balance from an account, respecting existence + /// requirements. + /// + /// `change_total_issuance`: + /// - true, decrease the total issuance by burned amount. + /// - false, do not update the total issuance. + /// + /// Is a no-op if value to be withdrawn is zero. + pub(crate) fn do_withdraw( + currency_id: T::CurrencyId, + who: &::AccountId, + amount: T::Balance, + change_total_issuance: bool, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + + Self::try_mutate_account(who, currency_id, |account, _existed| -> DispatchResult { + Self::ensure_can_withdraw(currency_id, who, amount)?; + + *account = account.defensive_saturating_sub(amount); + + if change_total_issuance { + TotalIssuance::::mutate(currency_id, |v| { + *v = v.defensive_saturating_sub(amount) + }); + } + + Self::deposit_event(Event::Withdrawn { + currency_id, + who: who.clone(), + amount, + }); + Ok(()) + })?; + + Ok(()) + } + + /// Deposit some `value` into the free balance of `who`. + /// + /// `require_existed`: + /// - true, the account must already exist, do not require ED. + /// - false, possibly creating a new account, require ED if the account does + /// not yet exist, but except this account is in the dust removal + /// whitelist. + /// + /// `change_total_issuance`: + /// - true, increase the issued amount to total issuance. + /// - false, do not update the total issuance. + pub(crate) fn do_deposit( + currency_id: T::CurrencyId, + who: &::AccountId, + amount: T::Balance, + require_existed: bool, + change_total_issuance: bool, + ) -> Result { + if amount.is_zero() { + return Ok(amount); + } + + Self::try_mutate_account(who, currency_id, |account, existed| -> DispatchResult { + if require_existed { + ensure!(existed, Error::::DeadAccount); + } + + let new_total_issuance = Self::total_issuance(currency_id) + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + if change_total_issuance { + TotalIssuance::::mutate(currency_id, |v| *v = new_total_issuance); + } + *account = account.defensive_saturating_add(amount); + Ok(()) + })?; + + Self::deposit_event(Event::Deposited { + currency_id, + who: who.clone(), + amount, + }); + Ok(amount) + } + + pub(crate) fn free_balance( + currency_id: T::CurrencyId, + who: &::AccountId, + ) -> T::Balance { + Self::accounts(who, currency_id) + } + + pub fn freeze_currency(currency_id: T::CurrencyId) { + Frozen::::insert(currency_id, true) + } + + pub fn unfreeze_currency(currency_id: T::CurrencyId) { + Frozen::::insert(currency_id, false) + } +} + +impl fungibles::Inspect<::AccountId> for Pallet { + type AssetId = T::CurrencyId; + type Balance = T::Balance; + + fn total_issuance(asset_id: Self::AssetId) -> Self::Balance { + Self::total_issuance(asset_id) + } + + fn minimum_balance(_asset_id: Self::AssetId) -> Self::Balance { + Self::Balance::zero() + } + + fn balance(asset_id: Self::AssetId, who: &::AccountId) -> Self::Balance { + Self::accounts(who, asset_id) + } + + fn total_balance( + asset_id: Self::AssetId, + who: &::AccountId, + ) -> Self::Balance { + Self::accounts(who, asset_id) + } + + fn reducible_balance( + asset_id: Self::AssetId, + who: &::AccountId, + _preservation: Preservation, + _force: Fortitude, + ) -> Self::Balance { + Self::accounts(who, asset_id) + } + + fn can_deposit( + asset_id: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + _provenance: Provenance, + ) -> DepositConsequence { + Self::deposit_consequence(asset_id, amount, &Self::accounts(who, asset_id)) + } + + fn can_withdraw( + asset_id: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + Self::withdraw_consequence(asset_id, amount, &Self::accounts(who, asset_id)) + } + + fn asset_exists(asset: Self::AssetId) -> bool { + TotalIssuance::::contains_key(asset) + } +} + +impl fungibles::InspectFreeze<::AccountId> for Pallet { + type Id = (); + + fn balance_frozen( + asset: Self::AssetId, + _id: &Self::Id, + _who: &::AccountId, + ) -> Self::Balance { + if Frozen::::get(asset) { + TotalIssuance::::get(asset) + } else { + Zero::zero() + } + } + + fn can_freeze( + asset: Self::AssetId, + _id: &Self::Id, + _who: &::AccountId, + ) -> bool { + Frozen::::get(asset) + } +} + +impl fungibles::MutateFreeze<::AccountId> for Pallet { + fn set_freeze( + asset: Self::AssetId, + _id: &Self::Id, + _who: &::AccountId, + _amount: Self::Balance, + ) -> DispatchResult { + Self::freeze_currency(asset); + + Ok(()) + } + + fn extend_freeze( + _asset: Self::AssetId, + _id: &Self::Id, + _who: &::AccountId, + _amount: Self::Balance, + ) -> DispatchResult { + Ok(()) + } + + fn thaw( + asset: Self::AssetId, + _id: &Self::Id, + _who: &::AccountId, + ) -> DispatchResult { + Self::unfreeze_currency(asset); + + Ok(()) + } +} + +impl fungibles::Mutate<::AccountId> for Pallet { + fn mint_into( + asset_id: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> Result { + Self::deposit_consequence(asset_id, amount, &Self::accounts(who, asset_id)) + .into_result()?; + // do not require existing + Self::do_deposit(asset_id, who, amount, false, true) + } + + fn burn_from( + asset_id: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + // TODO: Respect precision + _precision: Precision, + // TODO: Respect fortitude + _fortitude: Fortitude, + ) -> Result { + let extra = Self::withdraw_consequence(asset_id, amount, &Self::accounts(who, asset_id)) + .into_result(false)?; + let actual = amount.defensive_saturating_add(extra); + // allow death + Self::do_withdraw(asset_id, who, actual, true).map(|_| actual) + } + + fn transfer( + asset_id: Self::AssetId, + source: &::AccountId, + dest: &::AccountId, + amount: T::Balance, + _preservation: Preservation, + ) -> Result { + Self::do_transfer(asset_id, source, dest, amount).map(|_| amount) + } +} + +impl fungibles::Unbalanced<::AccountId> for Pallet { + fn handle_dust(_dust: fungibles::Dust<::AccountId, Self>) { + // Dust is handled in account mutate method + } + + fn write_balance( + asset_id: Self::AssetId, + who: &::AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + let max_reduction = >::reducible_balance( + asset_id, + who, + Preservation::Expendable, + Fortitude::Force, + ); + + // Balance is the same type and will not overflow + Self::try_mutate_account(who, asset_id, |account, _| -> Result<(), DispatchError> { + // Make sure the reduction (if there is one) is no more than the maximum + // allowed. + let reduction = account.saturating_sub(amount); + ensure!(reduction <= max_reduction, Error::::BalanceTooLow); + + *account = amount; + Self::deposit_event(Event::BalanceSet { + currency_id: asset_id, + who: who.clone(), + balance: *account, + }); + + Ok(()) + })?; + + Ok(None) + } + + fn set_total_issuance(asset_id: Self::AssetId, amount: Self::Balance) { + // Balance is the same type and will not overflow + TotalIssuance::::mutate(asset_id, |t| *t = amount); + + Self::deposit_event(Event::TotalIssuanceSet { + currency_id: asset_id, + amount, + }); + } + + fn decrease_balance( + asset: Self::AssetId, + who: &::AccountId, + mut amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + let old_balance = + as fungibles::Inspect<::AccountId>>::balance( + asset, who, + ); + let free = + as fungibles::Inspect<::AccountId>>::reducible_balance( + asset, + who, + preservation, + force, + ); + if let Precision::BestEffort = precision { + amount = amount.min(free); + } + let new_balance = old_balance + .checked_sub(&amount) + .ok_or(TokenError::FundsUnavailable)?; + let _dust_amount = Self::write_balance(asset, who, new_balance)?.unwrap_or_default(); + + // here just return decrease amount, shouldn't count the dust_amount + Ok(old_balance.saturating_sub(new_balance)) + } +} diff --git a/pallet-core-assets/src/weights.rs b/pallet-core-assets/src/weights.rs new file mode 100644 index 00000000..8f563716 --- /dev/null +++ b/pallet-core-assets/src/weights.rs @@ -0,0 +1,66 @@ +//! Autogenerated weights for orml_tokens +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-09-14, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// /Users/ermal/Acala/target/release/acala +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=orml_tokens +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --template=../templates/orml-weight-template.hbs +// --output=./tokens/src/weights.rs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for orml_tokens. +pub trait WeightInfo { + fn transfer() -> Weight; + fn transfer_all() -> Weight; + fn transfer_keep_alive() -> Weight; + fn force_transfer() -> Weight; + fn set_balance() -> Weight; +} + +/// Default weights. +impl WeightInfo for () { + fn transfer() -> Weight { + Weight::from_parts(69_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn transfer_all() -> Weight { + Weight::from_parts(69_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(5 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn transfer_keep_alive() -> Weight { + Weight::from_parts(38_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn force_transfer() -> Weight { + Weight::from_parts(45_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn set_balance() -> Weight { + Weight::from_parts(34_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } +}