difftreelog
refac: incapsulate CollectionHandler into CollectionDispatch
in: master
13 files changed
pallets/balances-adapter/src/lib.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/lib.rs
+++ b/pallets/balances-adapter/src/lib.rs
@@ -3,6 +3,7 @@
#![warn(missing_docs)]
extern crate alloc;
+use frame_support::sp_runtime::DispatchResult;
pub use pallet::*;
use pallet_common::CollectionHandle;
use pallet_evm_coder_substrate::{WithRecorder, SubstrateRecorder};
@@ -10,24 +11,23 @@
pub mod common;
pub mod erc;
-pub struct NativeFungibleHandle<T: Config>(CollectionHandle<T>);
+pub struct NativeFungibleHandle<T: Config>(SubstrateRecorder<T>);
impl<T: Config> NativeFungibleHandle<T> {
- pub fn cast(inner: CollectionHandle<T>) -> Self {
- Self(inner)
+ pub fn new() -> NativeFungibleHandle<T> {
+ Self(SubstrateRecorder::new(u64::MAX))
}
- /// Casts [`NativeFungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].
- pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {
- self.0
+ pub fn check_is_internal(&self) -> DispatchResult {
+ Ok(())
}
}
impl<T: Config> WithRecorder<T> for NativeFungibleHandle<T> {
fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
- &self.0.recorder
+ &self.0
}
fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {
- self.0.recorder
+ self.0
}
}
#[frame_support::pallet]
pallets/common/src/dispatch.rsdiffbeforeafterboth--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -34,16 +34,11 @@
collection: CollectionId,
call: C,
) -> DispatchResultWithPostInfo {
- let handle =
- CollectionHandle::try_get(collection).map_err(|error| DispatchErrorWithPostInfo {
- post_info: PostDispatchInfo {
- actual_weight: Some(dispatch_weight::<T>()),
- pays_fee: Pays::Yes,
- },
- error,
- })?;
- handle
- .check_is_internal()
+ let dispatched = T::CollectionDispatch::dispatch(collection)
+ .and_then(|dispatched| {
+ dispatched.check_is_internal()?;
+ Ok(dispatched)
+ })
.map_err(|error| DispatchErrorWithPostInfo {
post_info: PostDispatchInfo {
actual_weight: Some(dispatch_weight::<T>()),
@@ -51,7 +46,6 @@
},
error,
})?;
- let dispatched = T::CollectionDispatch::dispatch(handle);
let mut result = call(dispatched.as_dyn());
match &mut result {
Ok(PostDispatchInfo {
@@ -72,6 +66,8 @@
/// Interface for working with different collections through the dispatcher.
pub trait CollectionDispatch<T: Config> {
+ fn check_is_internal(&self) -> DispatchResult;
+
/// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
///
/// * `sender` - The user who will become the owner of the collection.
@@ -92,7 +88,9 @@
/// Get a specialized collection from the handle.
///
/// * `handle` - Collection handle.
- fn dispatch(handle: CollectionHandle<T>) -> Self;
+ fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError>
+ where
+ Self: Sized;
/// Get the implementation of [`CommonCollectionOperations`].
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T>;
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -77,6 +77,14 @@
fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;
}
+impl CommonEvmHandler for () {
+ const CODE: &'static [u8] = &[];
+
+ fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+ None
+ }
+}
+
/// @title A contract that allows you to work with collections.
#[solidity_interface(name = Collection, enum(derive(PreDispatch)), enum_attr(weight))]
impl<T: Config> CollectionHandle<T>
pallets/structure/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -42,7 +42,7 @@
},
CollectionFlags::default(),
)?;
- let dispatch = T::CollectionDispatch::dispatch(CollectionHandle::try_get(CollectionId(1))?);
+ let dispatch = T::CollectionDispatch::dispatch(CollectionId(1))?;
let dispatch = dispatch.as_dyn();
dispatch.create_item(caller_cross.clone(), caller_cross.clone(), CreateItemData::NFT(CreateNftData::default()), &Unlimited)?;
pallets/structure/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Structure Pallet18//!19//! The Structure pallet provides functionality for handling tokens nesting an unnesting.20//!21//! - [`Config`]22//! - [`Pallet`]23//!24//! ## Overview25//!26//! The Structure pallet provides functions for:27//!28//! - Searching for token parents, children and owners. Actual implementation of searching for29//! parent/child is done by pallets corresponding to token's collection type.30//! - Nesting and unnesting tokens. Actual implementation of nesting is done by pallets corresponding31//! to token's collection type.32//!33//! ### Terminology34//!35//! - **Nesting:** Setting up parent-child relationship between tokens. Nested tokens are inhereting36//! owner from their parent. There could be multiple levels of nesting. Token couldn't be nested in37//! it's child token i.e. parent-child relationship graph shouldn't have38//!39//! - **Parent:** Token that current token is nested in.40//!41//! - **Owner:** Account that owns the token and all nested tokens.42//!43//! ## Interface44//!45//! ### Available Functions46//!47//! - `find_parent` - Find parent of the token. It could be an account or another token.48//! - `parent_chain` - Find chain of parents of the token.49//! - `find_topmost_owner` - Find account or token in the end of the chain of parents.50//! - `check_nesting` - Check if the token could be nested in the other token51//! - `nest_if_sent_to_token` - Nest the token in the other token52//! - `unnest_if_nested` - Unnest the token from the other token5354#![cfg_attr(not(feature = "std"), no_std)]5556use pallet_common::CommonCollectionOperations;57use pallet_common::{erc::CrossAccountId, eth::is_collection};58use sp_std::collections::btree_set::BTreeSet;5960use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};61use frame_support::fail;62pub use pallet::*;63use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};64use up_data_structs::{65 CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,66};6768#[cfg(feature = "runtime-benchmarks")]69pub mod benchmarking;70pub mod weights;7172pub type SelfWeightOf<T> = <T as crate::Config>::WeightInfo;7374#[frame_support::pallet]75pub mod pallet {76 use frame_support::Parameter;77 use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};78 use frame_support::pallet_prelude::*;7980 use super::*;8182 #[pallet::error]83 pub enum Error<T> {84 /// While nesting, encountered an already checked account, detecting a loop.85 OuroborosDetected,86 /// While nesting, reached the depth limit of nesting, exceeding the provided budget.87 DepthLimit,88 /// While nesting, reached the breadth limit of nesting, exceeding the provided budget.89 BreadthLimit,90 /// Couldn't find the token owner that is itself a token.91 TokenNotFound,92 /// Tried to nest token under collection contract address, instead of token address93 CantNestTokenUnderCollection,94 }9596 #[pallet::event]97 pub enum Event<T> {98 /// Executed call on behalf of the token.99 Executed(DispatchResult),100 }101102 #[pallet::config]103 pub trait Config: frame_system::Config + pallet_common::Config {104 type WeightInfo: weights::WeightInfo;105 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;106 type RuntimeCall: Parameter107 + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>108 + GetDispatchInfo;109 }110111 #[pallet::pallet]112 pub struct Pallet<T>(_);113114 #[pallet::call]115 impl<T: Config> Pallet<T> {116 // #[pallet::weight({117 // let dispatch_info = call.get_dispatch_info();118119 // (120 // dispatch_info.weight121 // // Cost of dereferencing parent122 // .saturating_add(T::DbWeight::get().reads(2 * *max_depth as Weight))123 // .saturating_add(4000 * *max_depth as Weight),124 // dispatch_info.class)125 // })]126 // pub fn execute(127 // origin: OriginFor<T>,128 // call: Box<<T as Config>::Call>,129 // max_depth: u32,130 // ) -> DispatchResult {131 }132}133134#[derive(PartialEq)]135pub enum Parent<CrossAccountId> {136 /// Token owned by a normal account.137 User(CrossAccountId),138 /// Could not find the token provided as the owner.139 TokenNotFound,140 /// Nested token has multiple owners.141 MultipleOwners,142 /// Token owner is another token (still, the target token may not exist).143 Token(CollectionId, TokenId),144}145146impl<T: Config> Pallet<T> {147 /// Find account owning the `token` or a token that the `token` is nested in.148 ///149 /// Returns the enum that have three variants:150 /// - [`User`](crate::Parent<T>::User): Contains account.151 /// - [`Token`](crate::Parent<T>::Token): Contains token id and collection id.152 /// - [`TokenNotFound`](crate::Parent<T>::TokenNotFound): Indicates that parent was not found153 pub fn find_parent(154 collection: CollectionId,155 token: TokenId,156 ) -> Result<Parent<T::CrossAccountId>, DispatchError> {157 // TODO: Reduce cost by not reading collection config158 let handle = match CollectionHandle::try_get(collection) {159 Ok(v) => v,160 Err(_) => return Ok(Parent::TokenNotFound),161 };162 let handle = T::CollectionDispatch::dispatch(handle);163 let handle = handle.as_dyn();164165 Ok(match handle.token_owner(token) {166 Ok(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {167 Some((collection, token)) => Parent::Token(collection, token),168 None => Parent::User(owner),169 },170 Err(TokenOwnerError::MultipleOwners) => Parent::MultipleOwners,171 Err(TokenOwnerError::NotFound) => Parent::TokenNotFound,172 })173 }174175 /// Get the chain of parents of a token in the nesting hierarchy176 ///177 /// Returns an iterator of addresses of the owning tokens and the owning account,178 /// starting from the immediate parent token, ending with the account.179 /// Returns error if cycle is detected.180 pub fn parent_chain(181 mut collection: CollectionId,182 mut token: TokenId,183 ) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {184 let mut finished = false;185 let mut visited = BTreeSet::new();186 visited.insert((collection, token));187 core::iter::from_fn(move || {188 if finished {189 return None;190 }191 let parent = Self::find_parent(collection, token);192 match parent {193 Ok(Parent::Token(new_collection, new_token)) => {194 collection = new_collection;195 token = new_token;196 if !visited.insert((new_collection, new_token)) {197 finished = true;198 return Some(Err(<Error<T>>::OuroborosDetected.into()));199 }200 }201 _ => finished = true,202 }203 Some(parent as Result<_, DispatchError>)204 })205 }206207 /// Try to dereference address, until finding top level owner208 ///209 /// May return token address if parent token not yet exists210 ///211 /// Returns `None` if the token has multiple owners.212 ///213 /// - `budget`: Limit for searching parents in depth.214 pub fn find_topmost_owner(215 collection: CollectionId,216 token: TokenId,217 budget: &dyn Budget,218 ) -> Result<Option<T::CrossAccountId>, DispatchError> {219 let owner = Self::parent_chain(collection, token)220 .take_while(|_| budget.consume())221 .find(|p| {222 matches!(223 p,224 Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)225 )226 })227 .ok_or(<Error<T>>::DepthLimit)??;228229 Ok(match owner {230 Parent::User(v) => Some(v),231 Parent::MultipleOwners => None,232 _ => fail!(<Error<T>>::TokenNotFound),233 })234 }235236 /// Find the topmost parent and check that assigning `for_nest` token as a child for237 /// `token` wouldn't create a cycle.238 ///239 /// Returns `None` if the token has multiple owners.240 ///241 /// - `budget`: Limit for searching parents in depth.242 pub fn get_checked_topmost_owner(243 collection: CollectionId,244 token: TokenId,245 for_nest: Option<(CollectionId, TokenId)>,246 budget: &dyn Budget,247 ) -> Result<Option<T::CrossAccountId>, DispatchError> {248 // Tried to nest token in itself249 if Some((collection, token)) == for_nest {250 return Err(<Error<T>>::OuroborosDetected.into());251 }252253 for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {254 match parent? {255 // Tried to nest token in chain, which has this token as one of parents256 Parent::Token(collection, token) if Some((collection, token)) == for_nest => {257 return Err(<Error<T>>::OuroborosDetected.into())258 }259 // Token is owned by other user260 Parent::User(user) => return Ok(Some(user)),261 Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),262 Parent::MultipleOwners => return Ok(None),263 // Continue parent chain264 Parent::Token(_, _) => {}265 }266 }267268 Err(<Error<T>>::DepthLimit.into())269 }270271 /// Burn token and all of it's nested tokens272 ///273 /// - `self_budget`: Limit for searching children in depth.274 /// - `breadth_budget`: Limit of breadth of searching children.275 pub fn burn_item_recursively(276 from: T::CrossAccountId,277 collection: CollectionId,278 token: TokenId,279 self_budget: &dyn Budget,280 breadth_budget: &dyn Budget,281 ) -> DispatchResultWithPostInfo {282 let handle = <CollectionHandle<T>>::try_get(collection)?;283 let dispatch = T::CollectionDispatch::dispatch(handle);284 let dispatch = dispatch.as_dyn();285 dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)286 }287288 /// Check if `token` indirectly owned by `user`289 ///290 /// Returns `true` if `user` is `token`'s owner. Or If token is provided as `user` then291 /// check that `user` and `token` have same owner.292 /// Checks that assigning `for_nest` token as a child for `token` wouldn't create a cycle.293 ///294 /// - `budget`: Limit for searching parents in depth.295 pub fn check_indirectly_owned(296 user: T::CrossAccountId,297 collection: CollectionId,298 token: TokenId,299 for_nest: Option<(CollectionId, TokenId)>,300 budget: &dyn Budget,301 ) -> Result<bool, DispatchError> {302 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {303 Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?304 {305 Some(topmost_owner) => topmost_owner,306 None => return Ok(false),307 },308 None => user,309 };310311 Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {312 indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)313 })314 }315316 /// Checks that `under` is valid token and that `token_id` could be nested under it317 /// and that `from` is `under`'s owner318 ///319 /// Returns OK if `under` is not a token320 ///321 /// - `nesting_budget`: Limit for searching parents in depth.322 pub fn check_nesting(323 from: T::CrossAccountId,324 under: &T::CrossAccountId,325 collection_id: CollectionId,326 token_id: TokenId,327 nesting_budget: &dyn Budget,328 ) -> DispatchResult {329 Self::try_exec_if_token(under, |collection, parent_id| {330 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)331 })332 }333334 /// Nests `token_id` under `under` token335 ///336 /// Returns OK if `under` is not a token. Checks that nesting is possible.337 ///338 /// - `nesting_budget`: Limit for searching parents in depth.339 pub fn nest_if_sent_to_token(340 from: T::CrossAccountId,341 under: &T::CrossAccountId,342 collection_id: CollectionId,343 token_id: TokenId,344 nesting_budget: &dyn Budget,345 ) -> DispatchResult {346 Self::try_exec_if_token(under, |collection, parent_id| {347 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;348349 collection.nest(parent_id, (collection_id, token_id));350351 Ok(())352 })353 }354355 /// Nests `token_id` under `owner` token356 ///357 /// Caller should check that nesting wouldn't cause recursion in nesting358 pub fn nest_if_sent_to_token_unchecked(359 owner: &T::CrossAccountId,360 collection_id: CollectionId,361 token_id: TokenId,362 ) {363 Self::exec_if_token(owner, |collection, parent_id| {364 collection.nest(parent_id, (collection_id, token_id))365 });366 }367368 /// Unnests `token_id` from `owner`.369 pub fn unnest_if_nested(370 owner: &T::CrossAccountId,371 collection_id: CollectionId,372 token_id: TokenId,373 ) {374 if let Err(e) = Self::try_exec_if_token(owner, |collection, parent_id| {375 collection.unnest(parent_id, (collection_id, token_id));376 Ok(())377 }) {378 log::warn!("unnest precondition failed: {e:?}")379 }380 }381382 /// # Panics383 /// If [`Self::try_exec_if_token`] fails384 fn exec_if_token(385 account: &T::CrossAccountId,386 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),387 ) {388 Self::try_exec_if_token(account, |collection, id| {389 action(collection, id);390 Ok(())391 })392 .unwrap();393 }394395 /// If `account` is a token address, execute `action` providing found collection as an argument396 /// Token may not exist, it is expected it will be checked in the callback.397 fn try_exec_if_token(398 account: &T::CrossAccountId,399 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,400 ) -> DispatchResult {401 if is_collection(&account.as_eth()) {402 fail!(<Error<T>>::CantNestTokenUnderCollection);403 }404 let Some((collection, token)) = T::CrossTokenAddressMapping::address_to_token(account) else {405 return Ok(())406 };407408 let handle = <CollectionHandle<T>>::try_get(collection)?;409410 let dispatch = T::CollectionDispatch::dispatch(handle);411 let dispatch = dispatch.as_dyn();412413 action(dispatch, token)414 }415}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Structure Pallet18//!19//! The Structure pallet provides functionality for handling tokens nesting an unnesting.20//!21//! - [`Config`]22//! - [`Pallet`]23//!24//! ## Overview25//!26//! The Structure pallet provides functions for:27//!28//! - Searching for token parents, children and owners. Actual implementation of searching for29//! parent/child is done by pallets corresponding to token's collection type.30//! - Nesting and unnesting tokens. Actual implementation of nesting is done by pallets corresponding31//! to token's collection type.32//!33//! ### Terminology34//!35//! - **Nesting:** Setting up parent-child relationship between tokens. Nested tokens are inhereting36//! owner from their parent. There could be multiple levels of nesting. Token couldn't be nested in37//! it's child token i.e. parent-child relationship graph shouldn't have38//!39//! - **Parent:** Token that current token is nested in.40//!41//! - **Owner:** Account that owns the token and all nested tokens.42//!43//! ## Interface44//!45//! ### Available Functions46//!47//! - `find_parent` - Find parent of the token. It could be an account or another token.48//! - `parent_chain` - Find chain of parents of the token.49//! - `find_topmost_owner` - Find account or token in the end of the chain of parents.50//! - `check_nesting` - Check if the token could be nested in the other token51//! - `nest_if_sent_to_token` - Nest the token in the other token52//! - `unnest_if_nested` - Unnest the token from the other token5354#![cfg_attr(not(feature = "std"), no_std)]5556use pallet_common::CommonCollectionOperations;57use pallet_common::{erc::CrossAccountId, eth::is_collection};58use sp_std::collections::btree_set::BTreeSet;5960use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};61use frame_support::fail;62pub use pallet::*;63use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};64use up_data_structs::{65 CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,66};6768#[cfg(feature = "runtime-benchmarks")]69pub mod benchmarking;70pub mod weights;7172pub type SelfWeightOf<T> = <T as crate::Config>::WeightInfo;7374#[frame_support::pallet]75pub mod pallet {76 use frame_support::Parameter;77 use frame_support::dispatch::{GetDispatchInfo, UnfilteredDispatchable};78 use frame_support::pallet_prelude::*;7980 use super::*;8182 #[pallet::error]83 pub enum Error<T> {84 /// While nesting, encountered an already checked account, detecting a loop.85 OuroborosDetected,86 /// While nesting, reached the depth limit of nesting, exceeding the provided budget.87 DepthLimit,88 /// While nesting, reached the breadth limit of nesting, exceeding the provided budget.89 BreadthLimit,90 /// Couldn't find the token owner that is itself a token.91 TokenNotFound,92 /// Tried to nest token under collection contract address, instead of token address93 CantNestTokenUnderCollection,94 }9596 #[pallet::event]97 pub enum Event<T> {98 /// Executed call on behalf of the token.99 Executed(DispatchResult),100 }101102 #[pallet::config]103 pub trait Config: frame_system::Config + pallet_common::Config {104 type WeightInfo: weights::WeightInfo;105 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;106 type RuntimeCall: Parameter107 + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>108 + GetDispatchInfo;109 }110111 #[pallet::pallet]112 pub struct Pallet<T>(_);113114 #[pallet::call]115 impl<T: Config> Pallet<T> {116 // #[pallet::weight({117 // let dispatch_info = call.get_dispatch_info();118119 // (120 // dispatch_info.weight121 // // Cost of dereferencing parent122 // .saturating_add(T::DbWeight::get().reads(2 * *max_depth as Weight))123 // .saturating_add(4000 * *max_depth as Weight),124 // dispatch_info.class)125 // })]126 // pub fn execute(127 // origin: OriginFor<T>,128 // call: Box<<T as Config>::Call>,129 // max_depth: u32,130 // ) -> DispatchResult {131 }132}133134#[derive(PartialEq)]135pub enum Parent<CrossAccountId> {136 /// Token owned by a normal account.137 User(CrossAccountId),138 /// Could not find the token provided as the owner.139 TokenNotFound,140 /// Nested token has multiple owners.141 MultipleOwners,142 /// Token owner is another token (still, the target token may not exist).143 Token(CollectionId, TokenId),144}145146impl<T: Config> Pallet<T> {147 /// Find account owning the `token` or a token that the `token` is nested in.148 ///149 /// Returns the enum that have three variants:150 /// - [`User`](crate::Parent<T>::User): Contains account.151 /// - [`Token`](crate::Parent<T>::Token): Contains token id and collection id.152 /// - [`TokenNotFound`](crate::Parent<T>::TokenNotFound): Indicates that parent was not found153 pub fn find_parent(154 collection: CollectionId,155 token: TokenId,156 ) -> Result<Parent<T::CrossAccountId>, DispatchError> {157 // TODO: Reduce cost by not reading collection config158 let handle = match T::CollectionDispatch::dispatch(collection) {159 Ok(v) => v,160 Err(_) => return Ok(Parent::TokenNotFound),161 };162 let handle = handle.as_dyn();163164 Ok(match handle.token_owner(token) {165 Ok(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {166 Some((collection, token)) => Parent::Token(collection, token),167 None => Parent::User(owner),168 },169 Err(TokenOwnerError::MultipleOwners) => Parent::MultipleOwners,170 Err(TokenOwnerError::NotFound) => Parent::TokenNotFound,171 })172 }173174 /// Get the chain of parents of a token in the nesting hierarchy175 ///176 /// Returns an iterator of addresses of the owning tokens and the owning account,177 /// starting from the immediate parent token, ending with the account.178 /// Returns error if cycle is detected.179 pub fn parent_chain(180 mut collection: CollectionId,181 mut token: TokenId,182 ) -> impl Iterator<Item = Result<Parent<T::CrossAccountId>, DispatchError>> {183 let mut finished = false;184 let mut visited = BTreeSet::new();185 visited.insert((collection, token));186 core::iter::from_fn(move || {187 if finished {188 return None;189 }190 let parent = Self::find_parent(collection, token);191 match parent {192 Ok(Parent::Token(new_collection, new_token)) => {193 collection = new_collection;194 token = new_token;195 if !visited.insert((new_collection, new_token)) {196 finished = true;197 return Some(Err(<Error<T>>::OuroborosDetected.into()));198 }199 }200 _ => finished = true,201 }202 Some(parent as Result<_, DispatchError>)203 })204 }205206 /// Try to dereference address, until finding top level owner207 ///208 /// May return token address if parent token not yet exists209 ///210 /// Returns `None` if the token has multiple owners.211 ///212 /// - `budget`: Limit for searching parents in depth.213 pub fn find_topmost_owner(214 collection: CollectionId,215 token: TokenId,216 budget: &dyn Budget,217 ) -> Result<Option<T::CrossAccountId>, DispatchError> {218 let owner = Self::parent_chain(collection, token)219 .take_while(|_| budget.consume())220 .find(|p| {221 matches!(222 p,223 Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)224 )225 })226 .ok_or(<Error<T>>::DepthLimit)??;227228 Ok(match owner {229 Parent::User(v) => Some(v),230 Parent::MultipleOwners => None,231 _ => fail!(<Error<T>>::TokenNotFound),232 })233 }234235 /// Find the topmost parent and check that assigning `for_nest` token as a child for236 /// `token` wouldn't create a cycle.237 ///238 /// Returns `None` if the token has multiple owners.239 ///240 /// - `budget`: Limit for searching parents in depth.241 pub fn get_checked_topmost_owner(242 collection: CollectionId,243 token: TokenId,244 for_nest: Option<(CollectionId, TokenId)>,245 budget: &dyn Budget,246 ) -> Result<Option<T::CrossAccountId>, DispatchError> {247 // Tried to nest token in itself248 if Some((collection, token)) == for_nest {249 return Err(<Error<T>>::OuroborosDetected.into());250 }251252 for parent in Self::parent_chain(collection, token).take_while(|_| budget.consume()) {253 match parent? {254 // Tried to nest token in chain, which has this token as one of parents255 Parent::Token(collection, token) if Some((collection, token)) == for_nest => {256 return Err(<Error<T>>::OuroborosDetected.into())257 }258 // Token is owned by other user259 Parent::User(user) => return Ok(Some(user)),260 Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),261 Parent::MultipleOwners => return Ok(None),262 // Continue parent chain263 Parent::Token(_, _) => {}264 }265 }266267 Err(<Error<T>>::DepthLimit.into())268 }269270 /// Burn token and all of it's nested tokens271 ///272 /// - `self_budget`: Limit for searching children in depth.273 /// - `breadth_budget`: Limit of breadth of searching children.274 pub fn burn_item_recursively(275 from: T::CrossAccountId,276 collection: CollectionId,277 token: TokenId,278 self_budget: &dyn Budget,279 breadth_budget: &dyn Budget,280 ) -> DispatchResultWithPostInfo {281 let dispatch = T::CollectionDispatch::dispatch(collection)?;282 let dispatch = dispatch.as_dyn();283 dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)284 }285286 /// Check if `token` indirectly owned by `user`287 ///288 /// Returns `true` if `user` is `token`'s owner. Or If token is provided as `user` then289 /// check that `user` and `token` have same owner.290 /// Checks that assigning `for_nest` token as a child for `token` wouldn't create a cycle.291 ///292 /// - `budget`: Limit for searching parents in depth.293 pub fn check_indirectly_owned(294 user: T::CrossAccountId,295 collection: CollectionId,296 token: TokenId,297 for_nest: Option<(CollectionId, TokenId)>,298 budget: &dyn Budget,299 ) -> Result<bool, DispatchError> {300 let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {301 Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?302 {303 Some(topmost_owner) => topmost_owner,304 None => return Ok(false),305 },306 None => user,307 };308309 Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {310 indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)311 })312 }313314 /// Checks that `under` is valid token and that `token_id` could be nested under it315 /// and that `from` is `under`'s owner316 ///317 /// Returns OK if `under` is not a token318 ///319 /// - `nesting_budget`: Limit for searching parents in depth.320 pub fn check_nesting(321 from: T::CrossAccountId,322 under: &T::CrossAccountId,323 collection_id: CollectionId,324 token_id: TokenId,325 nesting_budget: &dyn Budget,326 ) -> DispatchResult {327 Self::try_exec_if_token(under, |collection, parent_id| {328 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)329 })330 }331332 /// Nests `token_id` under `under` token333 ///334 /// Returns OK if `under` is not a token. Checks that nesting is possible.335 ///336 /// - `nesting_budget`: Limit for searching parents in depth.337 pub fn nest_if_sent_to_token(338 from: T::CrossAccountId,339 under: &T::CrossAccountId,340 collection_id: CollectionId,341 token_id: TokenId,342 nesting_budget: &dyn Budget,343 ) -> DispatchResult {344 Self::try_exec_if_token(under, |collection, parent_id| {345 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;346347 collection.nest(parent_id, (collection_id, token_id));348349 Ok(())350 })351 }352353 /// Nests `token_id` under `owner` token354 ///355 /// Caller should check that nesting wouldn't cause recursion in nesting356 pub fn nest_if_sent_to_token_unchecked(357 owner: &T::CrossAccountId,358 collection_id: CollectionId,359 token_id: TokenId,360 ) {361 Self::exec_if_token(owner, |collection, parent_id| {362 collection.nest(parent_id, (collection_id, token_id))363 });364 }365366 /// Unnests `token_id` from `owner`.367 pub fn unnest_if_nested(368 owner: &T::CrossAccountId,369 collection_id: CollectionId,370 token_id: TokenId,371 ) {372 if let Err(e) = Self::try_exec_if_token(owner, |collection, parent_id| {373 collection.unnest(parent_id, (collection_id, token_id));374 Ok(())375 }) {376 log::warn!("unnest precondition failed: {e:?}")377 }378 }379380 /// # Panics381 /// If [`Self::try_exec_if_token`] fails382 fn exec_if_token(383 account: &T::CrossAccountId,384 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),385 ) {386 Self::try_exec_if_token(account, |collection, id| {387 action(collection, id);388 Ok(())389 })390 .unwrap();391 }392393 /// If `account` is a token address, execute `action` providing found collection as an argument394 /// Token may not exist, it is expected it will be checked in the callback.395 fn try_exec_if_token(396 account: &T::CrossAccountId,397 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,398 ) -> DispatchResult {399 if is_collection(&account.as_eth()) {400 fail!(<Error<T>>::CantNestTokenUnderCollection);401 }402 let Some((collection, token)) = T::CrossTokenAddressMapping::address_to_token(account) else {403 return Ok(())404 };405406 let dispatch = T::CollectionDispatch::dispatch(collection)?;407 let dispatch = dispatch.as_dyn();408409 action(dispatch, token)410 }411}runtime/common/dispatch.rsdiffbeforeafterboth--- a/runtime/common/dispatch.rs
+++ b/runtime/common/dispatch.rs
@@ -50,6 +50,7 @@
Refungible(RefungibleHandle<T>),
NativeFungible(NativeFungibleHandle<T>),
}
+
impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
where
T: pallet_common::Config
@@ -59,6 +60,15 @@
+ pallet_refungible::Config
+ pallet_balances_adapter::Config,
{
+ fn check_is_internal(&self) -> DispatchResult {
+ match self {
+ Self::Fungible(h) => h.check_is_internal(),
+ Self::Nonfungible(h) => h.check_is_internal(),
+ Self::Refungible(h) => h.check_is_internal(),
+ Self::NativeFungible(h) => h.check_is_internal(),
+ }
+ }
+
fn create(
sender: T::CrossAccountId,
payer: T::CrossAccountId,
@@ -104,18 +114,17 @@
Ok(())
}
- fn dispatch(handle: CollectionHandle<T>) -> Self {
- match handle.mode {
- CollectionMode::Fungible(_) => {
- if handle.id != up_data_structs::CollectionId(0) {
- Self::Fungible(FungibleHandle::cast(handle))
- } else {
- Self::NativeFungible(NativeFungibleHandle::cast(handle))
- }
- }
+ fn dispatch(collection_id: CollectionId) -> Result<Self, DispatchError> {
+ if collection_id == CollectionId(0) {
+ return Ok(Self::NativeFungible(NativeFungibleHandle::new()));
+ }
+
+ let handle = <CollectionHandle<T>>::try_get(collection_id)?;
+ Ok(match handle.mode {
+ CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)),
CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)),
CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)),
- }
+ })
}
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T> {
@@ -172,15 +181,19 @@
}
fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
- let collection =
- <CollectionHandle<T>>::new_with_gas_limit(collection_id, handle.remaining_gas())?;
- let dispatched = Self::dispatch(collection);
+ if collection_id == CollectionId(0) {
+ <NativeFungibleHandle<T>>::new().call(handle)
+ } else {
+ let collection = <CollectionHandle<T>>::new_with_gas_limit(
+ collection_id,
+ handle.remaining_gas(),
+ )?;
- match dispatched {
- Self::Fungible(h) => h.call(handle),
- Self::Nonfungible(h) => h.call(handle),
- Self::Refungible(h) => h.call(handle),
- Self::NativeFungible(h) => h.call(handle),
+ match collection.mode {
+ CollectionMode::Fungible(_) => FungibleHandle::cast(collection).call(handle),
+ CollectionMode::NFT => NonfungibleHandle::cast(collection).call(handle),
+ CollectionMode::ReFungible => RefungibleHandle::cast(collection).call(handle),
+ }
}
} else if let Some((collection_id, token_id)) =
<T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -17,7 +17,7 @@
#[macro_export]
macro_rules! dispatch_unique_runtime {
($collection:ident.$method:ident($($name:ident),*) $($rest:tt)*) => {{
- let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
+ let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch($collection)?;
let dispatch = collection.as_dyn();
Ok::<_, DispatchError>(dispatch.$method($($name),*) $($rest)*)
tests/src/eth/fungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -33,7 +33,7 @@
'substrate' as const,
'ethereum' as const,
].map(testCase => {
- itEth.only(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {
+ itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {
// 1. Create receiver depending on the test case:
const receiverEth = helper.eth.createAccount();
const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);
tests/src/eth/nativeFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nativeFungible.test.ts
+++ b/tests/src/eth/nativeFungible.test.ts
@@ -29,7 +29,7 @@
});
});
- itEth.only('Can perform approve()', async ({helper}) => {
+ itEth.skip('Can perform approve()', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const spender = helper.eth.createAccount();
const collection = await helper.ft.mintCollection(alice);
tests/src/eth/util/playgrounds/types.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/types.ts
+++ b/tests/src/eth/util/playgrounds/types.ts
@@ -48,3 +48,5 @@
field: CollectionLimitField,
value: OptionUint,
}
+
+export const NON_EXISTENT_COLLECTION_ID = 4_294_967_295;
\ No newline at end of file
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -19,6 +19,7 @@
// Pallets that must always be present
const requiredPallets = [
'balances',
+ 'balancesadapter',
'common',
'timestamp',
'transactionpayment',
tests/src/transfer.test.tsdiffbeforeafterboth--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -17,6 +17,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itEth, usingEthPlaygrounds} from './eth/util';
import {itSub, Pallets, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './eth/util/playgrounds/types';
describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {
let donor: IKeyringPair;
@@ -124,20 +125,17 @@
itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))
+ await expect(helper.nft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))
+ await expect(helper.ft.transfer(alice, NON_EXISTENT_COLLECTION_ID, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))
+ await expect(helper.rft.transferToken(alice, NON_EXISTENT_COLLECTION_ID, 1, {Substrate: bob.address}))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});
tests/src/transferFrom.test.tsdiffbeforeafterboth--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -16,6 +16,7 @@
import {IKeyringPair} from '@polkadot/types/types';
import {itSub, Pallets, usingPlaygrounds, expect} from './util';
+import {NON_EXISTENT_COLLECTION_ID} from './eth/util/playgrounds/types';
describe('Integration Test transferFrom(from, recipient, collection_id, item_id, value):', () => {
let alice: IKeyringPair;
@@ -97,10 +98,9 @@
});
itSub('transferFrom for a collection that does not exist', async ({helper}) => {
- const collectionId = (1 << 32) - 1;
- await expect(helper.collection.approveToken(alice, collectionId, 0, {Substrate: bob.address}, 1n))
+ await expect(helper.collection.approveToken(alice, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.CollectionNotFound/);
- await expect(helper.collection.transferTokenFrom(bob, collectionId, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
+ await expect(helper.collection.transferTokenFrom(bob, NON_EXISTENT_COLLECTION_ID, 0, {Substrate: alice.address}, {Substrate: bob.address}, 1n))
.to.be.rejectedWith(/common\.CollectionNotFound/);
});