difftreelog
feat(fungible) transfer from parent token
in: master
2 files changed
pallets/fungible/Cargo.tomldiffbeforeafterboth--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -17,6 +17,7 @@
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20" }
sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.20" }
pallet-common = { default-features = false, path = '../common' }
+pallet-structure = { default-features = false, path = '../structure' }
up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
evm-coder = { default-features = false, path = '../../crates/evm-coder' }
pallet-evm-coder-substrate = { default-features = false, path = '../evm-coder-substrate' }
@@ -36,6 +37,7 @@
"sp-std/std",
"up-data-structs/std",
"pallet-common/std",
+ "pallet-structure/std",
"evm-coder/std",
"ethereum/std",
"pallet-evm-coder-substrate/std",
pallets/fungible/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#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::Deref;20use frame_support::{ensure};21use pallet_evm::account::CrossAccountId;22use up_data_structs::{23 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,24};25use pallet_common::{26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,27 CollectionHandle, dispatch::CollectionDispatch,28};29use pallet_evm_coder_substrate::WithRecorder;30use sp_core::H160;31use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};32use sp_std::collections::btree_map::BTreeMap;3334pub use pallet::*;3536use crate::erc::ERC20Events;37#[cfg(feature = "runtime-benchmarks")]38pub mod benchmarking;39pub mod common;40pub mod erc;41pub mod weights;4243pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);44pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4546#[frame_support::pallet]47pub mod pallet {48 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};49 use up_data_structs::CollectionId;50 use super::weights::WeightInfo;5152 #[pallet::error]53 pub enum Error<T> {54 /// Not Fungible item data used to mint in Fungible collection.55 NotFungibleDataUsedToMintFungibleCollectionToken,56 /// Not default id passed as TokenId argument57 FungibleItemsHaveNoId,58 /// Tried to set data for fungible item59 FungibleItemsDontHaveData,60 /// Fungible token does not support nested61 FungibleDisallowsNesting,62 }6364 #[pallet::config]65 pub trait Config: frame_system::Config + pallet_common::Config {66 type WeightInfo: WeightInfo;67 }6869 #[pallet::pallet]70 #[pallet::generate_store(pub(super) trait Store)]71 pub struct Pallet<T>(_);7273 #[pallet::storage]74 pub type TotalSupply<T: Config> =75 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;7677 #[pallet::storage]78 pub type Balance<T: Config> = StorageNMap<79 Key = (80 Key<Twox64Concat, CollectionId>,81 Key<Blake2_128Concat, T::CrossAccountId>,82 ),83 Value = u128,84 QueryKind = ValueQuery,85 >;8687 #[pallet::storage]88 pub type Allowance<T: Config> = StorageNMap<89 Key = (90 Key<Twox64Concat, CollectionId>,91 Key<Blake2_128, T::CrossAccountId>,92 Key<Blake2_128Concat, T::CrossAccountId>,93 ),94 Value = u128,95 QueryKind = ValueQuery,96 >;97}9899pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);100impl<T: Config> FungibleHandle<T> {101 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {102 Self(inner)103 }104 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {105 self.0106 }107}108impl<T: Config> WithRecorder<T> for FungibleHandle<T> {109 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {110 self.0.recorder()111 }112 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {113 self.0.into_recorder()114 }115}116impl<T: Config> Deref for FungibleHandle<T> {117 type Target = pallet_common::CollectionHandle<T>;118119 fn deref(&self) -> &Self::Target {120 &self.0121 }122}123124impl<T: Config> Pallet<T> {125 pub fn init_collection(126 owner: T::AccountId,127 data: CreateCollectionData<T::AccountId>,128 ) -> Result<CollectionId, DispatchError> {129 <PalletCommon<T>>::init_collection(owner, data)130 }131 pub fn destroy_collection(132 collection: FungibleHandle<T>,133 sender: &T::CrossAccountId,134 ) -> DispatchResult {135 let id = collection.id;136137 // =========138139 PalletCommon::destroy_collection(collection.0, sender)?;140141 <TotalSupply<T>>::remove(id);142 <Balance<T>>::remove_prefix((id,), None);143 <Allowance<T>>::remove_prefix((id,), None);144 Ok(())145 }146147 pub fn burn(148 collection: &FungibleHandle<T>,149 owner: &T::CrossAccountId,150 amount: u128,151 ) -> DispatchResult {152 let total_supply = <TotalSupply<T>>::get(collection.id)153 .checked_sub(amount)154 .ok_or(<CommonError<T>>::TokenValueTooLow)?;155156 let balance = <Balance<T>>::get((collection.id, owner))157 .checked_sub(amount)158 .ok_or(<CommonError<T>>::TokenValueTooLow)?;159160 if collection.access == AccessMode::AllowList {161 collection.check_allowlist(owner)?;162 }163164 // =========165166 if balance == 0 {167 <Balance<T>>::remove((collection.id, owner));168 } else {169 <Balance<T>>::insert((collection.id, owner), balance);170 }171 <TotalSupply<T>>::insert(collection.id, total_supply);172173 collection.log_mirrored(ERC20Events::Transfer {174 from: *owner.as_eth(),175 to: H160::default(),176 value: amount.into(),177 });178 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(179 collection.id,180 TokenId::default(),181 owner.clone(),182 amount,183 ));184 Ok(())185 }186187 pub fn transfer(188 collection: &FungibleHandle<T>,189 from: &T::CrossAccountId,190 to: &T::CrossAccountId,191 amount: u128,192 ) -> DispatchResult {193 ensure!(194 collection.limits.transfers_enabled(),195 <CommonError<T>>::TransferNotAllowed,196 );197198 if collection.access == AccessMode::AllowList {199 collection.check_allowlist(from)?;200 collection.check_allowlist(to)?;201 }202 <PalletCommon<T>>::ensure_correct_receiver(to)?;203204 let balance_from = <Balance<T>>::get((collection.id, from))205 .checked_sub(amount)206 .ok_or(<CommonError<T>>::TokenValueTooLow)?;207 let balance_to = if from != to {208 Some(209 <Balance<T>>::get((collection.id, to))210 .checked_add(amount)211 .ok_or(ArithmeticError::Overflow)?,212 )213 } else {214 None215 };216217 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {218 let handle = <CollectionHandle<T>>::try_get(target.0)?;219 let dispatch = T::CollectionDispatch::dispatch(handle);220 let dispatch = dispatch.as_dyn();221222 // =========223224 dispatch.nest_token(from.clone(), (collection.id, TokenId::default()), target.1)?;225 }226227 if let Some(balance_to) = balance_to {228 // from != to229 if balance_from == 0 {230 <Balance<T>>::remove((collection.id, from));231 } else {232 <Balance<T>>::insert((collection.id, from), balance_from);233 }234 <Balance<T>>::insert((collection.id, to), balance_to);235 }236237 collection.log_mirrored(ERC20Events::Transfer {238 from: *from.as_eth(),239 to: *to.as_eth(),240 value: amount.into(),241 });242 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(243 collection.id,244 TokenId::default(),245 from.clone(),246 to.clone(),247 amount,248 ));249 Ok(())250 }251252 pub fn create_multiple_items(253 collection: &FungibleHandle<T>,254 sender: &T::CrossAccountId,255 data: BTreeMap<T::CrossAccountId, u128>,256 ) -> DispatchResult {257 if !collection.is_owner_or_admin(sender) {258 ensure!(259 collection.mint_mode,260 <CommonError<T>>::PublicMintingNotAllowed261 );262 collection.check_allowlist(sender)?;263264 for (owner, _) in data.iter() {265 collection.check_allowlist(owner)?;266 }267 }268269 let total_supply = data270 .iter()271 .map(|(_, v)| *v)272 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {273 acc.checked_add(v)274 })275 .ok_or(ArithmeticError::Overflow)?;276277 let mut balances = data;278 for (k, v) in balances.iter_mut() {279 *v = <Balance<T>>::get((collection.id, &k))280 .checked_add(*v)281 .ok_or(ArithmeticError::Overflow)?;282 }283284 // =========285286 <TotalSupply<T>>::insert(collection.id, total_supply);287 for (user, amount) in balances {288 <Balance<T>>::insert((collection.id, &user), amount);289290 collection.log_mirrored(ERC20Events::Transfer {291 from: H160::default(),292 to: *user.as_eth(),293 value: amount.into(),294 });295 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(296 collection.id,297 TokenId::default(),298 user.clone(),299 amount,300 ));301 }302303 Ok(())304 }305306 fn set_allowance_unchecked(307 collection: &FungibleHandle<T>,308 owner: &T::CrossAccountId,309 spender: &T::CrossAccountId,310 amount: u128,311 ) {312 if amount == 0 {313 <Allowance<T>>::remove((collection.id, owner, spender));314 } else {315 <Allowance<T>>::insert((collection.id, owner, spender), amount);316 }317318 collection.log_mirrored(ERC20Events::Approval {319 owner: *owner.as_eth(),320 spender: *spender.as_eth(),321 value: amount.into(),322 });323 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(324 collection.id,325 TokenId(0),326 owner.clone(),327 spender.clone(),328 amount,329 ));330 }331332 pub fn set_allowance(333 collection: &FungibleHandle<T>,334 owner: &T::CrossAccountId,335 spender: &T::CrossAccountId,336 amount: u128,337 ) -> DispatchResult {338 if collection.access == AccessMode::AllowList {339 collection.check_allowlist(owner)?;340 collection.check_allowlist(spender)?;341 }342343 if <Balance<T>>::get((collection.id, owner)) < amount {344 ensure!(345 collection.ignores_owned_amount(owner),346 <CommonError<T>>::CantApproveMoreThanOwned347 );348 }349350 // =========351352 Self::set_allowance_unchecked(collection, owner, spender, amount);353 Ok(())354 }355356 pub fn transfer_from(357 collection: &FungibleHandle<T>,358 spender: &T::CrossAccountId,359 from: &T::CrossAccountId,360 to: &T::CrossAccountId,361 amount: u128,362 ) -> DispatchResult {363 if spender.conv_eq(from) {364 return Self::transfer(collection, from, to, amount);365 }366 if collection.access == AccessMode::AllowList {367 // `from`, `to` checked in [`transfer`]368 collection.check_allowlist(spender)?;369 }370371 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);372 if allowance.is_none() {373 ensure!(374 collection.ignores_allowance(spender),375 <CommonError<T>>::ApprovedValueTooLow376 );377 }378379 // =========380381 Self::transfer(collection, from, to, amount)?;382 if let Some(allowance) = allowance {383 Self::set_allowance_unchecked(collection, from, spender, allowance);384 }385 Ok(())386 }387388 pub fn burn_from(389 collection: &FungibleHandle<T>,390 spender: &T::CrossAccountId,391 from: &T::CrossAccountId,392 amount: u128,393 ) -> DispatchResult {394 if spender.conv_eq(from) {395 return Self::burn(collection, from, amount);396 }397 if collection.access == AccessMode::AllowList {398 // `from` checked in [`burn`]399 collection.check_allowlist(spender)?;400 }401402 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);403 if allowance.is_none() {404 ensure!(405 collection.ignores_allowance(spender),406 <CommonError<T>>::ApprovedValueTooLow407 );408 }409410 // =========411412 Self::burn(collection, from, amount)?;413 if let Some(allowance) = allowance {414 Self::set_allowance_unchecked(collection, from, spender, allowance);415 }416 Ok(())417 }418419 /// Delegated to `create_multiple_items`420 pub fn create_item(421 collection: &FungibleHandle<T>,422 sender: &T::CrossAccountId,423 data: CreateItemData<T>,424 ) -> DispatchResult {425 Self::create_multiple_items(collection, sender, [(data.0, data.1)].into_iter().collect())426 }427}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#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::Deref;20use frame_support::{ensure};21use pallet_evm::account::CrossAccountId;22use up_data_structs::{23 AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,24};25use pallet_common::{26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,27 CollectionHandle, dispatch::CollectionDispatch,28};29use pallet_structure::Pallet as PalletStructure;30use pallet_evm_coder_substrate::WithRecorder;31use sp_core::H160;32use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};33use sp_std::collections::btree_map::BTreeMap;3435pub use pallet::*;3637use crate::erc::ERC20Events;38#[cfg(feature = "runtime-benchmarks")]39pub mod benchmarking;40pub mod common;41pub mod erc;42pub mod weights;4344pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);45pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4647#[frame_support::pallet]48pub mod pallet {49 use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};50 use up_data_structs::CollectionId;51 use super::weights::WeightInfo;5253 #[pallet::error]54 pub enum Error<T> {55 /// Not Fungible item data used to mint in Fungible collection.56 NotFungibleDataUsedToMintFungibleCollectionToken,57 /// Not default id passed as TokenId argument58 FungibleItemsHaveNoId,59 /// Tried to set data for fungible item60 FungibleItemsDontHaveData,61 /// Fungible token does not support nested62 FungibleDisallowsNesting,63 }6465 #[pallet::config]66 pub trait Config:67 frame_system::Config + pallet_common::Config + pallet_structure::Config68 {69 type WeightInfo: WeightInfo;70 }7172 #[pallet::pallet]73 #[pallet::generate_store(pub(super) trait Store)]74 pub struct Pallet<T>(_);7576 #[pallet::storage]77 pub type TotalSupply<T: Config> =78 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;7980 #[pallet::storage]81 pub type Balance<T: Config> = StorageNMap<82 Key = (83 Key<Twox64Concat, CollectionId>,84 Key<Blake2_128Concat, T::CrossAccountId>,85 ),86 Value = u128,87 QueryKind = ValueQuery,88 >;8990 #[pallet::storage]91 pub type Allowance<T: Config> = StorageNMap<92 Key = (93 Key<Twox64Concat, CollectionId>,94 Key<Blake2_128, T::CrossAccountId>,95 Key<Blake2_128Concat, T::CrossAccountId>,96 ),97 Value = u128,98 QueryKind = ValueQuery,99 >;100}101102pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);103impl<T: Config> FungibleHandle<T> {104 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {105 Self(inner)106 }107 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {108 self.0109 }110}111impl<T: Config> WithRecorder<T> for FungibleHandle<T> {112 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {113 self.0.recorder()114 }115 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {116 self.0.into_recorder()117 }118}119impl<T: Config> Deref for FungibleHandle<T> {120 type Target = pallet_common::CollectionHandle<T>;121122 fn deref(&self) -> &Self::Target {123 &self.0124 }125}126127impl<T: Config> Pallet<T> {128 pub fn init_collection(129 owner: T::AccountId,130 data: CreateCollectionData<T::AccountId>,131 ) -> Result<CollectionId, DispatchError> {132 <PalletCommon<T>>::init_collection(owner, data)133 }134 pub fn destroy_collection(135 collection: FungibleHandle<T>,136 sender: &T::CrossAccountId,137 ) -> DispatchResult {138 let id = collection.id;139140 // =========141142 PalletCommon::destroy_collection(collection.0, sender)?;143144 <TotalSupply<T>>::remove(id);145 <Balance<T>>::remove_prefix((id,), None);146 <Allowance<T>>::remove_prefix((id,), None);147 Ok(())148 }149150 pub fn burn(151 collection: &FungibleHandle<T>,152 owner: &T::CrossAccountId,153 amount: u128,154 ) -> DispatchResult {155 let total_supply = <TotalSupply<T>>::get(collection.id)156 .checked_sub(amount)157 .ok_or(<CommonError<T>>::TokenValueTooLow)?;158159 let balance = <Balance<T>>::get((collection.id, owner))160 .checked_sub(amount)161 .ok_or(<CommonError<T>>::TokenValueTooLow)?;162163 if collection.access == AccessMode::AllowList {164 collection.check_allowlist(owner)?;165 }166167 // =========168169 if balance == 0 {170 <Balance<T>>::remove((collection.id, owner));171 } else {172 <Balance<T>>::insert((collection.id, owner), balance);173 }174 <TotalSupply<T>>::insert(collection.id, total_supply);175176 collection.log_mirrored(ERC20Events::Transfer {177 from: *owner.as_eth(),178 to: H160::default(),179 value: amount.into(),180 });181 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(182 collection.id,183 TokenId::default(),184 owner.clone(),185 amount,186 ));187 Ok(())188 }189190 pub fn transfer(191 collection: &FungibleHandle<T>,192 from: &T::CrossAccountId,193 to: &T::CrossAccountId,194 amount: u128,195 ) -> DispatchResult {196 ensure!(197 collection.limits.transfers_enabled(),198 <CommonError<T>>::TransferNotAllowed,199 );200201 if collection.access == AccessMode::AllowList {202 collection.check_allowlist(from)?;203 collection.check_allowlist(to)?;204 }205 <PalletCommon<T>>::ensure_correct_receiver(to)?;206207 let balance_from = <Balance<T>>::get((collection.id, from))208 .checked_sub(amount)209 .ok_or(<CommonError<T>>::TokenValueTooLow)?;210 let balance_to = if from != to {211 Some(212 <Balance<T>>::get((collection.id, to))213 .checked_add(amount)214 .ok_or(ArithmeticError::Overflow)?,215 )216 } else {217 None218 };219220 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {221 let handle = <CollectionHandle<T>>::try_get(target.0)?;222 let dispatch = T::CollectionDispatch::dispatch(handle);223 let dispatch = dispatch.as_dyn();224225 // =========226227 dispatch.nest_token(from.clone(), (collection.id, TokenId::default()), target.1)?;228 }229230 if let Some(balance_to) = balance_to {231 // from != to232 if balance_from == 0 {233 <Balance<T>>::remove((collection.id, from));234 } else {235 <Balance<T>>::insert((collection.id, from), balance_from);236 }237 <Balance<T>>::insert((collection.id, to), balance_to);238 }239240 collection.log_mirrored(ERC20Events::Transfer {241 from: *from.as_eth(),242 to: *to.as_eth(),243 value: amount.into(),244 });245 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(246 collection.id,247 TokenId::default(),248 from.clone(),249 to.clone(),250 amount,251 ));252 Ok(())253 }254255 pub fn create_multiple_items(256 collection: &FungibleHandle<T>,257 sender: &T::CrossAccountId,258 data: BTreeMap<T::CrossAccountId, u128>,259 ) -> DispatchResult {260 if !collection.is_owner_or_admin(sender) {261 ensure!(262 collection.mint_mode,263 <CommonError<T>>::PublicMintingNotAllowed264 );265 collection.check_allowlist(sender)?;266267 for (owner, _) in data.iter() {268 collection.check_allowlist(owner)?;269 }270 }271272 let total_supply = data273 .iter()274 .map(|(_, v)| *v)275 .try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {276 acc.checked_add(v)277 })278 .ok_or(ArithmeticError::Overflow)?;279280 let mut balances = data;281 for (k, v) in balances.iter_mut() {282 *v = <Balance<T>>::get((collection.id, &k))283 .checked_add(*v)284 .ok_or(ArithmeticError::Overflow)?;285 }286287 // =========288289 <TotalSupply<T>>::insert(collection.id, total_supply);290 for (user, amount) in balances {291 <Balance<T>>::insert((collection.id, &user), amount);292293 collection.log_mirrored(ERC20Events::Transfer {294 from: H160::default(),295 to: *user.as_eth(),296 value: amount.into(),297 });298 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(299 collection.id,300 TokenId::default(),301 user.clone(),302 amount,303 ));304 }305306 Ok(())307 }308309 fn set_allowance_unchecked(310 collection: &FungibleHandle<T>,311 owner: &T::CrossAccountId,312 spender: &T::CrossAccountId,313 amount: u128,314 ) {315 if amount == 0 {316 <Allowance<T>>::remove((collection.id, owner, spender));317 } else {318 <Allowance<T>>::insert((collection.id, owner, spender), amount);319 }320321 collection.log_mirrored(ERC20Events::Approval {322 owner: *owner.as_eth(),323 spender: *spender.as_eth(),324 value: amount.into(),325 });326 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(327 collection.id,328 TokenId(0),329 owner.clone(),330 spender.clone(),331 amount,332 ));333 }334335 pub fn set_allowance(336 collection: &FungibleHandle<T>,337 owner: &T::CrossAccountId,338 spender: &T::CrossAccountId,339 amount: u128,340 ) -> DispatchResult {341 if collection.access == AccessMode::AllowList {342 collection.check_allowlist(owner)?;343 collection.check_allowlist(spender)?;344 }345346 if <Balance<T>>::get((collection.id, owner)) < amount {347 ensure!(348 collection.ignores_owned_amount(owner),349 <CommonError<T>>::CantApproveMoreThanOwned350 );351 }352353 // =========354355 Self::set_allowance_unchecked(collection, owner, spender, amount);356 Ok(())357 }358359 fn check_allowed(360 collection: &FungibleHandle<T>,361 spender: &T::CrossAccountId,362 from: &T::CrossAccountId,363 amount: u128,364 ) -> Result<Option<u128>, DispatchError> {365 if spender.conv_eq(from) {366 return Ok(None);367 }368 if collection.access == AccessMode::AllowList {369 // `from`, `to` checked in [`transfer`]370 collection.check_allowlist(spender)?;371 }372 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {373 // TODO: should collection owner be allowed to perform this transfer?374 ensure!(375 <PalletStructure<T>>::indirectly_owned(spender.clone(), source.0, source.1, 1)?,376 <CommonError<T>>::ApprovedValueTooLow,377 );378 return Ok(None);379 }380 let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);381 if allowance.is_none() {382 ensure!(383 collection.ignores_allowance(spender),384 <CommonError<T>>::ApprovedValueTooLow385 );386 }387388 Ok(allowance)389 }390391 pub fn transfer_from(392 collection: &FungibleHandle<T>,393 spender: &T::CrossAccountId,394 from: &T::CrossAccountId,395 to: &T::CrossAccountId,396 amount: u128,397 ) -> DispatchResult {398 let allowance = Self::check_allowed(collection, spender, from, amount)?;399400 // =========401402 Self::transfer(collection, from, to, amount)?;403 if let Some(allowance) = allowance {404 Self::set_allowance_unchecked(collection, from, spender, allowance);405 }406 Ok(())407 }408409 pub fn burn_from(410 collection: &FungibleHandle<T>,411 spender: &T::CrossAccountId,412 from: &T::CrossAccountId,413 amount: u128,414 ) -> DispatchResult {415 let allowance = Self::check_allowed(collection, spender, from, amount)?;416417 // =========418419 Self::burn(collection, from, amount)?;420 if let Some(allowance) = allowance {421 Self::set_allowance_unchecked(collection, from, spender, allowance);422 }423 Ok(())424 }425426 /// Delegated to `create_multiple_items`427 pub fn create_item(428 collection: &FungibleHandle<T>,429 sender: &T::CrossAccountId,430 data: CreateItemData<T>,431 ) -> DispatchResult {432 Self::create_multiple_items(collection, sender, [(data.0, data.1)].into_iter().collect())433 }434}