diff --git a/Cargo.toml b/Cargo.toml index e00d1e58..943f97b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,7 @@ members = [ "INV4/pallet-inv4", "OCIF/staking", "pallet-checked-inflation", - "pallet-rings" + "pallet-rings", + "pallet-nft-origins", + "pallet-core-assets", ] 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..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 { @@ -43,7 +44,7 @@ pub trait MultisigFeeHandler { fn handle_creation_fee( imbalance: FeeAssetNegativeImbalance< - >::NegativeImbalance, + <::Currency as Currency>::NegativeImbalance, Credit, >, ); 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 f5073e09..3db578eb 100644 --- a/INV4/pallet-inv4/src/lib.rs +++ b/INV4/pallet-inv4/src/lib.rs @@ -51,6 +51,8 @@ pub mod pallet { use crate::{ fee_handling::MultisigFeeHandler, + multisig::MultisigMemberOf, + origin::{ensure_multisig_member_account_id, ensure_multisig_member_nft}, voting::{Tally, VoteRecord}, }; @@ -87,7 +89,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 @@ -142,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; @@ -203,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)] @@ -226,13 +235,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. @@ -241,7 +250,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, }, @@ -251,7 +260,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, @@ -259,7 +268,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, }, @@ -269,7 +278,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 +330,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 +364,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 +375,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) } @@ -378,34 +388,82 @@ 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 { - 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)] + #[pallet::weight(1)] + pub fn nft_vote_multisig( + origin: OriginFor, + core_id: T::CoreId, + call_hash: T::Hash, + aye: bool, + ) -> DispatchResultWithPostInfo { + let member = ensure_multisig_member_nft::>(origin)?; + + //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 { - Pallet::::inner_withdraw_vote_multisig(caller, core_id, call_hash) + 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 { + let member = ensure_multisig_member_nft::>(origin)?; + + Pallet::::inner_withdraw_vote_multisig(member, core_id, call_hash) } #[pallet::call_index(6)] @@ -430,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 26a8f0d4..858cf3f9 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, @@ -18,9 +19,9 @@ use frame_support::{ }, BoundedBTreeMap, }; -use frame_system::{ensure_signed, pallet_prelude::*}; +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}; @@ -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,39 @@ 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 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 { + Self::AccountId(account_id) => account_id, + Self::Nft(nft_location) => nft_location.derive_account::(), + } + } +} + +pub type MultisigMemberOf = MultisigMember<::AccountId>; + impl Pallet where Result< @@ -59,7 +93,7 @@ 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; @@ -79,7 +113,7 @@ 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; @@ -103,17 +137,15 @@ 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_balance: BalanceOf = T::AssetsProvider::balance(core_id, &owner); + let member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member); - ensure!(!owner_balance.is_zero(), Error::::NoPermission); + ensure!(!member_balance.is_zero(), Error::::NoPermission); let (minimum_support, _) = Pallet::::minimum_support_and_required_approval(core_id) .ok_or(Error::::CoreNotFound)?; @@ -129,7 +161,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()); @@ -140,7 +172,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: member, call_hash, call: *call, result: dispatch_result.map(|_| ()).map_err(|e| e.error), @@ -157,15 +189,15 @@ where call_hash, MultisigOperation { tally: Tally::from_parts( - owner_balance, + member_balance, Zero::zero(), BoundedBTreeMap::try_from(BTreeMap::from([( - owner.clone(), - Vote::Aye(owner_balance), + member.clone(), + Vote::Aye(member_balance), )])) .map_err(|_| Error::::MaxCallersExceeded)?, ), - original_caller: owner.clone(), + original_caller: member.clone(), actual_call: bounded_call, metadata, fee_asset, @@ -179,8 +211,8 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, - votes_added: Vote::Aye(owner_balance), + voter: member, + votes_added: Vote::Aye(member_balance), call_hash, }); } @@ -190,17 +222,15 @@ where /// Vote on a multisig transaction that has not been executed yet pub(crate) fn inner_vote_multisig( - caller: OriginFor, + 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 member_balance: BalanceOf = T::AssetsProvider::balance(core_id, &member); - let voter_balance: BalanceOf = T::AssetsProvider::balance(core_id, &owner); - - ensure!(!voter_balance.is_zero(), Error::::NoPermission); + ensure!(!member_balance.is_zero(), Error::::NoPermission); let mut old_data = data.take().ok_or(Error::::MultisigCallNotFound)?; @@ -209,14 +239,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(owner.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); @@ -242,7 +272,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: member, call_hash, call: decoded_call, result: dispatch_result.map(|_| ()).map_err(|e| e.error), @@ -257,7 +287,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: member, votes_added: new_vote_record, current_votes: old_data.tally, call_hash, @@ -270,16 +300,14 @@ 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 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(member.clone(), None)?; *data = Some(old_data.clone()); @@ -290,7 +318,7 @@ where ::CoreId, ::AccountId, >(core_id), - voter: owner, + voter: member, votes_removed: old_vote, call_hash, }); @@ -313,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/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) + } +} 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-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)) + } +} diff --git a/pallet-nft-origins/Cargo.toml b/pallet-nft-origins/Cargo.toml new file mode 100644 index 00000000..7d1489ec --- /dev/null +++ b/pallet-nft-origins/Cargo.toml @@ -0,0 +1,60 @@ +[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" } + +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] +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..4035a274 --- /dev/null +++ b/pallet-nft-origins/src/chains.rs @@ -0,0 +1,6 @@ +use crate::location::Parachain; +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..ff1b3c8e --- /dev/null +++ b/pallet-nft-origins/src/lib.rs @@ -0,0 +1,218 @@ +#![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::{Collection, Nft, NftLocation, Parachain}; +pub use origin::NftOrigin; +pub use xcm_converters::*; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use crate::{ + chains::ChainVerifier, + location::{Collection, Nft, NftLocation, Parachain}, + 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 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(_); + + #[pallet::origin] + pub type Origin = NftOrigin; + + #[pallet::error] + pub enum Error { + SendingFailed, + } + + #[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({ + 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 = + crate::origin::ensure_nft::::RuntimeOrigin>(nft)?; + + Self::deposit_event(Event::::NftCalledTestFunction { nft_location }); + + Ok(()) + } + + #[pallet::call_index(91)] + #[pallet::weight(1)] + pub fn test_send_xcm_as_nft( + _: 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(()) + } + + #[pallet::call_index(92)] + #[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(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 new file mode 100644 index 00000000..042f69a5 --- /dev/null +++ b/pallet-nft-origins/src/location.rs @@ -0,0 +1,118 @@ +use crate::{chains::ChainVerifier, Config}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::RuntimeDebug; +use primitive_types::U256; +use scale_info::TypeInfo; +use sp_io::hashing::blake2_256; +use sp_runtime::traits::TrailingZeroInput; +use xcm::latest::Junction; + +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] +pub struct Parachain(pub u32); + +impl Parachain { + pub fn new_parachain_verified( + para_id: u32, + verifier_junction: Junction, + ) -> Option { + <::Chains as ChainVerifier>::get_chain_from_verifier( + para_id, + verifier_junction, + ) + } + + pub const fn para_id(&self) -> u32 { + self.0 + } +} + +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] +#[repr(u8)] +pub enum Collection { + /// Pallet based NFT collection + Id(u128) = 0, + /// EVM based NFT collection + Contract20([u8; 20]) = 1, + /// WASM based NFT collection + Contract32([u8; 32]) = 2, +} + +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] +#[repr(u8)] +pub enum Nft { + /// U128 NFT id + U128Id(u128) = 0, + /// U256 NFT id + U256Id(U256) = 1, + /// 20 bytes NFT id + Key20([u8; 20]) = 2, + /// 32 bytes NFT id + Key32([u8; 32]) = 3, +} + +#[derive( + PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug, PartialOrd, Ord, +)] +pub struct NftLocation { + /// Chain where the collection and NFT originate + pub chain: Parachain, + /// NFT collection + pub collection: Collection, + /// Specific NFT + pub nft: Nft, +} + +impl NftLocation { + pub fn new_verified( + 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 new(chain: Parachain, collection: Collection, nft: Nft) -> Self { + Self { + chain, + collection, + nft, + } + } + + pub fn derive_account(&self) -> AccountId { + let chain = (b"para", self.chain.para_id()).encode(); + + let collection = match self.collection { + 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::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(), + }; + + 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") + } +} diff --git a/pallet-nft-origins/src/origin.rs b/pallet-nft-origins/src/origin.rs new file mode 100644 index 00000000..2ed0d978 --- /dev/null +++ b/pallet-nft-origins/src/origin.rs @@ -0,0 +1,33 @@ +use crate::{ + location::{NftLocation, Parachain}, + pallet, Config, +}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{error::BadOrigin, RuntimeDebug}; +use scale_info::TypeInfo; + +#[derive(PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen, Clone, RuntimeDebug)] +pub enum NftOrigin { + Nft(NftLocation), + Verifier(Parachain), +} + +pub fn ensure_nft(o: OuterOrigin) -> Result +where + OuterOrigin: Into>, +{ + match o.into() { + 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 new file mode 100644 index 00000000..c8894cbf --- /dev/null +++ b/pallet-nft-origins/src/xcm_converters.rs @@ -0,0 +1,80 @@ +use crate::{ + location::{Collection, Nft, NftLocation, Parachain}, + origin::NftOrigin, + Config, +}; +use core::marker::PhantomData; +use frame_support::traits::OriginTrait; +use xcm::latest::{ + Junction, + Junctions::{X1, X2, 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: 1, + interior: + X4( + Junction::Parachain(para_id), + verifier_junction, + Junction::AccountKey20 { + network: None, + key: collection_key, + }, + Junction::GeneralIndex(nft_id), + ), + }, + ) => NftLocation::new_verified::( + para_id, + verifier_junction, + Collection::Contract20(collection_key), + Nft::U128Id(nft_id), + ) + .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", "VerifierMultilocationAsOrigin origin: {:?}, kind: {:?}", origin, kind); + + 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), + + _ => Err(origin), + } + } +}