difftreelog
CORE-345 Rename module
in: master
5 files changed
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/unique/src/eth/mod.rs
@@ -0,0 +1,288 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+pub mod sponsoring;
+
+use fp_evm::PrecompileResult;
+use pallet_common::{
+ CollectionById,
+ erc::CommonEvmHandler,
+ eth::{map_eth_to_id, map_eth_to_token_id},
+};
+use pallet_fungible::FungibleHandle;
+use pallet_nonfungible::NonfungibleHandle;
+use pallet_refungible::{RefungibleHandle, erc::RefungibleTokenHandle};
+use sp_std::borrow::ToOwned;
+use sp_std::vec::Vec;
+use sp_core::{H160, U256};
+use crate::{CollectionMode, Config, dispatch::Dispatched};
+use pallet_common::CollectionHandle;
+
+pub struct UniqueErcSupport<T: Config>(core::marker::PhantomData<T>);
+
+impl<T: Config> pallet_evm::OnMethodCall<T> for UniqueErcSupport<T> {
+ fn is_reserved(target: &H160) -> bool {
+ map_eth_to_id(target).is_some()
+ }
+ fn is_used(target: &H160) -> bool {
+ map_eth_to_id(target)
+ .map(<CollectionById<T>>::contains_key)
+ .unwrap_or(false)
+ }
+ fn get_code(target: &H160) -> Option<Vec<u8>> {
+ if let Some(collection_id) = map_eth_to_id(target) {
+ let collection = <CollectionById<T>>::get(collection_id)?;
+ Some(
+ match collection.mode {
+ CollectionMode::NFT => <NonfungibleHandle<T>>::CODE,
+ CollectionMode::Fungible(_) => <FungibleHandle<T>>::CODE,
+ CollectionMode::ReFungible => <RefungibleHandle<T>>::CODE,
+ }
+ .to_owned(),
+ )
+ } else if let Some((collection_id, _token_id)) = map_eth_to_token_id(target) {
+ let collection = <CollectionById<T>>::get(collection_id)?;
+ if collection.mode != CollectionMode::ReFungible {
+ return None;
+ }
+ // TODO: check token existence
+ Some(<RefungibleTokenHandle<T>>::CODE.to_owned())
+ } else {
+ None
+ }
+ }
+ fn call(
+ source: &H160,
+ target: &H160,
+ gas_limit: u64,
+ input: &[u8],
+ value: U256,
+ ) -> Option<PrecompileResult> {
+ if let Some(collection_id) = map_eth_to_id(target) {
+ let collection = <CollectionHandle<T>>::new_with_gas_limit(collection_id, gas_limit)?;
+ let dispatched = Dispatched::dispatch(collection);
+
+ match dispatched {
+ Dispatched::Fungible(h) => h.call(source, input, value),
+ Dispatched::Nonfungible(h) => h.call(source, input, value),
+ Dispatched::Refungible(h) => h.call(source, input, value),
+ }
+ } else if let Some((collection_id, token_id)) = map_eth_to_token_id(target) {
+ let collection = <CollectionHandle<T>>::new_with_gas_limit(collection_id, gas_limit)?;
+ if collection.mode != CollectionMode::ReFungible {
+ return None;
+ }
+
+ let handle = RefungibleHandle::cast(collection);
+ // TODO: check token existence
+ RefungibleTokenHandle(handle, token_id).call(source, input, value)
+ } else {
+ None
+ }
+ }
+}
+
+pub mod evm_collection {
+ use core::marker::PhantomData;
+ use evm_coder::{execution::*, generate_stubgen, solidity_interface, types::*, ToLog};
+ use ethereum as _;
+ use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
+ use pallet_evm::{OnMethodCall, PrecompileResult, account::CrossAccountId};
+ use up_data_structs::{
+ CreateCollectionData, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
+ MAX_COLLECTION_NAME_LENGTH,
+ };
+ use frame_support::traits::Get;
+ use sp_core::H160;
+ use pallet_common::{CollectionHandle, save_eth};
+
+ use sp_std::{vec::Vec, rc::Rc};
+ use alloc::format;
+
+ // #[pallet::config]
+ pub trait Config:
+ frame_system::Config
+ + pallet_evm_coder_substrate::Config
+ + pallet_evm::account::Config
+ + pallet_nonfungible::Config
+ {
+ type ContractAddress: Get<H160>;
+ }
+
+ struct EvmCollection<T: Config>(Rc<SubstrateRecorder<T>>);
+ impl<T: Config> WithRecorder<T> for EvmCollection<T> {
+ fn recorder(&self) -> &SubstrateRecorder<T> {
+ &self.0
+ }
+
+ fn into_recorder(self) -> Rc<SubstrateRecorder<T>> {
+ self.0
+ }
+ }
+
+ #[derive(ToLog)]
+ pub enum EthCollectionEvent {
+ CollectionCreated {
+ #[indexed]
+ owner: address,
+ #[indexed]
+ collection_id: address,
+ },
+ }
+
+ #[solidity_interface(name = "Collection")]
+ impl<T: Config> EvmCollection<T> {
+ fn create_721_collection(
+ &self,
+ caller: caller,
+ name: string,
+ description: string,
+ token_prefix: string,
+ ) -> Result<address> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let name = name
+ .encode_utf16()
+ .collect::<Vec<u16>>()
+ .try_into()
+ .map_err(|_| error_feild_too_long(stringify!(name), MAX_COLLECTION_NAME_LENGTH))?;
+ let description = description
+ .encode_utf16()
+ .collect::<Vec<u16>>()
+ .try_into()
+ .map_err(|_| {
+ error_feild_too_long(stringify!(description), MAX_COLLECTION_DESCRIPTION_LENGTH)
+ })?;
+ let token_prefix = token_prefix
+ .into_bytes()
+ .try_into()
+ .map_err(|_| error_feild_too_long(stringify!(token_prefix), MAX_TOKEN_PREFIX_LENGTH))?;
+
+ let data = CreateCollectionData {
+ name,
+ description,
+ token_prefix,
+ ..Default::default()
+ };
+
+ let collection_id =
+ <pallet_nonfungible::Pallet<T>>::init_collection(caller.as_sub().clone(), data)
+ .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+
+ let address = pallet_common::eth::collection_id_to_address(collection_id);
+ self.0.log_mirrored(EthCollectionEvent::CollectionCreated {
+ owner: *caller.as_eth(),
+ collection_id: address,
+ });
+ Ok(address)
+ }
+
+ fn set_sponsor(
+ &self,
+ caller: caller,
+ collection_address: address,
+ sponsor: address,
+ ) -> Result<void> {
+ let mut collection = collection_from_address(collection_address, &self.0)?;
+ check_is_owner(caller, &collection)?;
+
+ let sponsor = T::CrossAccountId::from_eth(sponsor);
+ collection.set_sponsor(sponsor.as_sub().clone());
+ save_eth(collection)
+ }
+
+ fn confirm_sponsorship(&self, caller: caller, collection_address: address) -> Result<void> {
+ let mut collection = collection_from_address(collection_address, &self.0)?;
+ let caller = T::CrossAccountId::from_eth(caller);
+ if !collection.confirm_sponsorship(caller.as_sub()) {
+ return Err(Error::Revert("Caller is not set as sponsor".into()));
+ }
+ save_eth(collection)
+ }
+
+ fn set_limits(
+ &self,
+ caller: caller,
+ collection_address: address,
+ limits_json: string,
+ ) -> Result<void> {
+ let mut collection = collection_from_address(collection_address, &self.0)?;
+ check_is_owner(caller, &collection)?;
+
+ let limits = serde_json_core::from_str(limits_json.as_ref())
+ .map_err(|e| Error::Revert(format!("Parse JSON error: {}", e)))?;
+ collection.limits = limits.0;
+ save_eth(collection)
+ }
+ }
+
+ fn error_feild_too_long(feild: &str, bound: u32) -> Error {
+ Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))
+ }
+
+ fn collection_from_address<T: Config>(
+ collection_address: address,
+ recorder: &Rc<SubstrateRecorder<T>>,
+ ) -> Result<CollectionHandle<T>> {
+ let collection_id = pallet_common::eth::map_eth_to_id(&collection_address)
+ .ok_or(Error::Revert("Contract is not an unique collection".into()))?;
+ let collection =
+ pallet_common::CollectionHandle::new_with_recorder(collection_id, recorder.clone())
+ .ok_or(Error::Revert("Create collection handle error".into()))?;
+ Ok(collection)
+ }
+
+ fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<()> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ collection
+ .check_is_owner(&caller)
+ .map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+ Ok(())
+ }
+
+ pub struct CollectionOnMethodCall<T: Config>(PhantomData<*const T>);
+ impl<T: Config> OnMethodCall<T> for CollectionOnMethodCall<T> {
+ fn is_reserved(contract: &sp_core::H160) -> bool {
+ contract == &T::ContractAddress::get()
+ }
+
+ fn is_used(contract: &sp_core::H160) -> bool {
+ contract == &T::ContractAddress::get()
+ }
+
+ fn call(
+ source: &sp_core::H160,
+ target: &sp_core::H160,
+ gas_left: u64,
+ input: &[u8],
+ value: sp_core::U256,
+ ) -> Option<PrecompileResult> {
+ if target != &T::ContractAddress::get() {
+ return None;
+ }
+
+ let helpers = EvmCollection::<T>(Rc::new(SubstrateRecorder::<T>::new(*target, gas_left)));
+ pallet_evm_coder_substrate::call(*source, helpers, value, input)
+ }
+
+ fn get_code(contract: &sp_core::H160) -> Option<Vec<u8>> {
+ (contract == &T::ContractAddress::get())
+ .then(|| include_bytes!("./stubs/Collection.raw").to_vec())
+ }
+ }
+
+ generate_stubgen!(collection_impl, CollectionCall<()>, true);
+ generate_stubgen!(collection_iface, CollectionCall<()>, false);
+}
pallets/unique/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#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425use frame_support::{26 decl_module, decl_storage, decl_error, decl_event,27 dispatch::DispatchResult,28 ensure,29 weights::{Weight},30 transactional,31 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32 BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38 MAX_COLLECTION_NAME_LENGTH,39 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40 CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,41 CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,42 PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,47 dispatch::CollectionDispatch,48};49pub use eth::pallet_evm_collection;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;53pub mod weights;54use weights::WeightInfo;5556decl_error! {57 /// Error for non-fungible-token module.58 pub enum Error for Module<T: Config> {59 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.60 CollectionDecimalPointLimitExceeded,61 /// This address is not set as sponsor, use setCollectionSponsor first.62 ConfirmUnsetSponsorFail,63 /// Length of items properties must be greater than 0.64 EmptyArgument,65 }66}6768pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {69 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7071 /// Weight information for extrinsics in this pallet.72 type WeightInfo: WeightInfo;73 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;74}7576decl_event! {77 pub enum Event<T>78 where79 <T as frame_system::Config>::AccountId,80 <T as pallet_evm::account::Config>::CrossAccountId,81 {82 /// Collection sponsor was removed83 ///84 /// # Arguments85 ///86 /// * collection_id: Globally unique collection identifier.87 CollectionSponsorRemoved(CollectionId),8889 /// Collection admin was added90 ///91 /// # Arguments92 ///93 /// * collection_id: Globally unique collection identifier.94 ///95 /// * admin: Admin address.96 CollectionAdminAdded(CollectionId, CrossAccountId),9798 /// Collection owned was change99 ///100 /// # Arguments101 ///102 /// * collection_id: Globally unique collection identifier.103 ///104 /// * owner: New owner address.105 CollectionOwnedChanged(CollectionId, AccountId),106107 /// Collection sponsor was set108 ///109 /// # Arguments110 ///111 /// * collection_id: Globally unique collection identifier.112 ///113 /// * owner: New sponsor address.114 CollectionSponsorSet(CollectionId, AccountId),115116 /// const on chain schema was set117 ///118 /// # Arguments119 ///120 /// * collection_id: Globally unique collection identifier.121 ConstOnChainSchemaSet(CollectionId),122123 /// New sponsor was confirm124 ///125 /// # Arguments126 ///127 /// * collection_id: Globally unique collection identifier.128 ///129 /// * sponsor: New sponsor address.130 SponsorshipConfirmed(CollectionId, AccountId),131132 /// Collection admin was removed133 ///134 /// # Arguments135 ///136 /// * collection_id: Globally unique collection identifier.137 ///138 /// * admin: Admin address.139 CollectionAdminRemoved(CollectionId, CrossAccountId),140141 /// Address was remove from allow list142 ///143 /// # Arguments144 ///145 /// * collection_id: Globally unique collection identifier.146 ///147 /// * user: Address.148 AllowListAddressRemoved(CollectionId, CrossAccountId),149150 /// Address was add to allow list151 ///152 /// # Arguments153 ///154 /// * collection_id: Globally unique collection identifier.155 ///156 /// * user: Address.157 AllowListAddressAdded(CollectionId, CrossAccountId),158159 /// Collection limits was set160 ///161 /// # Arguments162 ///163 /// * collection_id: Globally unique collection identifier.164 CollectionLimitSet(CollectionId),165166 CollectionPermissionSet(CollectionId),167168 /// Mint permission was set169 ///170 /// # Arguments171 ///172 /// * collection_id: Globally unique collection identifier.173 MintPermissionSet(CollectionId),174175 /// Offchain schema was set176 ///177 /// # Arguments178 ///179 /// * collection_id: Globally unique collection identifier.180 OffchainSchemaSet(CollectionId),181182 /// Public access mode was set183 ///184 /// # Arguments185 ///186 /// * collection_id: Globally unique collection identifier.187 ///188 /// * mode: New access state.189 PublicAccessModeSet(CollectionId, AccessMode),190191 /// Schema version was set192 ///193 /// # Arguments194 ///195 /// * collection_id: Globally unique collection identifier.196 SchemaVersionSet(CollectionId),197 }198}199200type SelfWeightOf<T> = <T as Config>::WeightInfo;201202// # Used definitions203//204// ## User control levels205//206// chain-controlled - key is uncontrolled by user207// i.e autoincrementing index208// can use non-cryptographic hash209// real - key is controlled by user210// but it is hard to generate enough colliding values, i.e owner of signed txs211// can use non-cryptographic hash212// controlled - key is completly controlled by users213// i.e maps with mutable keys214// should use cryptographic hash215//216// ## User control level downgrade reasons217//218// ?1 - chain-controlled -> controlled219// collections/tokens can be destroyed, resulting in massive holes220// ?2 - chain-controlled -> controlled221// same as ?1, but can be only added, resulting in easier exploitation222// ?3 - real -> controlled223// no confirmation required, so addresses can be easily generated224decl_storage! {225 trait Store for Module<T: Config> as Unique {226227 //#region Private members228 /// Used for migrations229 ChainVersion: u64;230 //#endregion231232 //#region Tokens transfer rate limit baskets233 /// (Collection id (controlled?2), who created (real))234 /// TODO: Off chain worker should remove from this map when collection gets removed235 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;236 /// Collection id (controlled?2), token id (controlled?2)237 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;238 /// Collection id (controlled?2), owning user (real)239 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;240 /// Collection id (controlled?2), token id (controlled?2)241 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;242 //#endregion243244 /// Variable metadata sponsoring245 /// Collection id (controlled?2), token id (controlled?2)246 #[deprecated]247 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;249250 /// Approval sponsoring251 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;252 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;253 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254 }255}256257decl_module! {258 pub struct Module<T: Config> for enum Call259 where260 origin: T::Origin261 {262 type Error = Error<T>;263264 fn deposit_event() = default;265266 fn on_initialize(_now: T::BlockNumber) -> Weight {267 0268 }269270 fn on_runtime_upgrade() -> Weight {271 let limit = None;272273 <VariableMetaDataBasket<T>>::remove_all(limit);274275 0276 }277278 /// 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 of the collection is set to the address that signed the transaction and can be changed later.279 ///280 /// # Permissions281 ///282 /// * Anyone.283 ///284 /// # Arguments285 ///286 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.287 ///288 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.289 ///290 /// * token_prefix: UTF-8 string with token prefix.291 ///292 /// * mode: [CollectionMode] collection type and type dependent data.293 // returns collection ID294 #[weight = <SelfWeightOf<T>>::create_collection()]295 #[transactional]296 #[deprecated]297 pub fn create_collection(origin,298 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,299 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,300 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,301 mode: CollectionMode) -> DispatchResult {302 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {303 name: collection_name,304 description: collection_description,305 token_prefix,306 mode,307 ..Default::default()308 };309 Self::create_collection_ex(origin, data)310 }311312 /// This method creates a collection313 ///314 /// Prefer it to deprecated [`created_collection`] method315 #[weight = <SelfWeightOf<T>>::create_collection()]316 #[transactional]317 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {318 let sender = ensure_signed(origin)?;319320 // =========321322 T::CollectionDispatch::create(sender, data)?;323324 Ok(())325 }326327 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.328 ///329 /// # Permissions330 ///331 /// * Collection Owner.332 ///333 /// # Arguments334 ///335 /// * collection_id: collection to destroy.336 #[weight = <SelfWeightOf<T>>::destroy_collection()]337 #[transactional]338 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {339 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);340 let collection = <CollectionHandle<T>>::try_get(collection_id)?;341342 // =========343344 T::CollectionDispatch::destroy(sender, collection)?;345346 <NftTransferBasket<T>>::remove_prefix(collection_id, None);347 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);348 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);349350 <NftApproveBasket<T>>::remove_prefix(collection_id, None);351 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);352 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);353354 Ok(())355 }356357 /// Add an address to allow list.358 ///359 /// # Permissions360 ///361 /// * Collection Owner362 /// * Collection Admin363 ///364 /// # Arguments365 ///366 /// * collection_id.367 ///368 /// * address.369 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]370 #[transactional]371 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{372373 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);374 let collection = <CollectionHandle<T>>::try_get(collection_id)?;375376 <PalletCommon<T>>::toggle_allowlist(377 &collection,378 &sender,379 &address,380 true,381 )?;382383 Self::deposit_event(Event::<T>::AllowListAddressAdded(384 collection_id,385 address386 ));387388 Ok(())389 }390391 /// Remove an address from allow list.392 ///393 /// # Permissions394 ///395 /// * Collection Owner396 /// * Collection Admin397 ///398 /// # Arguments399 ///400 /// * collection_id.401 ///402 /// * address.403 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]404 #[transactional]405 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{406407 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408 let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410 <PalletCommon<T>>::toggle_allowlist(411 &collection,412 &sender,413 &address,414 false,415 )?;416417 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(418 collection_id,419 address420 ));421422 Ok(())423 }424425 /// Change the owner of the collection.426 ///427 /// # Permissions428 ///429 /// * Collection Owner.430 ///431 /// # Arguments432 ///433 /// * collection_id.434 ///435 /// * new_owner.436 #[weight = <SelfWeightOf<T>>::change_collection_owner()]437 #[transactional]438 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {439440 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);441442 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;443 target_collection.check_is_owner(&sender)?;444445 target_collection.owner = new_owner.clone();446 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(447 collection_id,448 new_owner449 ));450451 target_collection.save()452 }453454 /// Adds an admin of the Collection.455 /// 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.456 ///457 /// # Permissions458 ///459 /// * Collection Owner.460 /// * Collection Admin.461 ///462 /// # Arguments463 ///464 /// * collection_id: ID of the Collection to add admin for.465 ///466 /// * new_admin_id: Address of new admin to add.467 #[weight = <SelfWeightOf<T>>::add_collection_admin()]468 #[transactional]469 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {470 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);471 let collection = <CollectionHandle<T>>::try_get(collection_id)?;472473 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(474 collection_id,475 new_admin_id.clone()476 ));477478 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)479 }480481 /// 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.482 ///483 /// # Permissions484 ///485 /// * Collection Owner.486 /// * Collection Admin.487 ///488 /// # Arguments489 ///490 /// * collection_id: ID of the Collection to remove admin for.491 ///492 /// * account_id: Address of admin to remove.493 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]494 #[transactional]495 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {496 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497 let collection = <CollectionHandle<T>>::try_get(collection_id)?;498499 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(500 collection_id,501 account_id.clone()502 ));503504 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)505 }506507 /// # Permissions508 ///509 /// * Collection Owner510 ///511 /// # Arguments512 ///513 /// * collection_id.514 ///515 /// * new_sponsor.516 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]517 #[transactional]518 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {519 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);520521 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;522 target_collection.check_is_owner(&sender)?;523524 target_collection.set_sponsor(new_sponsor.clone());525526 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(527 collection_id,528 new_sponsor529 ));530531 target_collection.save()532 }533534 /// # Permissions535 ///536 /// * Sponsor.537 ///538 /// # Arguments539 ///540 /// * collection_id.541 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]542 #[transactional]543 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {544 let sender = ensure_signed(origin)?;545546 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;547 ensure!(548 target_collection.confirm_sponsorship(&sender),549 Error::<T>::ConfirmUnsetSponsorFail550 );551552 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(553 collection_id,554 sender555 ));556557 target_collection.save()558 }559560 /// Switch back to pay-per-own-transaction model.561 ///562 /// # Permissions563 ///564 /// * Collection owner.565 ///566 /// # Arguments567 ///568 /// * collection_id.569 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]570 #[transactional]571 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {572 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);573574 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;575 target_collection.check_is_owner(&sender)?;576577 target_collection.sponsorship = SponsorshipState::Disabled;578579 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(580 collection_id581 ));582 target_collection.save()583 }584585 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.586 ///587 /// # Permissions588 ///589 /// * Collection Owner.590 /// * Collection Admin.591 /// * Anyone if592 /// * Allow List is enabled, and593 /// * Address is added to allow list, and594 /// * MintPermission is enabled (see SetMintPermission method)595 ///596 /// # Arguments597 ///598 /// * collection_id: ID of the collection.599 ///600 /// * owner: Address, initial owner of the NFT.601 ///602 /// * data: Token data to store on chain.603 #[weight = T::CommonWeightInfo::create_item()]604 #[transactional]605 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {606 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);607 let budget = budget::Value::new(2);608609 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))610 }611612 /// This method creates multiple items in a collection created with CreateCollection method.613 ///614 /// # Permissions615 ///616 /// * Collection Owner.617 /// * Collection Admin.618 /// * Anyone if619 /// * Allow List is enabled, and620 /// * Address is added to allow list, and621 /// * MintPermission is enabled (see SetMintPermission method)622 ///623 /// # Arguments624 ///625 /// * collection_id: ID of the collection.626 ///627 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].628 ///629 /// * owner: Address, initial owner of the NFT.630 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]631 #[transactional]632 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {633 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);634 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635 let budget = budget::Value::new(2);636637 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))638 }639640 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]641 #[transactional]642 pub fn set_collection_properties(643 origin,644 collection_id: CollectionId,645 properties: Vec<Property>646 ) -> DispatchResultWithPostInfo {647 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);648649 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);650651 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))652 }653654 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]655 #[transactional]656 pub fn delete_collection_properties(657 origin,658 collection_id: CollectionId,659 property_keys: Vec<PropertyKey>,660 ) -> DispatchResultWithPostInfo {661 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);662663 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664665 dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))666 }667668 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]669 #[transactional]670 pub fn set_token_properties(671 origin,672 collection_id: CollectionId,673 token_id: TokenId,674 properties: Vec<Property>675 ) -> DispatchResultWithPostInfo {676 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);677678 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);679680 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))681 }682683 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]684 #[transactional]685 pub fn delete_token_properties(686 origin,687 collection_id: CollectionId,688 token_id: TokenId,689 property_keys: Vec<PropertyKey>690 ) -> DispatchResultWithPostInfo {691 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);692693 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);694695 dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))696 }697698 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]699 #[transactional]700 pub fn set_property_permissions(701 origin,702 collection_id: CollectionId,703 property_permissions: Vec<PropertyKeyPermission>,704 ) -> DispatchResultWithPostInfo {705 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);706707 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);708709 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))710 }711712 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]713 #[transactional]714 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {715 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716 let budget = budget::Value::new(2);717718 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))719 }720721 // TODO! transaction weight722723 /// Set transfers_enabled value for particular collection724 ///725 /// # Permissions726 ///727 /// * Collection Owner.728 ///729 /// # Arguments730 ///731 /// * collection_id: ID of the collection.732 ///733 /// * value: New flag value.734 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]735 #[transactional]736 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {737 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;739 target_collection.check_is_owner(&sender)?;740741 // =========742743 target_collection.limits.transfers_enabled = Some(value);744 target_collection.save()745 }746747 /// Destroys a concrete instance of NFT.748 ///749 /// # Permissions750 ///751 /// * Collection Owner.752 /// * Collection Admin.753 /// * Current NFT Owner.754 ///755 /// # Arguments756 ///757 /// * collection_id: ID of the collection.758 ///759 /// * item_id: ID of NFT to burn.760 #[weight = T::CommonWeightInfo::burn_item()]761 #[transactional]762 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {763 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);764765 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;766 if value == 1 {767 <NftTransferBasket<T>>::remove(collection_id, item_id);768 <NftApproveBasket<T>>::remove(collection_id, item_id);769 }770 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?771 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());772 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));773 Ok(post_info)774 }775776 /// Destroys a concrete instance of NFT on behalf of the owner777 /// See also: [`approve`]778 ///779 /// # Permissions780 ///781 /// * Collection Owner.782 /// * Collection Admin.783 /// * Current NFT Owner.784 ///785 /// # Arguments786 ///787 /// * collection_id: ID of the collection.788 ///789 /// * item_id: ID of NFT to burn.790 ///791 /// * from: owner of item792 #[weight = T::CommonWeightInfo::burn_from()]793 #[transactional]794 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {795 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);796 let budget = budget::Value::new(2);797798 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))799 }800801 /// Change ownership of the token.802 ///803 /// # Permissions804 ///805 /// * Collection Owner806 /// * Collection Admin807 /// * Current NFT owner808 ///809 /// # Arguments810 ///811 /// * recipient: Address of token recipient.812 ///813 /// * collection_id.814 ///815 /// * item_id: ID of the item816 /// * Non-Fungible Mode: Required.817 /// * Fungible Mode: Ignored.818 /// * Re-Fungible Mode: Required.819 ///820 /// * value: Amount to transfer.821 /// * Non-Fungible Mode: Ignored822 /// * Fungible Mode: Must specify transferred amount823 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)824 #[weight = T::CommonWeightInfo::transfer()]825 #[transactional]826 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {827 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);828 let budget = budget::Value::new(2);829830 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))831 }832833 /// Set, change, or remove approved address to transfer the ownership of the NFT.834 ///835 /// # Permissions836 ///837 /// * Collection Owner838 /// * Collection Admin839 /// * Current NFT owner840 ///841 /// # Arguments842 ///843 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).844 ///845 /// * collection_id.846 ///847 /// * item_id: ID of the item.848 #[weight = T::CommonWeightInfo::approve()]849 #[transactional]850 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {851 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);852853 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))854 }855856 /// 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.857 ///858 /// # Permissions859 /// * Collection Owner860 /// * Collection Admin861 /// * Current NFT owner862 /// * Address approved by current NFT owner863 ///864 /// # Arguments865 ///866 /// * from: Address that owns token.867 ///868 /// * recipient: Address of token recipient.869 ///870 /// * collection_id.871 ///872 /// * item_id: ID of the item.873 ///874 /// * value: Amount to transfer.875 #[weight = T::CommonWeightInfo::transfer_from()]876 #[transactional]877 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {878 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879 let budget = budget::Value::new(2);880881 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))882 }883884 #[weight = <SelfWeightOf<T>>::set_collection_limits()]885 #[transactional]886 pub fn set_collection_limits(887 origin,888 collection_id: CollectionId,889 new_limit: CollectionLimits,890 ) -> DispatchResult {891 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);892 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;893 target_collection.check_is_owner(&sender)?;894 let old_limit = &target_collection.limits;895896 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;897898 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(899 collection_id900 ));901902 target_collection.save()903 }904905 #[weight = <SelfWeightOf<T>>::set_collection_limits()]906 #[transactional]907 pub fn set_collection_permissions(908 origin,909 collection_id: CollectionId,910 new_limit: CollectionPermissions,911 ) -> DispatchResult {912 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);913 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;914 target_collection.check_is_owner(&sender)?;915 let old_limit = &target_collection.permissions;916917 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;918919 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(920 collection_id921 ));922923 target_collection.save()924 }925 }926}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#![recursion_limit = "1024"]18#![cfg_attr(not(feature = "std"), no_std)]19#![allow(20 clippy::too_many_arguments,21 clippy::unnecessary_mut_passed,22 clippy::unused_unit23)]2425use frame_support::{26 decl_module, decl_storage, decl_error, decl_event,27 dispatch::DispatchResult,28 ensure,29 weights::{Weight},30 transactional,31 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},32 BoundedVec,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use up_data_structs::{38 MAX_COLLECTION_NAME_LENGTH,39 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData,40 CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, SponsorshipState,41 CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,42 PropertyKeyPermission,43};44use pallet_evm::account::CrossAccountId;45use pallet_common::{46 CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_call,47 dispatch::CollectionDispatch,48};49pub use eth::evm_collection;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;53pub mod weights;54use weights::WeightInfo;5556decl_error! {57 /// Error for non-fungible-token module.58 pub enum Error for Module<T: Config> {59 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.60 CollectionDecimalPointLimitExceeded,61 /// This address is not set as sponsor, use setCollectionSponsor first.62 ConfirmUnsetSponsorFail,63 /// Length of items properties must be greater than 0.64 EmptyArgument,65 }66}6768pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {69 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;7071 /// Weight information for extrinsics in this pallet.72 type WeightInfo: WeightInfo;73 type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;74}7576decl_event! {77 pub enum Event<T>78 where79 <T as frame_system::Config>::AccountId,80 <T as pallet_evm::account::Config>::CrossAccountId,81 {82 /// Collection sponsor was removed83 ///84 /// # Arguments85 ///86 /// * collection_id: Globally unique collection identifier.87 CollectionSponsorRemoved(CollectionId),8889 /// Collection admin was added90 ///91 /// # Arguments92 ///93 /// * collection_id: Globally unique collection identifier.94 ///95 /// * admin: Admin address.96 CollectionAdminAdded(CollectionId, CrossAccountId),9798 /// Collection owned was change99 ///100 /// # Arguments101 ///102 /// * collection_id: Globally unique collection identifier.103 ///104 /// * owner: New owner address.105 CollectionOwnedChanged(CollectionId, AccountId),106107 /// Collection sponsor was set108 ///109 /// # Arguments110 ///111 /// * collection_id: Globally unique collection identifier.112 ///113 /// * owner: New sponsor address.114 CollectionSponsorSet(CollectionId, AccountId),115116 /// const on chain schema was set117 ///118 /// # Arguments119 ///120 /// * collection_id: Globally unique collection identifier.121 ConstOnChainSchemaSet(CollectionId),122123 /// New sponsor was confirm124 ///125 /// # Arguments126 ///127 /// * collection_id: Globally unique collection identifier.128 ///129 /// * sponsor: New sponsor address.130 SponsorshipConfirmed(CollectionId, AccountId),131132 /// Collection admin was removed133 ///134 /// # Arguments135 ///136 /// * collection_id: Globally unique collection identifier.137 ///138 /// * admin: Admin address.139 CollectionAdminRemoved(CollectionId, CrossAccountId),140141 /// Address was remove from allow list142 ///143 /// # Arguments144 ///145 /// * collection_id: Globally unique collection identifier.146 ///147 /// * user: Address.148 AllowListAddressRemoved(CollectionId, CrossAccountId),149150 /// Address was add to allow list151 ///152 /// # Arguments153 ///154 /// * collection_id: Globally unique collection identifier.155 ///156 /// * user: Address.157 AllowListAddressAdded(CollectionId, CrossAccountId),158159 /// Collection limits was set160 ///161 /// # Arguments162 ///163 /// * collection_id: Globally unique collection identifier.164 CollectionLimitSet(CollectionId),165166 CollectionPermissionSet(CollectionId),167168 /// Mint permission was set169 ///170 /// # Arguments171 ///172 /// * collection_id: Globally unique collection identifier.173 MintPermissionSet(CollectionId),174175 /// Offchain schema was set176 ///177 /// # Arguments178 ///179 /// * collection_id: Globally unique collection identifier.180 OffchainSchemaSet(CollectionId),181182 /// Public access mode was set183 ///184 /// # Arguments185 ///186 /// * collection_id: Globally unique collection identifier.187 ///188 /// * mode: New access state.189 PublicAccessModeSet(CollectionId, AccessMode),190191 /// Schema version was set192 ///193 /// # Arguments194 ///195 /// * collection_id: Globally unique collection identifier.196 SchemaVersionSet(CollectionId),197 }198}199200type SelfWeightOf<T> = <T as Config>::WeightInfo;201202// # Used definitions203//204// ## User control levels205//206// chain-controlled - key is uncontrolled by user207// i.e autoincrementing index208// can use non-cryptographic hash209// real - key is controlled by user210// but it is hard to generate enough colliding values, i.e owner of signed txs211// can use non-cryptographic hash212// controlled - key is completly controlled by users213// i.e maps with mutable keys214// should use cryptographic hash215//216// ## User control level downgrade reasons217//218// ?1 - chain-controlled -> controlled219// collections/tokens can be destroyed, resulting in massive holes220// ?2 - chain-controlled -> controlled221// same as ?1, but can be only added, resulting in easier exploitation222// ?3 - real -> controlled223// no confirmation required, so addresses can be easily generated224decl_storage! {225 trait Store for Module<T: Config> as Unique {226227 //#region Private members228 /// Used for migrations229 ChainVersion: u64;230 //#endregion231232 //#region Tokens transfer rate limit baskets233 /// (Collection id (controlled?2), who created (real))234 /// TODO: Off chain worker should remove from this map when collection gets removed235 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;236 /// Collection id (controlled?2), token id (controlled?2)237 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;238 /// Collection id (controlled?2), owning user (real)239 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;240 /// Collection id (controlled?2), token id (controlled?2)241 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;242 //#endregion243244 /// Variable metadata sponsoring245 /// Collection id (controlled?2), token id (controlled?2)246 #[deprecated]247 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;248 pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;249250 /// Approval sponsoring251 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;252 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;253 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254 }255}256257decl_module! {258 pub struct Module<T: Config> for enum Call259 where260 origin: T::Origin261 {262 type Error = Error<T>;263264 fn deposit_event() = default;265266 fn on_initialize(_now: T::BlockNumber) -> Weight {267 0268 }269270 fn on_runtime_upgrade() -> Weight {271 let limit = None;272273 <VariableMetaDataBasket<T>>::remove_all(limit);274275 0276 }277278 /// 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 of the collection is set to the address that signed the transaction and can be changed later.279 ///280 /// # Permissions281 ///282 /// * Anyone.283 ///284 /// # Arguments285 ///286 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.287 ///288 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.289 ///290 /// * token_prefix: UTF-8 string with token prefix.291 ///292 /// * mode: [CollectionMode] collection type and type dependent data.293 // returns collection ID294 #[weight = <SelfWeightOf<T>>::create_collection()]295 #[transactional]296 #[deprecated]297 pub fn create_collection(origin,298 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,299 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,300 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,301 mode: CollectionMode) -> DispatchResult {302 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {303 name: collection_name,304 description: collection_description,305 token_prefix,306 mode,307 ..Default::default()308 };309 Self::create_collection_ex(origin, data)310 }311312 /// This method creates a collection313 ///314 /// Prefer it to deprecated [`created_collection`] method315 #[weight = <SelfWeightOf<T>>::create_collection()]316 #[transactional]317 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {318 let sender = ensure_signed(origin)?;319320 // =========321322 T::CollectionDispatch::create(sender, data)?;323324 Ok(())325 }326327 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.328 ///329 /// # Permissions330 ///331 /// * Collection Owner.332 ///333 /// # Arguments334 ///335 /// * collection_id: collection to destroy.336 #[weight = <SelfWeightOf<T>>::destroy_collection()]337 #[transactional]338 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {339 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);340 let collection = <CollectionHandle<T>>::try_get(collection_id)?;341342 // =========343344 T::CollectionDispatch::destroy(sender, collection)?;345346 <NftTransferBasket<T>>::remove_prefix(collection_id, None);347 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);348 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);349350 <NftApproveBasket<T>>::remove_prefix(collection_id, None);351 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);352 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);353354 Ok(())355 }356357 /// Add an address to allow list.358 ///359 /// # Permissions360 ///361 /// * Collection Owner362 /// * Collection Admin363 ///364 /// # Arguments365 ///366 /// * collection_id.367 ///368 /// * address.369 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]370 #[transactional]371 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{372373 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);374 let collection = <CollectionHandle<T>>::try_get(collection_id)?;375376 <PalletCommon<T>>::toggle_allowlist(377 &collection,378 &sender,379 &address,380 true,381 )?;382383 Self::deposit_event(Event::<T>::AllowListAddressAdded(384 collection_id,385 address386 ));387388 Ok(())389 }390391 /// Remove an address from allow list.392 ///393 /// # Permissions394 ///395 /// * Collection Owner396 /// * Collection Admin397 ///398 /// # Arguments399 ///400 /// * collection_id.401 ///402 /// * address.403 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]404 #[transactional]405 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{406407 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408 let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410 <PalletCommon<T>>::toggle_allowlist(411 &collection,412 &sender,413 &address,414 false,415 )?;416417 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(418 collection_id,419 address420 ));421422 Ok(())423 }424425 /// Change the owner of the collection.426 ///427 /// # Permissions428 ///429 /// * Collection Owner.430 ///431 /// # Arguments432 ///433 /// * collection_id.434 ///435 /// * new_owner.436 #[weight = <SelfWeightOf<T>>::change_collection_owner()]437 #[transactional]438 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {439440 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);441442 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;443 target_collection.check_is_owner(&sender)?;444445 target_collection.owner = new_owner.clone();446 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(447 collection_id,448 new_owner449 ));450451 target_collection.save()452 }453454 /// Adds an admin of the Collection.455 /// 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.456 ///457 /// # Permissions458 ///459 /// * Collection Owner.460 /// * Collection Admin.461 ///462 /// # Arguments463 ///464 /// * collection_id: ID of the Collection to add admin for.465 ///466 /// * new_admin_id: Address of new admin to add.467 #[weight = <SelfWeightOf<T>>::add_collection_admin()]468 #[transactional]469 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {470 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);471 let collection = <CollectionHandle<T>>::try_get(collection_id)?;472473 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(474 collection_id,475 new_admin_id.clone()476 ));477478 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)479 }480481 /// 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.482 ///483 /// # Permissions484 ///485 /// * Collection Owner.486 /// * Collection Admin.487 ///488 /// # Arguments489 ///490 /// * collection_id: ID of the Collection to remove admin for.491 ///492 /// * account_id: Address of admin to remove.493 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]494 #[transactional]495 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {496 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497 let collection = <CollectionHandle<T>>::try_get(collection_id)?;498499 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(500 collection_id,501 account_id.clone()502 ));503504 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)505 }506507 /// # Permissions508 ///509 /// * Collection Owner510 ///511 /// # Arguments512 ///513 /// * collection_id.514 ///515 /// * new_sponsor.516 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]517 #[transactional]518 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {519 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);520521 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;522 target_collection.check_is_owner(&sender)?;523524 target_collection.set_sponsor(new_sponsor.clone());525526 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(527 collection_id,528 new_sponsor529 ));530531 target_collection.save()532 }533534 /// # Permissions535 ///536 /// * Sponsor.537 ///538 /// # Arguments539 ///540 /// * collection_id.541 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]542 #[transactional]543 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {544 let sender = ensure_signed(origin)?;545546 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;547 ensure!(548 target_collection.confirm_sponsorship(&sender),549 Error::<T>::ConfirmUnsetSponsorFail550 );551552 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(553 collection_id,554 sender555 ));556557 target_collection.save()558 }559560 /// Switch back to pay-per-own-transaction model.561 ///562 /// # Permissions563 ///564 /// * Collection owner.565 ///566 /// # Arguments567 ///568 /// * collection_id.569 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]570 #[transactional]571 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {572 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);573574 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;575 target_collection.check_is_owner(&sender)?;576577 target_collection.sponsorship = SponsorshipState::Disabled;578579 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(580 collection_id581 ));582 target_collection.save()583 }584585 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.586 ///587 /// # Permissions588 ///589 /// * Collection Owner.590 /// * Collection Admin.591 /// * Anyone if592 /// * Allow List is enabled, and593 /// * Address is added to allow list, and594 /// * MintPermission is enabled (see SetMintPermission method)595 ///596 /// # Arguments597 ///598 /// * collection_id: ID of the collection.599 ///600 /// * owner: Address, initial owner of the NFT.601 ///602 /// * data: Token data to store on chain.603 #[weight = T::CommonWeightInfo::create_item()]604 #[transactional]605 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {606 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);607 let budget = budget::Value::new(2);608609 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))610 }611612 /// This method creates multiple items in a collection created with CreateCollection method.613 ///614 /// # Permissions615 ///616 /// * Collection Owner.617 /// * Collection Admin.618 /// * Anyone if619 /// * Allow List is enabled, and620 /// * Address is added to allow list, and621 /// * MintPermission is enabled (see SetMintPermission method)622 ///623 /// # Arguments624 ///625 /// * collection_id: ID of the collection.626 ///627 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].628 ///629 /// * owner: Address, initial owner of the NFT.630 #[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]631 #[transactional]632 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {633 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);634 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635 let budget = budget::Value::new(2);636637 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))638 }639640 #[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]641 #[transactional]642 pub fn set_collection_properties(643 origin,644 collection_id: CollectionId,645 properties: Vec<Property>646 ) -> DispatchResultWithPostInfo {647 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);648649 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);650651 dispatch_call::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))652 }653654 #[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]655 #[transactional]656 pub fn delete_collection_properties(657 origin,658 collection_id: CollectionId,659 property_keys: Vec<PropertyKey>,660 ) -> DispatchResultWithPostInfo {661 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);662663 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664665 dispatch_call::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))666 }667668 #[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]669 #[transactional]670 pub fn set_token_properties(671 origin,672 collection_id: CollectionId,673 token_id: TokenId,674 properties: Vec<Property>675 ) -> DispatchResultWithPostInfo {676 ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);677678 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);679680 dispatch_call::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties))681 }682683 #[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]684 #[transactional]685 pub fn delete_token_properties(686 origin,687 collection_id: CollectionId,688 token_id: TokenId,689 property_keys: Vec<PropertyKey>690 ) -> DispatchResultWithPostInfo {691 ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);692693 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);694695 dispatch_call::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys))696 }697698 #[weight = T::CommonWeightInfo::set_property_permissions(property_permissions.len() as u32)]699 #[transactional]700 pub fn set_property_permissions(701 origin,702 collection_id: CollectionId,703 property_permissions: Vec<PropertyKeyPermission>,704 ) -> DispatchResultWithPostInfo {705 ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);706707 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);708709 dispatch_call::<T, _>(collection_id, |d| d.set_property_permissions(&sender, property_permissions))710 }711712 #[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]713 #[transactional]714 pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {715 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716 let budget = budget::Value::new(2);717718 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))719 }720721 // TODO! transaction weight722723 /// Set transfers_enabled value for particular collection724 ///725 /// # Permissions726 ///727 /// * Collection Owner.728 ///729 /// # Arguments730 ///731 /// * collection_id: ID of the collection.732 ///733 /// * value: New flag value.734 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]735 #[transactional]736 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {737 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;739 target_collection.check_is_owner(&sender)?;740741 // =========742743 target_collection.limits.transfers_enabled = Some(value);744 target_collection.save()745 }746747 /// Destroys a concrete instance of NFT.748 ///749 /// # Permissions750 ///751 /// * Collection Owner.752 /// * Collection Admin.753 /// * Current NFT Owner.754 ///755 /// # Arguments756 ///757 /// * collection_id: ID of the collection.758 ///759 /// * item_id: ID of NFT to burn.760 #[weight = T::CommonWeightInfo::burn_item()]761 #[transactional]762 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {763 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);764765 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;766 if value == 1 {767 <NftTransferBasket<T>>::remove(collection_id, item_id);768 <NftApproveBasket<T>>::remove(collection_id, item_id);769 }770 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?771 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());772 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));773 Ok(post_info)774 }775776 /// Destroys a concrete instance of NFT on behalf of the owner777 /// See also: [`approve`]778 ///779 /// # Permissions780 ///781 /// * Collection Owner.782 /// * Collection Admin.783 /// * Current NFT Owner.784 ///785 /// # Arguments786 ///787 /// * collection_id: ID of the collection.788 ///789 /// * item_id: ID of NFT to burn.790 ///791 /// * from: owner of item792 #[weight = T::CommonWeightInfo::burn_from()]793 #[transactional]794 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {795 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);796 let budget = budget::Value::new(2);797798 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))799 }800801 /// Change ownership of the token.802 ///803 /// # Permissions804 ///805 /// * Collection Owner806 /// * Collection Admin807 /// * Current NFT owner808 ///809 /// # Arguments810 ///811 /// * recipient: Address of token recipient.812 ///813 /// * collection_id.814 ///815 /// * item_id: ID of the item816 /// * Non-Fungible Mode: Required.817 /// * Fungible Mode: Ignored.818 /// * Re-Fungible Mode: Required.819 ///820 /// * value: Amount to transfer.821 /// * Non-Fungible Mode: Ignored822 /// * Fungible Mode: Must specify transferred amount823 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)824 #[weight = T::CommonWeightInfo::transfer()]825 #[transactional]826 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {827 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);828 let budget = budget::Value::new(2);829830 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))831 }832833 /// Set, change, or remove approved address to transfer the ownership of the NFT.834 ///835 /// # Permissions836 ///837 /// * Collection Owner838 /// * Collection Admin839 /// * Current NFT owner840 ///841 /// # Arguments842 ///843 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).844 ///845 /// * collection_id.846 ///847 /// * item_id: ID of the item.848 #[weight = T::CommonWeightInfo::approve()]849 #[transactional]850 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {851 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);852853 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))854 }855856 /// 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.857 ///858 /// # Permissions859 /// * Collection Owner860 /// * Collection Admin861 /// * Current NFT owner862 /// * Address approved by current NFT owner863 ///864 /// # Arguments865 ///866 /// * from: Address that owns token.867 ///868 /// * recipient: Address of token recipient.869 ///870 /// * collection_id.871 ///872 /// * item_id: ID of the item.873 ///874 /// * value: Amount to transfer.875 #[weight = T::CommonWeightInfo::transfer_from()]876 #[transactional]877 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {878 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879 let budget = budget::Value::new(2);880881 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))882 }883884 #[weight = <SelfWeightOf<T>>::set_collection_limits()]885 #[transactional]886 pub fn set_collection_limits(887 origin,888 collection_id: CollectionId,889 new_limit: CollectionLimits,890 ) -> DispatchResult {891 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);892 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;893 target_collection.check_is_owner(&sender)?;894 let old_limit = &target_collection.limits;895896 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;897898 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(899 collection_id900 ));901902 target_collection.save()903 }904905 #[weight = <SelfWeightOf<T>>::set_collection_limits()]906 #[transactional]907 pub fn set_collection_permissions(908 origin,909 collection_id: CollectionId,910 new_limit: CollectionPermissions,911 ) -> DispatchResult {912 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);913 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;914 target_collection.check_is_owner(&sender)?;915 let old_limit = &target_collection.permissions;916917 target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_limit)?;918919 <Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(920 collection_id921 ));922923 target_collection.save()924 }925 }926}runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -80,7 +80,7 @@
};
use smallvec::smallvec;
use codec::{Encode, Decode};
-use pallet_unique::pallet_evm_collection;
+use pallet_unique::eth::evm_collection;
use fp_rpc::TransactionStatus;
use sp_runtime::{
traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
@@ -307,7 +307,7 @@
pallet_evm_migration::OnMethodCall<Self>,
pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
CollectionDispatchT<Self>,
- pallet_evm_collection::CollectionOnMethodCall<Self>,
+ evm_collection::CollectionOnMethodCall<Self>,
);
type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
type ChainId = ChainId;
@@ -988,7 +988,7 @@
type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
}
-impl pallet_evm_collection::Config for Runtime {
+impl evm_collection::Config for Runtime {
type ContractAddress = EvmCollectionAddress;
}
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -80,7 +80,7 @@
use smallvec::smallvec;
use codec::{Encode, Decode};
use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall};
-use pallet_unique::pallet_evm_collection;
+use pallet_unique::evm_collection;
use fp_rpc::TransactionStatus;
use sp_runtime::{
traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
@@ -279,7 +279,7 @@
pallet_evm_migration::OnMethodCall<Self>,
pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
CollectionDispatchT<Self>,
- pallet_evm_collection::CollectionOnMethodCall<Self>,
+ evm_collection::CollectionOnMethodCall<Self>,
);
type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
type ChainId = ChainId;
@@ -965,7 +965,7 @@
type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
}
-impl pallet_evm_collection::Config for Runtime {
+impl evm_collection::Config for Runtime {
type ContractAddress = EvmCollectionAddress;
}
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -85,7 +85,7 @@
use smallvec::smallvec;
use codec::{Encode, Decode};
use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall};
-use pallet_unique::pallet_evm_collection;
+use pallet_unique::evm_collection;
use fp_rpc::TransactionStatus;
use sp_runtime::{
traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
@@ -283,7 +283,7 @@
pallet_evm_migration::OnMethodCall<Self>,
pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
CollectionDispatchT<Self>,
- pallet_evm_collection::CollectionOnMethodCall<Self>,
+ evm_collection::CollectionOnMethodCall<Self>,
);
type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
type ChainId = ChainId;
@@ -970,7 +970,7 @@
type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit;
}
-impl pallet_evm_collection::Config for Runtime {
+impl evm_collection::Config for Runtime {
type ContractAddress = EvmCollectionAddress;
}