difftreelog
feat burn_from
in: master
12 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -459,6 +459,7 @@
fn transfer() -> Weight;
fn approve() -> Weight;
fn transfer_from() -> Weight;
+ fn burn_from() -> Weight;
fn set_variable_metadata(bytes: u32) -> Weight;
}
@@ -504,6 +505,13 @@
token: TokenId,
amount: u128,
) -> DispatchResultWithPostInfo;
+ fn burn_from(
+ &self,
+ sender: T::CrossAccountId,
+ from: T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> DispatchResultWithPostInfo;
fn set_variable_metadata(
&self,
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -38,6 +38,10 @@
<SelfWeightOf<T>>::transfer_from()
}
+ fn burn_from() -> Weight {
+ 0
+ }
+
fn set_variable_metadata(_bytes: u32) -> Weight {
// Error
0
@@ -156,6 +160,24 @@
)
}
+ fn burn_from(
+ &self,
+ sender: T::CrossAccountId,
+ from: T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> DispatchResultWithPostInfo {
+ ensure!(
+ token == TokenId::default(),
+ <Error<T>>::FungibleItemsHaveNoId
+ );
+
+ with_weight(
+ <Pallet<T>>::burn_from(&self, &sender, &from, amount),
+ <CommonWeights<T>>::burn_from(),
+ )
+ }
+
fn set_variable_metadata(
&self,
_sender: T::CrossAccountId,
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -96,6 +96,18 @@
}
}
+#[solidity_interface(name = "ERC20UniqueExtensions")]
+impl<T: Config> FungibleHandle<T> {
+ fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = T::CrossAccountId::from_eth(from);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+ <Pallet<T>>::burn_from(self, &caller, &from, amount).map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+}
+
#[solidity_interface(name = "UniqueFungible", is(ERC20))]
impl<T: Config> FungibleHandle<T> {}
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -356,6 +356,38 @@
Ok(())
}
+ pub fn burn_from(
+ collection: &FungibleHandle<T>,
+ spender: &T::CrossAccountId,
+ from: &T::CrossAccountId,
+ amount: u128,
+ ) -> DispatchResult {
+ if spender == from {
+ return Self::burn(collection, from, amount);
+ }
+ if collection.access == AccessMode::WhiteList {
+ // `from` checked in [`burn`]
+ collection.check_allowlist(spender)?;
+ }
+
+ let allowance = <Allowance<T>>::get((collection.id, from.as_sub(), spender.as_sub()))
+ .checked_sub(amount);
+ if allowance.is_none() {
+ ensure!(
+ collection.ignores_allowance(spender)?,
+ <CommonError<T>>::TokenValueNotEnough
+ );
+ }
+
+ // =========
+
+ Self::burn(collection, from, amount)?;
+ if let Some(allowance) = allowance {
+ Self::set_allowance_unchecked(collection, from, spender, allowance);
+ }
+ Ok(())
+ }
+
/// Delegated to `create_multiple_items`
pub fn create_item(
collection: &FungibleHandle<T>,
pallets/nft/src/common.rsdiffbeforeafterboth--- a/pallets/nft/src/common.rs
+++ b/pallets/nft/src/common.rs
@@ -45,4 +45,8 @@
fn set_variable_metadata(bytes: u32) -> nft_data_structs::Weight {
dispatch_weight::<T>() + max_weight_of!(set_variable_metadata(bytes))
}
+
+ fn burn_from() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(burn_from())
+ }
}
pallets/nft/src/eth/sponsoring.rsdiffbeforeafterboth--- a/pallets/nft/src/eth/sponsoring.rs
+++ b/pallets/nft/src/eth/sponsoring.rs
@@ -35,9 +35,10 @@
.map_err(|_| AnyError)?
.ok_or(AnyError)?;
match call {
- UniqueNFTCall::ERC721UniqueExtensions(
- ERC721UniqueExtensionsCall::TransferNft { token_id, .. },
- )
+ UniqueNFTCall::ERC721UniqueExtensions(ERC721UniqueExtensionsCall::Transfer {
+ token_id,
+ ..
+ })
| UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, .. }) => {
let token_id: u32 = token_id.try_into().map_err(|_| AnyError)?;
let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,40 OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,41 CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,42};43use pallet_common::{44 account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,45 Error as CommonError, CommonWeightInfo, Allowlist,46};47use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};48use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};49use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5051#[cfg(test)]52mod mock;5354#[cfg(test)]55mod tests;5657mod eth;58mod sponsorship;59pub use sponsorship::NftSponsorshipHandler;60pub use eth::sponsoring::NftEthSponsorshipHandler;6162pub use eth::NftErcSupport;6364pub mod common;65use common::CommonWeights;66pub mod dispatch;67use dispatch::dispatch_call;6869#[cfg(feature = "runtime-benchmarks")]70mod benchmarking;71pub mod weights;72use weights::WeightInfo;7374decl_error! {75 /// Error for non-fungible-token module.76 pub enum Error for Module<T: Config> {77 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.78 CollectionDecimalPointLimitExceeded,79 /// This address is not set as sponsor, use setCollectionSponsor first.80 ConfirmUnsetSponsorFail,81 /// Length of items properties must be greater than 0.82 EmptyArgument,83 /// Collection limit bounds per collection exceeded84 CollectionLimitBoundsExceeded,85 /// Tried to enable permissions which are only permitted to be disabled86 OwnerPermissionsCantBeReverted,87 }88}89pub trait Config:90 system::Config91 + pallet_evm_coder_substrate::Config92 + pallet_common::Config93 + pallet_nonfungible::Config94 + pallet_refungible::Config95 + pallet_fungible::Config96 + Sized97 + TypeInfo98{99 /// Weight information for extrinsics in this pallet.100 type WeightInfo: WeightInfo;101}102103type SelfWeightOf<T> = <T as Config>::WeightInfo;104105// # Used definitions106//107// ## User control levels108//109// chain-controlled - key is uncontrolled by user110// i.e autoincrementing index111// can use non-cryptographic hash112// real - key is controlled by user113// but it is hard to generate enough colliding values, i.e owner of signed txs114// can use non-cryptographic hash115// controlled - key is completly controlled by users116// i.e maps with mutable keys117// should use cryptographic hash118//119// ## User control level downgrade reasons120//121// ?1 - chain-controlled -> controlled122// collections/tokens can be destroyed, resulting in massive holes123// ?2 - chain-controlled -> controlled124// same as ?1, but can be only added, resulting in easier exploitation125// ?3 - real -> controlled126// no confirmation required, so addresses can be easily generated127decl_storage! {128 trait Store for Module<T: Config> as Nft {129130 //#region Private members131 /// Used for migrations132 ChainVersion: u64;133 //#endregion134135 //#region Tokens transfer rate limit baskets136 /// (Collection id (controlled?2), who created (real))137 /// TODO: Off chain worker should remove from this map when collection gets removed138 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;139 /// Collection id (controlled?2), token id (controlled?2)140 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;141 /// Collection id (controlled?2), owning user (real)142 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;143 /// Collection id (controlled?2), token id (controlled?2)144 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;145 //#endregion146147 /// Variable metadata sponsoring148 /// Collection id (controlled?2), token id (controlled?2)149 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;150 }151}152153decl_module! {154 pub struct Module<T: Config> for enum Call155 where156 origin: T::Origin157 {158 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;159 type Error = Error<T>;160161 fn on_initialize(_now: T::BlockNumber) -> Weight {162 0163 }164165 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.166 ///167 /// # Permissions168 ///169 /// * Anyone.170 ///171 /// # Arguments172 ///173 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.174 ///175 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.176 ///177 /// * token_prefix: UTF-8 string with token prefix.178 ///179 /// * mode: [CollectionMode] collection type and type dependent data.180 // returns collection ID181 #[weight = <SelfWeightOf<T>>::create_collection()]182 #[transactional]183 pub fn create_collection(origin,184 collection_name: Vec<u16>,185 collection_description: Vec<u16>,186 token_prefix: Vec<u8>,187 mode: CollectionMode) -> DispatchResult {188189 // Anyone can create a collection190 let who = ensure_signed(origin)?;191192 let limits = CollectionLimits::<T::BlockNumber> {193 sponsored_data_size: CUSTOM_DATA_LIMIT,194 ..Default::default()195 };196197 // Create new collection198 let new_collection = Collection::<T> {199 owner: who.clone(),200 name: collection_name,201 mode: mode.clone(),202 mint_mode: false,203 access: AccessMode::Normal,204 description: collection_description,205 token_prefix,206 offchain_schema: Vec::new(),207 schema_version: SchemaVersion::ImageURL,208 sponsorship: SponsorshipState::Disabled,209 variable_on_chain_schema: Vec::new(),210 const_on_chain_schema: Vec::new(),211 limits,212 transfers_enabled: true,213 meta_update_permission: Default::default(),214 };215216 let _id = match mode {217 CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},218 CollectionMode::Fungible(decimal_points) => {219 // check params220 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);221 PalletFungible::init_collection(new_collection)?222 }223 CollectionMode::ReFungible => {224 PalletRefungible::init_collection(new_collection)?225 }226 };227228 Ok(())229 }230231 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.232 ///233 /// # Permissions234 ///235 /// * Collection Owner.236 ///237 /// # Arguments238 ///239 /// * collection_id: collection to destroy.240 #[weight = <SelfWeightOf<T>>::destroy_collection()]241 #[transactional]242 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {243 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);244245 let collection = <CollectionHandle<T>>::try_get(collection_id)?;246 collection.check_is_owner(&sender)?;247248 // =========249250 match collection.mode {251 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,252 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,253 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,254 }255256 <NftTransferBasket<T>>::remove_prefix(collection_id, None);257 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);258 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);259260 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);261262 Ok(())263 }264265 /// Add an address to white list.266 ///267 /// # Permissions268 ///269 /// * Collection Owner270 /// * Collection Admin271 ///272 /// # Arguments273 ///274 /// * collection_id.275 ///276 /// * address.277 #[weight = <SelfWeightOf<T>>::add_to_white_list()]278 #[transactional]279 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{280281 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);282 let collection = <CollectionHandle<T>>::try_get(collection_id)?;283284 <PalletCommon<T>>::toggle_allowlist(285 &collection,286 &sender,287 &address,288 true,289 )?;290291 Ok(())292 }293294 /// Remove an address from white list.295 ///296 /// # Permissions297 ///298 /// * Collection Owner299 /// * Collection Admin300 ///301 /// # Arguments302 ///303 /// * collection_id.304 ///305 /// * address.306 #[weight = <SelfWeightOf<T>>::remove_from_white_list()]307 #[transactional]308 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{309310 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);311 let collection = <CollectionHandle<T>>::try_get(collection_id)?;312313 <PalletCommon<T>>::toggle_allowlist(314 &collection,315 &sender,316 &address,317 false,318 )?;319320 Ok(())321 }322323 /// Toggle between normal and white list access for the methods with access for `Anyone`.324 ///325 /// # Permissions326 ///327 /// * Collection Owner.328 ///329 /// # Arguments330 ///331 /// * collection_id.332 ///333 /// * mode: [AccessMode]334 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]335 #[transactional]336 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult337 {338 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339340 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;341 target_collection.check_is_owner(&sender)?;342343 target_collection.access = mode;344 target_collection.save()345 }346347 /// Allows Anyone to create tokens if:348 /// * White List is enabled, and349 /// * Address is added to white list, and350 /// * This method was called with True parameter351 ///352 /// # Permissions353 /// * Collection Owner354 ///355 /// # Arguments356 ///357 /// * collection_id.358 ///359 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.360 #[weight = <SelfWeightOf<T>>::set_mint_permission()]361 #[transactional]362 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult363 {364 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365366 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;367 target_collection.check_is_owner(&sender)?;368369 target_collection.mint_mode = mint_permission;370 target_collection.save()371 }372373 /// Change the owner of the collection.374 ///375 /// # Permissions376 ///377 /// * Collection Owner.378 ///379 /// # Arguments380 ///381 /// * collection_id.382 ///383 /// * new_owner.384 #[weight = <SelfWeightOf<T>>::change_collection_owner()]385 #[transactional]386 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {387388 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);389390 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;391 target_collection.check_is_owner(&sender)?;392393 target_collection.owner = new_owner;394 target_collection.save()395 }396397 /// Adds an admin of the Collection.398 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.399 ///400 /// # Permissions401 ///402 /// * Collection Owner.403 /// * Collection Admin.404 ///405 /// # Arguments406 ///407 /// * collection_id: ID of the Collection to add admin for.408 ///409 /// * new_admin_id: Address of new admin to add.410 #[weight = <SelfWeightOf<T>>::add_collection_admin()]411 #[transactional]412 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {413 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);414415 let collection = <CollectionHandle<T>>::try_get(collection_id)?;416 collection.check_is_owner_or_admin(&sender)?;417418 <IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);419 Ok(())420 }421422 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.423 ///424 /// # Permissions425 ///426 /// * Collection Owner.427 /// * Collection Admin.428 ///429 /// # Arguments430 ///431 /// * collection_id: ID of the Collection to remove admin for.432 ///433 /// * account_id: Address of admin to remove.434 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]435 #[transactional]436 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {437 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);438439 let collection = <CollectionHandle<T>>::try_get(collection_id)?;440 collection.check_is_owner_or_admin(&sender)?;441442 <IsAdmin<T>>::remove((collection_id, account_id.as_sub()));443 Ok(())444 }445446 /// # Permissions447 ///448 /// * Collection Owner449 ///450 /// # Arguments451 ///452 /// * collection_id.453 ///454 /// * new_sponsor.455 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]456 #[transactional]457 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {458 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459460 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;461 target_collection.check_is_owner_or_admin(&sender)?;462463 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);464 target_collection.save()465 }466467 /// # Permissions468 ///469 /// * Sponsor.470 ///471 /// # Arguments472 ///473 /// * collection_id.474 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]475 #[transactional]476 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {477 let sender = ensure_signed(origin)?;478479 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;480 ensure!(481 target_collection.sponsorship.pending_sponsor() == Some(&sender),482 Error::<T>::ConfirmUnsetSponsorFail483 );484485 target_collection.sponsorship = SponsorshipState::Confirmed(sender);486 target_collection.save()487 }488489 /// Switch back to pay-per-own-transaction model.490 ///491 /// # Permissions492 ///493 /// * Collection owner.494 ///495 /// # Arguments496 ///497 /// * collection_id.498 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]499 #[transactional]500 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {501 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);502503 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;504 target_collection.check_is_owner(&sender)?;505506 target_collection.sponsorship = SponsorshipState::Disabled;507 target_collection.save()508 }509510 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.511 ///512 /// # Permissions513 ///514 /// * Collection Owner.515 /// * Collection Admin.516 /// * Anyone if517 /// * White List is enabled, and518 /// * Address is added to white list, and519 /// * MintPermission is enabled (see SetMintPermission method)520 ///521 /// # Arguments522 ///523 /// * collection_id: ID of the collection.524 ///525 /// * owner: Address, initial owner of the NFT.526 ///527 /// * data: Token data to store on chain.528 #[weight = <CommonWeights<T>>::create_item()]529 #[transactional]530 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {531 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);532533 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))534 }535536 /// This method creates multiple items in a collection created with CreateCollection method.537 ///538 /// # Permissions539 ///540 /// * Collection Owner.541 /// * Collection Admin.542 /// * Anyone if543 /// * White List is enabled, and544 /// * Address is added to white list, and545 /// * MintPermission is enabled (see SetMintPermission method)546 ///547 /// # Arguments548 ///549 /// * collection_id: ID of the collection.550 ///551 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].552 ///553 /// * owner: Address, initial owner of the NFT.554 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]555 #[transactional]556 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {557 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);558 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);559560 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))561 }562563 // TODO! transaction weight564565 /// Set transfers_enabled value for particular collection566 ///567 /// # Permissions568 ///569 /// * Collection Owner.570 ///571 /// # Arguments572 ///573 /// * collection_id: ID of the collection.574 ///575 /// * value: New flag value.576 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]577 #[transactional]578 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {579 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);580 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581 target_collection.check_is_owner(&sender)?;582583 // =========584585 target_collection.transfers_enabled = value;586 target_collection.save()587 }588589 /// Destroys a concrete instance of NFT.590 ///591 /// # Permissions592 ///593 /// * Collection Owner.594 /// * Collection Admin.595 /// * Current NFT Owner.596 ///597 /// # Arguments598 ///599 /// * collection_id: ID of the collection.600 ///601 /// * item_id: ID of NFT to burn.602 #[weight = <CommonWeights<T>>::burn_item()]603 #[transactional]604 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {605 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606607 dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))608 }609610 /// Destroys a concrete instance of NFT on behalf of the owner611 /// See also: [`approve`]612 ///613 /// # Permissions614 ///615 /// * Collection Owner.616 /// * Collection Admin.617 /// * Current NFT Owner.618 ///619 /// # Arguments620 ///621 /// * collection_id: ID of the collection.622 ///623 /// * item_id: ID of NFT to burn.624 ///625 /// * from: owner of item626 // #[weight = 0]627 // #[transactional]628 // pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> PostDispatchInfo {629 // let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631 // // dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))632 // todo!()633 // }634635 /// Change ownership of the token.636 ///637 /// # Permissions638 ///639 /// * Collection Owner640 /// * Collection Admin641 /// * Current NFT owner642 ///643 /// # Arguments644 ///645 /// * recipient: Address of token recipient.646 ///647 /// * collection_id.648 ///649 /// * item_id: ID of the item650 /// * Non-Fungible Mode: Required.651 /// * Fungible Mode: Ignored.652 /// * Re-Fungible Mode: Required.653 ///654 /// * value: Amount to transfer.655 /// * Non-Fungible Mode: Ignored656 /// * Fungible Mode: Must specify transferred amount657 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)658 #[weight = <CommonWeights<T>>::transfer()]659 #[transactional]660 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {661 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);662663 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))664 }665666 /// Set, change, or remove approved address to transfer the ownership of the NFT.667 ///668 /// # Permissions669 ///670 /// * Collection Owner671 /// * Collection Admin672 /// * Current NFT owner673 ///674 /// # Arguments675 ///676 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).677 ///678 /// * collection_id.679 ///680 /// * item_id: ID of the item.681 #[weight = <CommonWeights<T>>::approve()]682 #[transactional]683 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {684 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);685686 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))687 }688689 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.690 ///691 /// # Permissions692 /// * Collection Owner693 /// * Collection Admin694 /// * Current NFT owner695 /// * Address approved by current NFT owner696 ///697 /// # Arguments698 ///699 /// * from: Address that owns token.700 ///701 /// * recipient: Address of token recipient.702 ///703 /// * collection_id.704 ///705 /// * item_id: ID of the item.706 ///707 /// * value: Amount to transfer.708 #[weight = <CommonWeights<T>>::transfer_from()]709 #[transactional]710 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {711 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);712713 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))714 }715716 /// Set off-chain data schema.717 ///718 /// # Permissions719 ///720 /// * Collection Owner721 /// * Collection Admin722 ///723 /// # Arguments724 ///725 /// * collection_id.726 ///727 /// * schema: String representing the offchain data schema.728 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]729 #[transactional]730 pub fn set_variable_meta_data (731 origin,732 collection_id: CollectionId,733 item_id: TokenId,734 data: Vec<u8>735 ) -> DispatchResultWithPostInfo {736 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);737738 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))739 }740741 /// Set meta_update_permission value for particular collection742 ///743 /// # Permissions744 ///745 /// * Collection Owner.746 ///747 /// # Arguments748 ///749 /// * collection_id: ID of the collection.750 ///751 /// * value: New flag value.752 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]753 #[transactional]754 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {755 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);756 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;757758 ensure!(759 target_collection.meta_update_permission != MetaUpdatePermission::None,760 <CommonError<T>>::MetadataFlagFrozen,761 );762 target_collection.check_is_owner(&sender)?;763764 target_collection.meta_update_permission = value;765766 target_collection.save()767 }768769 /// Set schema standard770 /// ImageURL771 /// Unique772 ///773 /// # Permissions774 ///775 /// * Collection Owner776 /// * Collection Admin777 ///778 /// # Arguments779 ///780 /// * collection_id.781 ///782 /// * schema: SchemaVersion: enum783 #[weight = <SelfWeightOf<T>>::set_schema_version()]784 #[transactional]785 pub fn set_schema_version(786 origin,787 collection_id: CollectionId,788 version: SchemaVersion789 ) -> DispatchResult {790 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);791 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;792 target_collection.check_is_owner_or_admin(&sender)?;793 target_collection.schema_version = version;794 target_collection.save()795 }796797 /// Set off-chain data schema.798 ///799 /// # Permissions800 ///801 /// * Collection Owner802 /// * Collection Admin803 ///804 /// # Arguments805 ///806 /// * collection_id.807 ///808 /// * schema: String representing the offchain data schema.809 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]810 #[transactional]811 pub fn set_offchain_schema(812 origin,813 collection_id: CollectionId,814 schema: Vec<u8>815 ) -> DispatchResult {816 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);817 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;818 target_collection.check_is_owner_or_admin(&sender)?;819820 // check schema limit821 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");822823 target_collection.offchain_schema = schema;824 target_collection.save()825 }826827 /// Set const on-chain data schema.828 ///829 /// # Permissions830 ///831 /// * Collection Owner832 /// * Collection Admin833 ///834 /// # Arguments835 ///836 /// * collection_id.837 ///838 /// * schema: String representing the const on-chain data schema.839 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]840 #[transactional]841 pub fn set_const_on_chain_schema (842 origin,843 collection_id: CollectionId,844 schema: Vec<u8>845 ) -> DispatchResult {846 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);847 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;848 target_collection.check_is_owner_or_admin(&sender)?;849850 // check schema limit851 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");852853 target_collection.const_on_chain_schema = schema;854 target_collection.save()855 }856857 /// Set variable on-chain data schema.858 ///859 /// # Permissions860 ///861 /// * Collection Owner862 /// * Collection Admin863 ///864 /// # Arguments865 ///866 /// * collection_id.867 ///868 /// * schema: String representing the variable on-chain data schema.869 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]870 #[transactional]871 pub fn set_variable_on_chain_schema (872 origin,873 collection_id: CollectionId,874 schema: Vec<u8>875 ) -> DispatchResult {876 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);877 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;878 target_collection.check_is_owner_or_admin(&sender)?;879880 // check schema limit881 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");882883 target_collection.variable_on_chain_schema = schema;884 target_collection.save()885 }886887 #[weight = <SelfWeightOf<T>>::set_collection_limits()]888 #[transactional]889 pub fn set_collection_limits(890 origin,891 collection_id: CollectionId,892 new_limits: CollectionLimits<T::BlockNumber>,893 ) -> DispatchResult {894 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);895 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;896 target_collection.check_is_owner(&sender)?;897 let old_limits = &target_collection.limits;898899 // collection bounds900 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&901 new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&902 new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,903 Error::<T>::CollectionLimitBoundsExceeded);904905 // token_limit check prev906 ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);907 ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);908909 ensure!(910 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&911 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),912 Error::<T>::OwnerPermissionsCantBeReverted,913 );914915 target_collection.limits = new_limits;916917 target_collection.save()918 }919 }920}921922// TODO: limit returned entries?923impl<T: Config> Pallet<T> {924 pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {925 <IsAdmin<T>>::iter_prefix((collection,))926 .map(|(a, _)| a)927 .collect()928 }929 pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {930 <Allowlist<T>>::iter_prefix((collection,))931 .map(|(a, _)| a)932 .collect()933 }934}1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,40 OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,41 CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,42};43use pallet_common::{44 account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,45 Error as CommonError, CommonWeightInfo, Allowlist,46};47use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};48use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};49use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5051#[cfg(test)]52mod mock;5354#[cfg(test)]55mod tests;5657mod eth;58mod sponsorship;59pub use sponsorship::NftSponsorshipHandler;60pub use eth::sponsoring::NftEthSponsorshipHandler;6162pub use eth::NftErcSupport;6364pub mod common;65use common::CommonWeights;66pub mod dispatch;67use dispatch::dispatch_call;6869#[cfg(feature = "runtime-benchmarks")]70mod benchmarking;71pub mod weights;72use weights::WeightInfo;7374decl_error! {75 /// Error for non-fungible-token module.76 pub enum Error for Module<T: Config> {77 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.78 CollectionDecimalPointLimitExceeded,79 /// This address is not set as sponsor, use setCollectionSponsor first.80 ConfirmUnsetSponsorFail,81 /// Length of items properties must be greater than 0.82 EmptyArgument,83 /// Collection limit bounds per collection exceeded84 CollectionLimitBoundsExceeded,85 /// Tried to enable permissions which are only permitted to be disabled86 OwnerPermissionsCantBeReverted,87 }88}89pub trait Config:90 system::Config91 + pallet_evm_coder_substrate::Config92 + pallet_common::Config93 + pallet_nonfungible::Config94 + pallet_refungible::Config95 + pallet_fungible::Config96 + Sized97 + TypeInfo98{99 /// Weight information for extrinsics in this pallet.100 type WeightInfo: WeightInfo;101}102103type SelfWeightOf<T> = <T as Config>::WeightInfo;104105// # Used definitions106//107// ## User control levels108//109// chain-controlled - key is uncontrolled by user110// i.e autoincrementing index111// can use non-cryptographic hash112// real - key is controlled by user113// but it is hard to generate enough colliding values, i.e owner of signed txs114// can use non-cryptographic hash115// controlled - key is completly controlled by users116// i.e maps with mutable keys117// should use cryptographic hash118//119// ## User control level downgrade reasons120//121// ?1 - chain-controlled -> controlled122// collections/tokens can be destroyed, resulting in massive holes123// ?2 - chain-controlled -> controlled124// same as ?1, but can be only added, resulting in easier exploitation125// ?3 - real -> controlled126// no confirmation required, so addresses can be easily generated127decl_storage! {128 trait Store for Module<T: Config> as Nft {129130 //#region Private members131 /// Used for migrations132 ChainVersion: u64;133 //#endregion134135 //#region Tokens transfer rate limit baskets136 /// (Collection id (controlled?2), who created (real))137 /// TODO: Off chain worker should remove from this map when collection gets removed138 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;139 /// Collection id (controlled?2), token id (controlled?2)140 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;141 /// Collection id (controlled?2), owning user (real)142 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;143 /// Collection id (controlled?2), token id (controlled?2)144 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;145 //#endregion146147 /// Variable metadata sponsoring148 /// Collection id (controlled?2), token id (controlled?2)149 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;150 }151}152153decl_module! {154 pub struct Module<T: Config> for enum Call155 where156 origin: T::Origin157 {158 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;159 type Error = Error<T>;160161 fn on_initialize(_now: T::BlockNumber) -> Weight {162 0163 }164165 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.166 ///167 /// # Permissions168 ///169 /// * Anyone.170 ///171 /// # Arguments172 ///173 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.174 ///175 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.176 ///177 /// * token_prefix: UTF-8 string with token prefix.178 ///179 /// * mode: [CollectionMode] collection type and type dependent data.180 // returns collection ID181 #[weight = <SelfWeightOf<T>>::create_collection()]182 #[transactional]183 pub fn create_collection(origin,184 collection_name: Vec<u16>,185 collection_description: Vec<u16>,186 token_prefix: Vec<u8>,187 mode: CollectionMode) -> DispatchResult {188189 // Anyone can create a collection190 let who = ensure_signed(origin)?;191192 let limits = CollectionLimits::<T::BlockNumber> {193 sponsored_data_size: CUSTOM_DATA_LIMIT,194 ..Default::default()195 };196197 // Create new collection198 let new_collection = Collection::<T> {199 owner: who.clone(),200 name: collection_name,201 mode: mode.clone(),202 mint_mode: false,203 access: AccessMode::Normal,204 description: collection_description,205 token_prefix,206 offchain_schema: Vec::new(),207 schema_version: SchemaVersion::ImageURL,208 sponsorship: SponsorshipState::Disabled,209 variable_on_chain_schema: Vec::new(),210 const_on_chain_schema: Vec::new(),211 limits,212 transfers_enabled: true,213 meta_update_permission: Default::default(),214 };215216 let _id = match mode {217 CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},218 CollectionMode::Fungible(decimal_points) => {219 // check params220 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);221 PalletFungible::init_collection(new_collection)?222 }223 CollectionMode::ReFungible => {224 PalletRefungible::init_collection(new_collection)?225 }226 };227228 Ok(())229 }230231 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.232 ///233 /// # Permissions234 ///235 /// * Collection Owner.236 ///237 /// # Arguments238 ///239 /// * collection_id: collection to destroy.240 #[weight = <SelfWeightOf<T>>::destroy_collection()]241 #[transactional]242 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {243 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);244245 let collection = <CollectionHandle<T>>::try_get(collection_id)?;246 collection.check_is_owner(&sender)?;247248 // =========249250 match collection.mode {251 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,252 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,253 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,254 }255256 <NftTransferBasket<T>>::remove_prefix(collection_id, None);257 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);258 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);259260 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);261262 Ok(())263 }264265 /// Add an address to white list.266 ///267 /// # Permissions268 ///269 /// * Collection Owner270 /// * Collection Admin271 ///272 /// # Arguments273 ///274 /// * collection_id.275 ///276 /// * address.277 #[weight = <SelfWeightOf<T>>::add_to_white_list()]278 #[transactional]279 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{280281 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);282 let collection = <CollectionHandle<T>>::try_get(collection_id)?;283284 <PalletCommon<T>>::toggle_allowlist(285 &collection,286 &sender,287 &address,288 true,289 )?;290291 Ok(())292 }293294 /// Remove an address from white list.295 ///296 /// # Permissions297 ///298 /// * Collection Owner299 /// * Collection Admin300 ///301 /// # Arguments302 ///303 /// * collection_id.304 ///305 /// * address.306 #[weight = <SelfWeightOf<T>>::remove_from_white_list()]307 #[transactional]308 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{309310 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);311 let collection = <CollectionHandle<T>>::try_get(collection_id)?;312313 <PalletCommon<T>>::toggle_allowlist(314 &collection,315 &sender,316 &address,317 false,318 )?;319320 Ok(())321 }322323 /// Toggle between normal and white list access for the methods with access for `Anyone`.324 ///325 /// # Permissions326 ///327 /// * Collection Owner.328 ///329 /// # Arguments330 ///331 /// * collection_id.332 ///333 /// * mode: [AccessMode]334 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]335 #[transactional]336 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult337 {338 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339340 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;341 target_collection.check_is_owner(&sender)?;342343 target_collection.access = mode;344 target_collection.save()345 }346347 /// Allows Anyone to create tokens if:348 /// * White List is enabled, and349 /// * Address is added to white list, and350 /// * This method was called with True parameter351 ///352 /// # Permissions353 /// * Collection Owner354 ///355 /// # Arguments356 ///357 /// * collection_id.358 ///359 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.360 #[weight = <SelfWeightOf<T>>::set_mint_permission()]361 #[transactional]362 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult363 {364 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365366 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;367 target_collection.check_is_owner(&sender)?;368369 target_collection.mint_mode = mint_permission;370 target_collection.save()371 }372373 /// Change the owner of the collection.374 ///375 /// # Permissions376 ///377 /// * Collection Owner.378 ///379 /// # Arguments380 ///381 /// * collection_id.382 ///383 /// * new_owner.384 #[weight = <SelfWeightOf<T>>::change_collection_owner()]385 #[transactional]386 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {387388 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);389390 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;391 target_collection.check_is_owner(&sender)?;392393 target_collection.owner = new_owner;394 target_collection.save()395 }396397 /// Adds an admin of the Collection.398 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.399 ///400 /// # Permissions401 ///402 /// * Collection Owner.403 /// * Collection Admin.404 ///405 /// # Arguments406 ///407 /// * collection_id: ID of the Collection to add admin for.408 ///409 /// * new_admin_id: Address of new admin to add.410 #[weight = <SelfWeightOf<T>>::add_collection_admin()]411 #[transactional]412 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {413 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);414415 let collection = <CollectionHandle<T>>::try_get(collection_id)?;416 collection.check_is_owner_or_admin(&sender)?;417418 <IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);419 Ok(())420 }421422 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.423 ///424 /// # Permissions425 ///426 /// * Collection Owner.427 /// * Collection Admin.428 ///429 /// # Arguments430 ///431 /// * collection_id: ID of the Collection to remove admin for.432 ///433 /// * account_id: Address of admin to remove.434 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]435 #[transactional]436 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {437 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);438439 let collection = <CollectionHandle<T>>::try_get(collection_id)?;440 collection.check_is_owner_or_admin(&sender)?;441442 <IsAdmin<T>>::remove((collection_id, account_id.as_sub()));443 Ok(())444 }445446 /// # Permissions447 ///448 /// * Collection Owner449 ///450 /// # Arguments451 ///452 /// * collection_id.453 ///454 /// * new_sponsor.455 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]456 #[transactional]457 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {458 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459460 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;461 target_collection.check_is_owner_or_admin(&sender)?;462463 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);464 target_collection.save()465 }466467 /// # Permissions468 ///469 /// * Sponsor.470 ///471 /// # Arguments472 ///473 /// * collection_id.474 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]475 #[transactional]476 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {477 let sender = ensure_signed(origin)?;478479 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;480 ensure!(481 target_collection.sponsorship.pending_sponsor() == Some(&sender),482 Error::<T>::ConfirmUnsetSponsorFail483 );484485 target_collection.sponsorship = SponsorshipState::Confirmed(sender);486 target_collection.save()487 }488489 /// Switch back to pay-per-own-transaction model.490 ///491 /// # Permissions492 ///493 /// * Collection owner.494 ///495 /// # Arguments496 ///497 /// * collection_id.498 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]499 #[transactional]500 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {501 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);502503 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;504 target_collection.check_is_owner(&sender)?;505506 target_collection.sponsorship = SponsorshipState::Disabled;507 target_collection.save()508 }509510 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.511 ///512 /// # Permissions513 ///514 /// * Collection Owner.515 /// * Collection Admin.516 /// * Anyone if517 /// * White List is enabled, and518 /// * Address is added to white list, and519 /// * MintPermission is enabled (see SetMintPermission method)520 ///521 /// # Arguments522 ///523 /// * collection_id: ID of the collection.524 ///525 /// * owner: Address, initial owner of the NFT.526 ///527 /// * data: Token data to store on chain.528 #[weight = <CommonWeights<T>>::create_item()]529 #[transactional]530 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {531 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);532533 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))534 }535536 /// This method creates multiple items in a collection created with CreateCollection method.537 ///538 /// # Permissions539 ///540 /// * Collection Owner.541 /// * Collection Admin.542 /// * Anyone if543 /// * White List is enabled, and544 /// * Address is added to white list, and545 /// * MintPermission is enabled (see SetMintPermission method)546 ///547 /// # Arguments548 ///549 /// * collection_id: ID of the collection.550 ///551 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].552 ///553 /// * owner: Address, initial owner of the NFT.554 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]555 #[transactional]556 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {557 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);558 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);559560 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))561 }562563 // TODO! transaction weight564565 /// Set transfers_enabled value for particular collection566 ///567 /// # Permissions568 ///569 /// * Collection Owner.570 ///571 /// # Arguments572 ///573 /// * collection_id: ID of the collection.574 ///575 /// * value: New flag value.576 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]577 #[transactional]578 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {579 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);580 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581 target_collection.check_is_owner(&sender)?;582583 // =========584585 target_collection.transfers_enabled = value;586 target_collection.save()587 }588589 /// Destroys a concrete instance of NFT.590 ///591 /// # Permissions592 ///593 /// * Collection Owner.594 /// * Collection Admin.595 /// * Current NFT Owner.596 ///597 /// # Arguments598 ///599 /// * collection_id: ID of the collection.600 ///601 /// * item_id: ID of NFT to burn.602 #[weight = <CommonWeights<T>>::burn_item()]603 #[transactional]604 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {605 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606607 dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))608 }609610 /// Destroys a concrete instance of NFT on behalf of the owner611 /// See also: [`approve`]612 ///613 /// # Permissions614 ///615 /// * Collection Owner.616 /// * Collection Admin.617 /// * Current NFT Owner.618 ///619 /// # Arguments620 ///621 /// * collection_id: ID of the collection.622 ///623 /// * item_id: ID of NFT to burn.624 ///625 /// * from: owner of item626 #[weight = <CommonWeights<T>>::burn_from()]627 #[transactional]628 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {629 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))632 }633634 /// Change ownership of the token.635 ///636 /// # Permissions637 ///638 /// * Collection Owner639 /// * Collection Admin640 /// * Current NFT owner641 ///642 /// # Arguments643 ///644 /// * recipient: Address of token recipient.645 ///646 /// * collection_id.647 ///648 /// * item_id: ID of the item649 /// * Non-Fungible Mode: Required.650 /// * Fungible Mode: Ignored.651 /// * Re-Fungible Mode: Required.652 ///653 /// * value: Amount to transfer.654 /// * Non-Fungible Mode: Ignored655 /// * Fungible Mode: Must specify transferred amount656 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)657 #[weight = <CommonWeights<T>>::transfer()]658 #[transactional]659 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {660 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);661662 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))663 }664665 /// Set, change, or remove approved address to transfer the ownership of the NFT.666 ///667 /// # Permissions668 ///669 /// * Collection Owner670 /// * Collection Admin671 /// * Current NFT owner672 ///673 /// # Arguments674 ///675 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).676 ///677 /// * collection_id.678 ///679 /// * item_id: ID of the item.680 #[weight = <CommonWeights<T>>::approve()]681 #[transactional]682 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {683 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);684685 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))686 }687688 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.689 ///690 /// # Permissions691 /// * Collection Owner692 /// * Collection Admin693 /// * Current NFT owner694 /// * Address approved by current NFT owner695 ///696 /// # Arguments697 ///698 /// * from: Address that owns token.699 ///700 /// * recipient: Address of token recipient.701 ///702 /// * collection_id.703 ///704 /// * item_id: ID of the item.705 ///706 /// * value: Amount to transfer.707 #[weight = <CommonWeights<T>>::transfer_from()]708 #[transactional]709 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {710 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);711712 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))713 }714715 /// Set off-chain data schema.716 ///717 /// # Permissions718 ///719 /// * Collection Owner720 /// * Collection Admin721 ///722 /// # Arguments723 ///724 /// * collection_id.725 ///726 /// * schema: String representing the offchain data schema.727 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]728 #[transactional]729 pub fn set_variable_meta_data (730 origin,731 collection_id: CollectionId,732 item_id: TokenId,733 data: Vec<u8>734 ) -> DispatchResultWithPostInfo {735 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);736737 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))738 }739740 /// Set meta_update_permission value for particular collection741 ///742 /// # Permissions743 ///744 /// * Collection Owner.745 ///746 /// # Arguments747 ///748 /// * collection_id: ID of the collection.749 ///750 /// * value: New flag value.751 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]752 #[transactional]753 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {754 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);755 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;756757 ensure!(758 target_collection.meta_update_permission != MetaUpdatePermission::None,759 <CommonError<T>>::MetadataFlagFrozen,760 );761 target_collection.check_is_owner(&sender)?;762763 target_collection.meta_update_permission = value;764765 target_collection.save()766 }767768 /// Set schema standard769 /// ImageURL770 /// Unique771 ///772 /// # Permissions773 ///774 /// * Collection Owner775 /// * Collection Admin776 ///777 /// # Arguments778 ///779 /// * collection_id.780 ///781 /// * schema: SchemaVersion: enum782 #[weight = <SelfWeightOf<T>>::set_schema_version()]783 #[transactional]784 pub fn set_schema_version(785 origin,786 collection_id: CollectionId,787 version: SchemaVersion788 ) -> DispatchResult {789 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);790 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;791 target_collection.check_is_owner_or_admin(&sender)?;792 target_collection.schema_version = version;793 target_collection.save()794 }795796 /// Set off-chain data schema.797 ///798 /// # Permissions799 ///800 /// * Collection Owner801 /// * Collection Admin802 ///803 /// # Arguments804 ///805 /// * collection_id.806 ///807 /// * schema: String representing the offchain data schema.808 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]809 #[transactional]810 pub fn set_offchain_schema(811 origin,812 collection_id: CollectionId,813 schema: Vec<u8>814 ) -> DispatchResult {815 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);816 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;817 target_collection.check_is_owner_or_admin(&sender)?;818819 // check schema limit820 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");821822 target_collection.offchain_schema = schema;823 target_collection.save()824 }825826 /// Set const on-chain data schema.827 ///828 /// # Permissions829 ///830 /// * Collection Owner831 /// * Collection Admin832 ///833 /// # Arguments834 ///835 /// * collection_id.836 ///837 /// * schema: String representing the const on-chain data schema.838 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]839 #[transactional]840 pub fn set_const_on_chain_schema (841 origin,842 collection_id: CollectionId,843 schema: Vec<u8>844 ) -> DispatchResult {845 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);846 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;847 target_collection.check_is_owner_or_admin(&sender)?;848849 // check schema limit850 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");851852 target_collection.const_on_chain_schema = schema;853 target_collection.save()854 }855856 /// Set variable on-chain data schema.857 ///858 /// # Permissions859 ///860 /// * Collection Owner861 /// * Collection Admin862 ///863 /// # Arguments864 ///865 /// * collection_id.866 ///867 /// * schema: String representing the variable on-chain data schema.868 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]869 #[transactional]870 pub fn set_variable_on_chain_schema (871 origin,872 collection_id: CollectionId,873 schema: Vec<u8>874 ) -> DispatchResult {875 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);876 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;877 target_collection.check_is_owner_or_admin(&sender)?;878879 // check schema limit880 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");881882 target_collection.variable_on_chain_schema = schema;883 target_collection.save()884 }885886 #[weight = <SelfWeightOf<T>>::set_collection_limits()]887 #[transactional]888 pub fn set_collection_limits(889 origin,890 collection_id: CollectionId,891 new_limits: CollectionLimits<T::BlockNumber>,892 ) -> DispatchResult {893 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);894 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;895 target_collection.check_is_owner(&sender)?;896 let old_limits = &target_collection.limits;897898 // collection bounds899 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&900 new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&901 new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,902 Error::<T>::CollectionLimitBoundsExceeded);903904 // token_limit check prev905 ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);906 ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);907908 ensure!(909 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&910 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),911 Error::<T>::OwnerPermissionsCantBeReverted,912 );913914 target_collection.limits = new_limits;915916 target_collection.save()917 }918 }919}920921// TODO: limit returned entries?922impl<T: Config> Pallet<T> {923 pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {924 <IsAdmin<T>>::iter_prefix((collection,))925 .map(|(a, _)| a)926 .collect()927 }928 pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {929 <Allowlist<T>>::iter_prefix((collection,))930 .map(|(a, _)| a)931 .collect()932 }933}pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -39,6 +39,10 @@
<SelfWeightOf<T>>::transfer_from()
}
+ fn burn_from() -> Weight {
+ 0
+ }
+
fn set_variable_metadata(bytes: u32) -> Weight {
<SelfWeightOf<T>>::set_variable_metadata(bytes)
}
@@ -163,6 +167,25 @@
}
}
+ fn burn_from(
+ &self,
+ sender: T::CrossAccountId,
+ from: T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> DispatchResultWithPostInfo {
+ ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+ if amount == 1 {
+ with_weight(
+ <Pallet<T>>::burn_from(&self, &sender, &from, token),
+ <CommonWeights<T>>::burn_from(),
+ )
+ } else {
+ Ok(().into())
+ }
+ }
+
fn set_variable_metadata(
&self,
sender: T::CrossAccountId,
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -264,8 +264,7 @@
#[solidity_interface(name = "ERC721UniqueExtensions")]
impl<T: Config> NonfungibleHandle<T> {
- #[solidity(rename_selector = "transfer")]
- fn transfer_nft(
+ fn transfer(
&mut self,
caller: caller,
to: address,
@@ -280,6 +279,21 @@
Ok(())
}
+ fn burn_from(
+ &mut self,
+ caller: caller,
+ from: address,
+ token_id: uint256,
+ _value: value,
+ ) -> Result<void> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = T::CrossAccountId::from_eth(from);
+ let token = token_id.try_into()?;
+
+ <Pallet<T>>::burn_from(self, &caller, &from, token).map_err(dispatch_to_evm::<T>)?;
+ Ok(())
+ }
+
fn next_token_id(&self) -> Result<uint256> {
Ok(<TokensMinted<T>>::get(self.id)
.checked_add(1)
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -499,6 +499,32 @@
Ok(())
}
+ pub fn burn_from(
+ collection: &NonfungibleHandle<T>,
+ spender: &T::CrossAccountId,
+ from: &T::CrossAccountId,
+ token: TokenId,
+ ) -> DispatchResult {
+ if spender == from {
+ return Self::burn(collection, from, token);
+ }
+ if collection.access == AccessMode::WhiteList {
+ // `from` checked in [`burn`]
+ collection.check_allowlist(spender)?;
+ }
+
+ if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
+ ensure!(
+ collection.ignores_allowance(spender)?,
+ <CommonError<T>>::TokenValueNotEnough
+ );
+ }
+
+ // =========
+
+ Self::burn(collection, &from, token)
+ }
+
pub fn set_variable_metadata(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -59,6 +59,10 @@
)
}
+ fn burn_from() -> Weight {
+ 0
+ }
+
fn set_variable_metadata(bytes: u32) -> Weight {
<SelfWeightOf<T>>::set_variable_metadata(bytes)
}
@@ -161,7 +165,20 @@
) -> DispatchResultWithPostInfo {
with_weight(
<Pallet<T>>::transfer_from(&self, &sender, &from, &to, token, amount),
- <CommonWeights<T>>::approve(),
+ <CommonWeights<T>>::transfer_from(),
+ )
+ }
+
+ fn burn_from(
+ &self,
+ sender: T::CrossAccountId,
+ from: T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> DispatchResultWithPostInfo {
+ with_weight(
+ <Pallet<T>>::burn_from(&self, &sender, &from, token, amount),
+ <CommonWeights<T>>::burn_from(),
)
}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -537,6 +537,39 @@
Ok(())
}
+ pub fn burn_from(
+ collection: &RefungibleHandle<T>,
+ spender: &T::CrossAccountId,
+ from: &T::CrossAccountId,
+ token: TokenId,
+ amount: u128,
+ ) -> DispatchResult {
+ if spender == from {
+ return Self::burn(collection, from, token, amount);
+ }
+ if collection.access == AccessMode::WhiteList {
+ // `from` checked in [`burn`]
+ collection.check_allowlist(spender)?;
+ }
+
+ let allowance = <Allowance<T>>::get((collection.id, token, from.as_sub(), &spender))
+ .checked_sub(amount);
+ if allowance.is_none() {
+ ensure!(
+ collection.ignores_allowance(spender)?,
+ <CommonError<T>>::TokenValueNotEnough
+ );
+ }
+
+ // =========
+
+ Self::burn(collection, from, token, amount)?;
+ if let Some(allowance) = allowance {
+ Self::set_allowance_unchecked(collection, from, spender, token, allowance);
+ }
+ Ok(())
+ }
+
pub fn set_variable_metadata(
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,