difftreelog
Merge pull request #148 from usetech-llc/feature/NFTPAR-373_chain_extensions
in: master
Chain extensions
10 files changed
.devcontainer/Dockerfilediffbeforeafterboth--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,6 +3,9 @@
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
apt-get -y install --no-install-recommends libssl-dev pkg-config libclang-dev clang
+RUN curl -L -o- https://github.com/WebAssembly/binaryen/releases/download/version_101/binaryen-version_101-x86_64-linux.tar.gz | \
+ tar xz --strip-components=1 -C /usr
+
USER vscode
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash && \
@@ -11,4 +14,5 @@
nvm install v12.20.1 && \
rustup toolchain install nightly-2021-03-01 && \
rustup default nightly-2021-03-01 && \
- rustup target add wasm32-unknown-unknown
\ No newline at end of file
+ rustup target add wasm32-unknown-unknown && \
+ cargo install cargo-contract
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5395,37 +5395,37 @@
[[package]]
name = "pallet-scheduler"
version = "3.0.0"
-source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"log",
+ "nft-data-structs",
+ "pallet-contracts",
+ "pallet-nft",
+ "pallet-nft-transaction-payment",
"parity-scale-codec",
+ "serde",
+ "sp-core",
"sp-io",
"sp-runtime",
"sp-std",
+ "substrate-test-utils",
]
[[package]]
name = "pallet-scheduler"
version = "3.0.0"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3#c94e0cdfe5556680dca1996004751eeb114755d7"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"log",
- "nft-data-structs",
- "pallet-contracts",
- "pallet-nft",
- "pallet-nft-transaction-payment",
"parity-scale-codec",
- "serde",
- "sp-core",
"sp-io",
"sp-runtime",
"sp-std",
- "substrate-test-utils",
]
[[package]]
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"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516pub use frame_support::{17 construct_runtime, decl_event, decl_module, decl_storage, decl_error,18 dispatch::DispatchResult,19 ensure, fail, parameter_types,20 traits::{21 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,22 Randomness, IsSubType, WithdrawReasons,23 },24 weights::{25 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},26 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,27 WeightToFeePolynomial, DispatchClass,28 },29 StorageValue,30 transactional,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use core::ops::{Deref, DerefMut};36use nft_data_structs::{37 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38 AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39 CollectionId, CollectionMode, TokenId, 40 SchemaVersion, SponsorshipState, Ownership,41 NftItemType, FungibleItemType, ReFungibleItemType42};4344#[cfg(test)]45mod mock;4647#[cfg(test)]48mod tests;4950mod default_weights;5152#[cfg(feature = "runtime-benchmarks")]53mod benchmarking;5455pub trait WeightInfo {56 fn create_collection() -> Weight;57 fn destroy_collection() -> Weight;58 fn add_to_white_list() -> Weight;59 fn remove_from_white_list() -> Weight;60 fn set_public_access_mode() -> Weight;61 fn set_mint_permission() -> Weight;62 fn change_collection_owner() -> Weight;63 fn add_collection_admin() -> Weight;64 fn remove_collection_admin() -> Weight;65 fn set_collection_sponsor() -> Weight;66 fn confirm_sponsorship() -> Weight;67 fn remove_collection_sponsor() -> Weight;68 fn create_item(s: usize) -> Weight;69 fn burn_item() -> Weight;70 fn transfer() -> Weight;71 fn approve() -> Weight;72 fn transfer_from() -> Weight;73 fn set_offchain_schema() -> Weight;74 fn set_const_on_chain_schema() -> Weight;75 fn set_variable_on_chain_schema() -> Weight;76 fn set_variable_meta_data() -> Weight;77 fn enable_contract_sponsoring() -> Weight;78 fn set_schema_version() -> Weight;79 fn set_chain_limits() -> Weight;80 fn set_contract_sponsoring_rate_limit() -> Weight;81 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;82 fn toggle_contract_white_list() -> Weight;83 fn add_to_contract_white_list() -> Weight;84 fn remove_from_contract_white_list() -> Weight;85 fn set_collection_limits() -> Weight;86}8788decl_error! {89 /// Error for non-fungible-token module.90 pub enum Error for Module<T: Config> {91 /// Total collections bound exceeded.92 TotalCollectionsLimitExceeded,93 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.94 CollectionDecimalPointLimitExceeded, 95 /// Collection name can not be longer than 63 char.96 CollectionNameLimitExceeded, 97 /// Collection description can not be longer than 255 char.98 CollectionDescriptionLimitExceeded, 99 /// Token prefix can not be longer than 15 char.100 CollectionTokenPrefixLimitExceeded,101 /// This collection does not exist.102 CollectionNotFound,103 /// Item not exists.104 TokenNotFound,105 /// Admin not found106 AdminNotFound,107 /// Arithmetic calculation overflow.108 NumOverflow, 109 /// Account already has admin role.110 AlreadyAdmin, 111 /// You do not own this collection.112 NoPermission,113 /// This address is not set as sponsor, use setCollectionSponsor first.114 ConfirmUnsetSponsorFail,115 /// Collection is not in mint mode.116 PublicMintingNotAllowed,117 /// Sender parameter and item owner must be equal.118 MustBeTokenOwner,119 /// Item balance not enough.120 TokenValueTooLow,121 /// Size of item is too large.122 NftSizeLimitExceeded,123 /// No approve found124 ApproveNotFound,125 /// Requested value more than approved.126 TokenValueNotEnough,127 /// Only approved addresses can call this method.128 ApproveRequired,129 /// Address is not in white list.130 AddresNotInWhiteList,131 /// Number of collection admins bound exceeded.132 CollectionAdminsLimitExceeded,133 /// Owned tokens by a single address bound exceeded.134 AddressOwnershipLimitExceeded,135 /// Length of items properties must be greater than 0.136 EmptyArgument,137 /// const_data exceeded data limit.138 TokenConstDataLimitExceeded,139 /// variable_data exceeded data limit.140 TokenVariableDataLimitExceeded,141 /// Not NFT item data used to mint in NFT collection.142 NotNftDataUsedToMintNftCollectionToken,143 /// Not Fungible item data used to mint in Fungible collection.144 NotFungibleDataUsedToMintFungibleCollectionToken,145 /// Not Re Fungible item data used to mint in Re Fungible collection.146 NotReFungibleDataUsedToMintReFungibleCollectionToken,147 /// Unexpected collection type.148 UnexpectedCollectionType,149 /// Can't store metadata in fungible tokens.150 CantStoreMetadataInFungibleTokens,151 /// Collection token limit exceeded152 CollectionTokenLimitExceeded,153 /// Account token limit exceeded per collection154 AccountTokenLimitExceeded,155 /// Collection limit bounds per collection exceeded156 CollectionLimitBoundsExceeded,157 /// Tried to enable permissions which are only permitted to be disabled158 OwnerPermissionsCantBeReverted,159 /// Schema data size limit bound exceeded160 SchemaDataLimitExceeded,161 /// Maximum refungibility exceeded162 WrongRefungiblePieces,163 /// createRefungible should be called with one owner164 BadCreateRefungibleCall,165 }166}167168pub struct CollectionHandle<T: system::Config> {169 pub id: CollectionId,170 pub collection: Collection<T>,171}172173impl<T: frame_system::Config> Deref for CollectionHandle<T> {174 type Target = Collection<T>;175176 fn deref(&self) -> &Self::Target {177 &self.collection178 }179}180181impl<T: frame_system::Config> DerefMut for CollectionHandle<T> {182 fn deref_mut(&mut self) -> &mut Self::Target {183 &mut self.collection184 }185}186187pub trait Config: system::Config + Sized {188 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;189190 /// Weight information for extrinsics in this pallet.191 type WeightInfo: WeightInfo;192193 type Currency: Currency<Self::AccountId>;194 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;195 type TreasuryAccountId: Get<Self::AccountId>;196}197198// # Used definitions199//200// ## User control levels201//202// chain-controlled - key is uncontrolled by user203// i.e autoincrementing index204// can use non-cryptographic hash205// real - key is controlled by user206// but it is hard to generate enough colliding values, i.e owner of signed txs207// can use non-cryptographic hash208// controlled - key is completly controlled by users209// i.e maps with mutable keys210// should use cryptographic hash211//212// ## User control level downgrade reasons213//214// ?1 - chain-controlled -> controlled215// collections/tokens can be destroyed, resulting in massive holes216// ?2 - chain-controlled -> controlled217// same as ?1, but can be only added, resulting in easier exploitation218// ?3 - real -> controlled219// no confirmation required, so addresses can be easily generated220decl_storage! {221 trait Store for Module<T: Config> as Nft {222223 //#region Private members224 /// Id of next collection225 CreatedCollectionCount: u32;226 /// Used for migrations227 ChainVersion: u64;228 /// Id of last collection token229 /// Collection id (controlled?1)230 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;231 //#endregion232233 //#region Chain limits struct234 pub ChainLimit get(fn chain_limit) config(): ChainLimits;235 //#endregion236237 //#region Bound counters238 /// Amount of collections destroyed, used for total amount tracking with239 /// CreatedCollectionCount240 DestroyedCollectionCount: u32;241 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)242 /// Account id (real)243 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;244 //#endregion245246 //#region Basic collections247 /// Collection info248 /// Collection id (controlled?1)249 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;250 /// List of collection admins251 /// Collection id (controlled?2)252 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::AccountId>;253 /// Whitelisted collection users254 /// Collection id (controlled?2), user id (controlled?3)255 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;256 //#endregion257258 /// How many of collection items user have259 /// Collection id (controlled?2), account id (real)260 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;261262 /// Amount of items which spender can transfer out of owners account (via transferFrom)263 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))264 /// TODO: Off chain worker should remove from this map when token gets removed265 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;266267 //#region Item collections268 /// Collection id (controlled?2), token id (controlled?1)269 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::AccountId>>;270 /// Collection id (controlled?2), owner (controlled?2)271 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;272 /// Collection id (controlled?2), token id (controlled?1)273 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::AccountId>>;274 //#endregion275276 //#region Index list277 /// Collection id (controlled?2), tokens owner (controlled?2)278 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;279 //#endregion280281 //#region Tokens transfer rate limit baskets282 /// (Collection id (controlled?2), who created (real))283 /// TODO: Off chain worker should remove from this map when collection gets removed284 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;285 /// Collection id (controlled?2), token id (controlled?2)286 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;287 /// Collection id (controlled?2), owning user (real)288 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;289 /// Collection id (controlled?2), token id (controlled?2)290 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;291 //#endregion292293 /// Variable metadata sponsoring294 /// Collection id (controlled?2), token id (controlled?2)295 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;296 297 //#region Contract Sponsorship and Ownership298 /// Contract address (real)299 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;300 /// Contract address (real)301 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;302 /// (Contract address(real), caller (real))303 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;304 /// Contract address (real)305 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;306 /// Contract address (real)307 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 308 /// Contract address (real) => Whitelisted user (controlled?3)309 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 310 //#endregion311 }312 add_extra_genesis {313 build(|config: &GenesisConfig<T>| {314 // Modification of storage315 for (_num, _c) in &config.collection_id {316 <Module<T>>::init_collection(_c);317 }318319 for (_num, _c, _i) in &config.nft_item_id {320 <Module<T>>::init_nft_token(*_c, _i);321 }322323 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {324 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);325 }326327 for (_num, _c, _i) in &config.refungible_item_id {328 <Module<T>>::init_refungible_token(*_c, _i);329 }330 })331 }332}333334decl_event!(335 pub enum Event<T>336 where337 AccountId = <T as system::Config>::AccountId,338 {339 /// New collection was created340 /// 341 /// # Arguments342 /// 343 /// * collection_id: Globally unique identifier of newly created collection.344 /// 345 /// * mode: [CollectionMode] converted into u8.346 /// 347 /// * account_id: Collection owner.348 CollectionCreated(CollectionId, u8, AccountId),349350 /// New item was created.351 /// 352 /// # Arguments353 /// 354 /// * collection_id: Id of the collection where item was created.355 /// 356 /// * item_id: Id of an item. Unique within the collection.357 ///358 /// * recipient: Owner of newly created item 359 ItemCreated(CollectionId, TokenId, AccountId),360361 /// Collection item was burned.362 /// 363 /// # Arguments364 /// 365 /// collection_id.366 /// 367 /// item_id: Identifier of burned NFT.368 ItemDestroyed(CollectionId, TokenId),369370 /// Item was transferred371 ///372 /// * collection_id: Id of collection to which item is belong373 ///374 /// * item_id: Id of an item375 ///376 /// * sender: Original owner of item377 ///378 /// * recipient: New owner of item379 ///380 /// * amount: Always 1 for NFT381 Transfer(CollectionId, TokenId, AccountId, AccountId, u128),382383 /// * collection_id384 ///385 /// * item_id386 ///387 /// * sender388 ///389 /// * spender390 ///391 /// * amount392 Approved(CollectionId, TokenId, AccountId, AccountId, u128),393 }394);395396decl_module! {397 pub struct Module<T: Config> for enum Call 398 where 399 origin: T::Origin400 {401 fn deposit_event() = default;402 type Error = Error<T>;403404 fn on_initialize(_now: T::BlockNumber) -> Weight {405 0406 }407408 /// 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.409 /// 410 /// # Permissions411 /// 412 /// * Anyone.413 /// 414 /// # Arguments415 /// 416 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.417 /// 418 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.419 /// 420 /// * token_prefix: UTF-8 string with token prefix.421 /// 422 /// * mode: [CollectionMode] collection type and type dependent data.423 // returns collection ID424 #[weight = <T as Config>::WeightInfo::create_collection()]425 #[transactional]426 pub fn create_collection(origin,427 collection_name: Vec<u16>,428 collection_description: Vec<u16>,429 token_prefix: Vec<u8>,430 mode: CollectionMode) -> DispatchResult {431432 // Anyone can create a collection433 let who = ensure_signed(origin)?;434435 // Take a (non-refundable) deposit of collection creation436 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();437 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(438 &T::TreasuryAccountId::get(),439 T::CollectionCreationPrice::get(),440 ));441 <T as Config>::Currency::settle(442 &who,443 imbalance,444 WithdrawReasons::TRANSFER,445 ExistenceRequirement::KeepAlive,446 ).map_err(|_| Error::<T>::NoPermission)?;447448 let decimal_points = match mode {449 CollectionMode::Fungible(points) => points,450 _ => 0451 };452453 let chain_limit = ChainLimit::get();454455 let created_count = CreatedCollectionCount::get();456 let destroyed_count = DestroyedCollectionCount::get();457458 // bound Total number of collections459 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);460461 // check params462 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);463 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);464 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);465 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);466467 // Generate next collection ID468 let next_id = created_count469 .checked_add(1)470 .ok_or(Error::<T>::NumOverflow)?;471472 CreatedCollectionCount::put(next_id);473474 let limits = CollectionLimits {475 sponsored_data_size: chain_limit.custom_data_limit,476 ..Default::default()477 };478479 // Create new collection480 let new_collection = Collection {481 owner: who.clone(),482 name: collection_name,483 mode: mode.clone(),484 mint_mode: false,485 access: AccessMode::Normal,486 description: collection_description,487 decimal_points: decimal_points,488 token_prefix: token_prefix,489 offchain_schema: Vec::new(),490 schema_version: SchemaVersion::ImageURL,491 sponsorship: SponsorshipState::Disabled,492 variable_on_chain_schema: Vec::new(),493 const_on_chain_schema: Vec::new(),494 limits,495 };496497 // Add new collection to map498 <CollectionById<T>>::insert(next_id, new_collection);499500 // call event501 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who.clone()));502503 Ok(())504 }505506 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.507 /// 508 /// # Permissions509 /// 510 /// * Collection Owner.511 /// 512 /// # Arguments513 /// 514 /// * collection_id: collection to destroy.515 #[weight = <T as Config>::WeightInfo::destroy_collection()]516 #[transactional]517 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {518519 let sender = ensure_signed(origin)?;520 let collection = Self::get_collection(collection_id)?;521 Self::check_owner_permissions(&collection, sender)?;522 if !collection.limits.owner_can_destroy {523 fail!(Error::<T>::NoPermission);524 }525526 <AddressTokens<T>>::remove_prefix(collection_id);527 <Allowances<T>>::remove_prefix(collection_id);528 <Balance<T>>::remove_prefix(collection_id);529 <ItemListIndex>::remove(collection_id);530 <AdminList<T>>::remove(collection_id);531 <CollectionById<T>>::remove(collection_id);532 <WhiteList<T>>::remove_prefix(collection_id);533534 <NftItemList<T>>::remove_prefix(collection_id);535 <FungibleItemList<T>>::remove_prefix(collection_id);536 <ReFungibleItemList<T>>::remove_prefix(collection_id);537538 <NftTransferBasket<T>>::remove_prefix(collection_id);539 <FungibleTransferBasket<T>>::remove_prefix(collection_id);540 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);541542 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);543544 DestroyedCollectionCount::put(DestroyedCollectionCount::get()545 .checked_add(1)546 .ok_or(Error::<T>::NumOverflow)?);547548 Ok(())549 }550551 /// Add an address to white list.552 /// 553 /// # Permissions554 /// 555 /// * Collection Owner556 /// * Collection Admin557 /// 558 /// # Arguments559 /// 560 /// * collection_id.561 /// 562 /// * address.563 #[weight = <T as Config>::WeightInfo::add_to_white_list()]564 #[transactional]565 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{566567 let sender = ensure_signed(origin)?;568 let collection = Self::get_collection(collection_id)?;569570 Self::toggle_white_list_internal(571 &sender,572 &collection,573 &address,574 true,575 )?;576577 Ok(())578 }579580 /// Remove an address from white list.581 /// 582 /// # Permissions583 /// 584 /// * Collection Owner585 /// * Collection Admin586 /// 587 /// # Arguments588 /// 589 /// * collection_id.590 /// 591 /// * address.592 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]593 #[transactional]594 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{595596 let sender = ensure_signed(origin)?;597 let collection = Self::get_collection(collection_id)?;598599 Self::toggle_white_list_internal(600 &sender,601 &collection,602 &address,603 false,604 )?;605606 Ok(())607 }608609 /// Toggle between normal and white list access for the methods with access for `Anyone`.610 /// 611 /// # Permissions612 /// 613 /// * Collection Owner.614 /// 615 /// # Arguments616 /// 617 /// * collection_id.618 /// 619 /// * mode: [AccessMode]620 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]621 #[transactional]622 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult623 {624 let sender = ensure_signed(origin)?;625626 let mut target_collection = Self::get_collection(collection_id)?;627 Self::check_owner_permissions(&target_collection, sender)?;628 target_collection.access = mode;629 Self::save_collection(target_collection);630631 Ok(())632 }633634 /// Allows Anyone to create tokens if:635 /// * White List is enabled, and636 /// * Address is added to white list, and637 /// * This method was called with True parameter638 /// 639 /// # Permissions640 /// * Collection Owner641 ///642 /// # Arguments643 /// 644 /// * collection_id.645 /// 646 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.647 #[weight = <T as Config>::WeightInfo::set_mint_permission()]648 #[transactional]649 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult650 {651 let sender = ensure_signed(origin)?;652653 let mut target_collection = Self::get_collection(collection_id)?;654 Self::check_owner_permissions(&target_collection, sender)?;655 target_collection.mint_mode = mint_permission;656 Self::save_collection(target_collection);657658 Ok(())659 }660661 /// Change the owner of the collection.662 /// 663 /// # Permissions664 /// 665 /// * Collection Owner.666 /// 667 /// # Arguments668 /// 669 /// * collection_id.670 /// 671 /// * new_owner.672 #[weight = <T as Config>::WeightInfo::change_collection_owner()]673 #[transactional]674 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {675676 let sender = ensure_signed(origin)?;677 let mut target_collection = Self::get_collection(collection_id)?;678 Self::check_owner_permissions(&target_collection, sender)?;679 target_collection.owner = new_owner;680 Self::save_collection(target_collection);681682 Ok(())683 }684685 /// Adds an admin of the Collection.686 /// 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. 687 /// 688 /// # Permissions689 /// 690 /// * Collection Owner.691 /// * Collection Admin.692 /// 693 /// # Arguments694 /// 695 /// * collection_id: ID of the Collection to add admin for.696 /// 697 /// * new_admin_id: Address of new admin to add.698 #[weight = <T as Config>::WeightInfo::add_collection_admin()]699 #[transactional]700 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {701702 let sender = ensure_signed(origin)?;703 let collection = Self::get_collection(collection_id)?;704 Self::check_owner_or_admin_permissions(&collection, sender)?;705 let mut admin_arr = <AdminList<T>>::get(collection_id);706707 match admin_arr.binary_search(&new_admin_id) {708 Ok(_) => {},709 Err(idx) => {710 let limits = ChainLimit::get();711 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);712 admin_arr.insert(idx, new_admin_id);713 <AdminList<T>>::insert(collection_id, admin_arr);714 }715 }716 Ok(())717 }718719 /// 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.720 ///721 /// # Permissions722 /// 723 /// * Collection Owner.724 /// * Collection Admin.725 /// 726 /// # Arguments727 /// 728 /// * collection_id: ID of the Collection to remove admin for.729 /// 730 /// * account_id: Address of admin to remove.731 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]732 #[transactional]733 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {734735 let sender = ensure_signed(origin)?;736 let collection = Self::get_collection(collection_id)?;737 Self::check_owner_or_admin_permissions(&collection, sender)?;738 let mut admin_arr = <AdminList<T>>::get(collection_id);739740 match admin_arr.binary_search(&account_id) {741 Ok(idx) => {742 admin_arr.remove(idx);743 <AdminList<T>>::insert(collection_id, admin_arr);744 },745 Err(_) => {}746 }747 Ok(())748 }749750 /// # Permissions751 /// 752 /// * Collection Owner753 /// 754 /// # Arguments755 /// 756 /// * collection_id.757 /// 758 /// * new_sponsor.759 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]760 #[transactional]761 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {762763 let sender = ensure_signed(origin)?;764 let mut target_collection = Self::get_collection(collection_id)?;765 Self::check_owner_permissions(&target_collection, sender)?;766767 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);768 Self::save_collection(target_collection);769770 Ok(())771 }772773 /// # Permissions774 /// 775 /// * Sponsor.776 /// 777 /// # Arguments778 /// 779 /// * collection_id.780 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]781 #[transactional]782 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {783784 let sender = ensure_signed(origin)?;785786 let mut target_collection = Self::get_collection(collection_id)?;787 ensure!(788 target_collection.sponsorship.pending_sponsor() == Some(&sender),789 Error::<T>::ConfirmUnsetSponsorFail790 );791792 target_collection.sponsorship = SponsorshipState::Confirmed(sender);793 Self::save_collection(target_collection);794795 Ok(())796 }797798 /// Switch back to pay-per-own-transaction model.799 ///800 /// # Permissions801 ///802 /// * Collection owner.803 /// 804 /// # Arguments805 /// 806 /// * collection_id.807 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]808 #[transactional]809 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {810811 let sender = ensure_signed(origin)?;812813 let mut target_collection = Self::get_collection(collection_id)?;814 Self::check_owner_permissions(&target_collection, sender)?;815816 target_collection.sponsorship = SponsorshipState::Disabled;817 Self::save_collection(target_collection);818819 Ok(())820 }821822 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.823 /// 824 /// # Permissions825 /// 826 /// * Collection Owner.827 /// * Collection Admin.828 /// * Anyone if829 /// * White List is enabled, and830 /// * Address is added to white list, and831 /// * MintPermission is enabled (see SetMintPermission method)832 /// 833 /// # Arguments834 /// 835 /// * collection_id: ID of the collection.836 /// 837 /// * owner: Address, initial owner of the NFT.838 ///839 /// * data: Token data to store on chain.840 // #[weight =841 // (130_000_000 as Weight)842 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))843 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))844 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]845846 #[weight = <T as Config>::WeightInfo::create_item(data.len())]847 #[transactional]848 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {849 let sender = ensure_signed(origin)?;850 Self::create_item_internal(sender, collection_id, owner, data)851 }852853 /// This method creates multiple items in a collection created with CreateCollection method.854 /// 855 /// # Permissions856 /// 857 /// * Collection Owner.858 /// * Collection Admin.859 /// * Anyone if860 /// * White List is enabled, and861 /// * Address is added to white list, and862 /// * MintPermission is enabled (see SetMintPermission method)863 /// 864 /// # Arguments865 /// 866 /// * collection_id: ID of the collection.867 /// 868 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].869 /// 870 /// * owner: Address, initial owner of the NFT.871 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()872 .map(|data| { data.len() })873 .sum())]874 #[transactional]875 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {876877 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);878 let sender = ensure_signed(origin)?;879880 let target_collection = Self::get_collection(collection_id)?;881882 Self::can_create_items_in_collection(&target_collection, &sender, &owner, items_data.len() as u32)?;883884 for data in &items_data {885 Self::validate_create_item_args(&target_collection, data)?;886 }887 for data in &items_data {888 Self::create_item_no_validation(&target_collection, owner.clone(), data.clone())?;889 }890891 Ok(())892 }893894 /// Destroys a concrete instance of NFT.895 /// 896 /// # Permissions897 /// 898 /// * Collection Owner.899 /// * Collection Admin.900 /// * Current NFT Owner.901 /// 902 /// # Arguments903 /// 904 /// * collection_id: ID of the collection.905 /// 906 /// * item_id: ID of NFT to burn.907 #[weight = <T as Config>::WeightInfo::burn_item()]908 #[transactional]909 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {910911 let sender = ensure_signed(origin)?;912913 // Transfer permissions check914 let target_collection = Self::get_collection(collection_id)?;915 ensure!(916 Self::is_item_owner(sender.clone(), &target_collection, item_id) ||917 (918 target_collection.limits.owner_can_transfer &&919 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())920 ),921 Error::<T>::NoPermission922 );923924 if target_collection.access == AccessMode::WhiteList {925 Self::check_white_list(&target_collection, &sender)?;926 }927928 match target_collection.mode929 {930 CollectionMode::NFT => Self::burn_nft_item(&target_collection, item_id)?,931 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &target_collection, value)?,932 CollectionMode::ReFungible => Self::burn_refungible_item(&target_collection, item_id, &sender)?,933 _ => ()934 };935936 // call event937 Self::deposit_event(RawEvent::ItemDestroyed(target_collection.id, item_id));938939 Ok(())940 }941942 /// Change ownership of the token.943 /// 944 /// # Permissions945 /// 946 /// * Collection Owner947 /// * Collection Admin948 /// * Current NFT owner949 ///950 /// # Arguments951 /// 952 /// * recipient: Address of token recipient.953 /// 954 /// * collection_id.955 /// 956 /// * item_id: ID of the item957 /// * Non-Fungible Mode: Required.958 /// * Fungible Mode: Ignored.959 /// * Re-Fungible Mode: Required.960 /// 961 /// * value: Amount to transfer.962 /// * Non-Fungible Mode: Ignored963 /// * Fungible Mode: Must specify transferred amount964 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)965 #[weight = <T as Config>::WeightInfo::transfer()]966 #[transactional]967 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {968 let sender = ensure_signed(origin)?;969 let collection = Self::get_collection(collection_id)?;970971 Self::transfer_internal(sender, recipient, &collection, item_id, value)972 }973974 /// Set, change, or remove approved address to transfer the ownership of the NFT.975 /// 976 /// # Permissions977 /// 978 /// * Collection Owner979 /// * Collection Admin980 /// * Current NFT owner981 /// 982 /// # Arguments983 /// 984 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).985 /// 986 /// * collection_id.987 /// 988 /// * item_id: ID of the item.989 #[weight = <T as Config>::WeightInfo::approve()]990 #[transactional]991 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {992993 let sender = ensure_signed(origin)?;994 let collection = Self::get_collection(collection_id)?;995996 Self::approve_internal(sender, spender, &collection, item_id, amount)?;997998 Ok(())999 }1000 1001 /// 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.1002 /// 1003 /// # Permissions1004 /// * Collection Owner1005 /// * Collection Admin1006 /// * Current NFT owner1007 /// * Address approved by current NFT owner1008 /// 1009 /// # Arguments1010 /// 1011 /// * from: Address that owns token.1012 /// 1013 /// * recipient: Address of token recipient.1014 /// 1015 /// * collection_id.1016 /// 1017 /// * item_id: ID of the item.1018 /// 1019 /// * value: Amount to transfer.1020 #[weight = <T as Config>::WeightInfo::transfer_from()]1021 #[transactional]1022 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {10231024 let sender = ensure_signed(origin)?;1025 let collection = Self::get_collection(collection_id)?;10261027 Self::transfer_from_internal(sender, from, recipient, &collection, item_id, value)?;10281029 Ok(())1030 }10311032 // #[weight = 0]1033 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {10341035 // // let no_perm_mes = "You do not have permissions to modify this collection";1036 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1037 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1038 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10391040 // // // on_nft_received call10411042 // // Self::transfer(origin, collection_id, item_id, new_owner)?;10431044 // Ok(())1045 // }10461047 /// Set off-chain data schema.1048 /// 1049 /// # Permissions1050 /// 1051 /// * Collection Owner1052 /// * Collection Admin1053 /// 1054 /// # Arguments1055 /// 1056 /// * collection_id.1057 /// 1058 /// * schema: String representing the offchain data schema.1059 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1060 #[transactional]1061 pub fn set_variable_meta_data (1062 origin,1063 collection_id: CollectionId,1064 item_id: TokenId,1065 data: Vec<u8>1066 ) -> DispatchResult {1067 let sender = ensure_signed(origin)?;1068 1069 let collection = Self::get_collection(collection_id)?;10701071 Self::set_variable_meta_data_internal(sender, &collection, item_id, data)?;10721073 Ok(())1074 }1075 1076 /// Set schema standard1077 /// ImageURL1078 /// Unique1079 /// 1080 /// # Permissions1081 /// 1082 /// * Collection Owner1083 /// * Collection Admin1084 /// 1085 /// # Arguments1086 /// 1087 /// * collection_id.1088 /// 1089 /// * schema: SchemaVersion: enum1090 #[weight = <T as Config>::WeightInfo::set_schema_version()]1091 #[transactional]1092 pub fn set_schema_version(1093 origin,1094 collection_id: CollectionId,1095 version: SchemaVersion1096 ) -> DispatchResult {1097 let sender = ensure_signed(origin)?;1098 let mut target_collection = Self::get_collection(collection_id)?;1099 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;1100 target_collection.schema_version = version;1101 Self::save_collection(target_collection);11021103 Ok(())1104 }11051106 /// Set off-chain data schema.1107 /// 1108 /// # Permissions1109 /// 1110 /// * Collection Owner1111 /// * Collection Admin1112 /// 1113 /// # Arguments1114 /// 1115 /// * collection_id.1116 /// 1117 /// * schema: String representing the offchain data schema.1118 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1119 #[transactional]1120 pub fn set_offchain_schema(1121 origin,1122 collection_id: CollectionId,1123 schema: Vec<u8>1124 ) -> DispatchResult {1125 let sender = ensure_signed(origin)?;1126 let mut target_collection = Self::get_collection(collection_id)?;1127 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;11281129 // check schema limit1130 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11311132 target_collection.offchain_schema = schema;1133 Self::save_collection(target_collection);11341135 Ok(())1136 }11371138 /// Set const on-chain data schema.1139 /// 1140 /// # Permissions1141 /// 1142 /// * Collection Owner1143 /// * Collection Admin1144 /// 1145 /// # Arguments1146 /// 1147 /// * collection_id.1148 /// 1149 /// * schema: String representing the const on-chain data schema.1150 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1151 #[transactional]1152 pub fn set_const_on_chain_schema (1153 origin,1154 collection_id: CollectionId,1155 schema: Vec<u8>1156 ) -> DispatchResult {1157 let sender = ensure_signed(origin)?;1158 let mut target_collection = Self::get_collection(collection_id)?;1159 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;11601161 // check schema limit1162 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");11631164 target_collection.const_on_chain_schema = schema;1165 Self::save_collection(target_collection);11661167 Ok(())1168 }11691170 /// Set variable on-chain data schema.1171 /// 1172 /// # Permissions1173 /// 1174 /// * Collection Owner1175 /// * Collection Admin1176 /// 1177 /// # Arguments1178 /// 1179 /// * collection_id.1180 /// 1181 /// * schema: String representing the variable on-chain data schema.1182 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1183 #[transactional]1184 pub fn set_variable_on_chain_schema (1185 origin,1186 collection_id: CollectionId,1187 schema: Vec<u8>1188 ) -> DispatchResult {1189 let sender = ensure_signed(origin)?;1190 let mut target_collection = Self::get_collection(collection_id)?;1191 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;11921193 // check schema limit1194 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");11951196 target_collection.variable_on_chain_schema = schema;1197 Self::save_collection(target_collection);11981199 Ok(())1200 }12011202 // Sudo permissions function1203 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1204 #[transactional]1205 pub fn set_chain_limits(1206 origin,1207 limits: ChainLimits1208 ) -> DispatchResult {12091210 #[cfg(not(feature = "runtime-benchmarks"))]1211 ensure_root(origin)?;12121213 <ChainLimit>::put(limits);1214 Ok(())1215 }12161217 /// Enable smart contract self-sponsoring.1218 /// 1219 /// # Permissions1220 /// 1221 /// * Contract Owner1222 /// 1223 /// # Arguments1224 /// 1225 /// * contract address1226 /// * enable flag1227 /// 1228 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1229 #[transactional]1230 pub fn enable_contract_sponsoring(1231 origin,1232 contract_address: T::AccountId,1233 enable: bool1234 ) -> DispatchResult {12351236 let sender = ensure_signed(origin)?;12371238 #[cfg(feature = "runtime-benchmarks")]1239 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());12401241 Self::ensure_contract_owned(sender, &contract_address)?;12421243 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1244 Ok(())1245 }12461247 /// Set the rate limit for contract sponsoring to specified number of blocks.1248 /// 1249 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1250 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1251 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1252 /// from contract endowment if there are at least B blocks between such transactions. 1253 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1254 /// 1255 /// # Permissions1256 /// 1257 /// * Contract Owner1258 /// 1259 /// # Arguments1260 /// 1261 /// -`contract_address`: Address of the contract to sponsor1262 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1263 /// 1264 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1265 #[transactional]1266 pub fn set_contract_sponsoring_rate_limit(1267 origin,1268 contract_address: T::AccountId,1269 rate_limit: T::BlockNumber1270 ) -> DispatchResult {1271 let sender = ensure_signed(origin)?;12721273 #[cfg(feature = "runtime-benchmarks")]1274 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());12751276 Self::ensure_contract_owned(sender, &contract_address)?;1277 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1278 Ok(())1279 }12801281 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1282 /// 1283 /// # Permissions1284 /// 1285 /// * Address that deployed smart contract.1286 /// 1287 /// # Arguments1288 /// 1289 /// -`contract_address`: Address of the contract.1290 /// 1291 /// - `enable`: . 1292 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1293 #[transactional]1294 pub fn toggle_contract_white_list(1295 origin,1296 contract_address: T::AccountId,1297 enable: bool1298 ) -> DispatchResult {1299 let sender = ensure_signed(origin)?;13001301 #[cfg(feature = "runtime-benchmarks")]1302 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13031304 Self::ensure_contract_owned(sender, &contract_address)?;1305 if enable {1306 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1307 } else {1308 <ContractWhiteListEnabled<T>>::remove(contract_address);1309 }1310 Ok(())1311 }1312 1313 /// Add an address to smart contract white list.1314 /// 1315 /// # Permissions1316 /// 1317 /// * Address that deployed smart contract.1318 /// 1319 /// # Arguments1320 /// 1321 /// -`contract_address`: Address of the contract.1322 ///1323 /// -`account_address`: Address to add.1324 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1325 #[transactional]1326 pub fn add_to_contract_white_list(1327 origin,1328 contract_address: T::AccountId,1329 account_address: T::AccountId1330 ) -> DispatchResult {1331 let sender = ensure_signed(origin)?;13321333 #[cfg(feature = "runtime-benchmarks")]1334 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1335 1336 Self::ensure_contract_owned(sender, &contract_address)?; 1337 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1338 Ok(())1339 }13401341 /// Remove an address from smart contract white list.1342 /// 1343 /// # Permissions1344 /// 1345 /// * Address that deployed smart contract.1346 /// 1347 /// # Arguments1348 /// 1349 /// -`contract_address`: Address of the contract.1350 ///1351 /// -`account_address`: Address to remove.1352 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1353 #[transactional]1354 pub fn remove_from_contract_white_list(1355 origin,1356 contract_address: T::AccountId,1357 account_address: T::AccountId1358 ) -> DispatchResult {1359 let sender = ensure_signed(origin)?;13601361 #[cfg(feature = "runtime-benchmarks")]1362 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13631364 Self::ensure_contract_owned(sender, &contract_address)?;1365 <ContractWhiteList<T>>::remove(contract_address, account_address);1366 Ok(())1367 }13681369 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1370 #[transactional]1371 pub fn set_collection_limits(1372 origin,1373 collection_id: u32,1374 new_limits: CollectionLimits<T::BlockNumber>,1375 ) -> DispatchResult {1376 let sender = ensure_signed(origin)?;1377 let mut target_collection = Self::get_collection(collection_id)?;1378 Self::check_owner_permissions(&target_collection, sender.clone())?;1379 let old_limits = &target_collection.limits;1380 let chain_limits = ChainLimit::get();13811382 // collection bounds1383 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1384 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1385 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1386 Error::<T>::CollectionLimitBoundsExceeded);13871388 // token_limit check prev1389 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1390 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);13911392 ensure!(1393 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1394 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1395 Error::<T>::OwnerPermissionsCantBeReverted,1396 );13971398 target_collection.limits = new_limits;1399 Self::save_collection(target_collection);14001401 Ok(())1402 } 1403 }1404}14051406impl<T: Config> Module<T> {1407 pub fn create_item_internal(sender: T::AccountId, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1408 let target_collection = Self::get_collection(collection_id)?;14091410 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1411 Self::validate_create_item_args(&target_collection, &data)?;1412 Self::create_item_no_validation(&target_collection, owner, data)?;14131414 Ok(())1415 }14161417 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1418 // Limits check1419 Self::is_correct_transfer(target_collection, &recipient)?;14201421 // Transfer permissions check1422 ensure!(Self::is_item_owner(sender.clone(), target_collection, item_id) ||1423 Self::is_owner_or_admin_permissions(target_collection, sender.clone()),1424 Error::<T>::NoPermission);14251426 if target_collection.access == AccessMode::WhiteList {1427 Self::check_white_list(target_collection, &sender)?;1428 Self::check_white_list(target_collection, &recipient)?;1429 }14301431 match target_collection.mode1432 {1433 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1434 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1435 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1436 _ => ()1437 };14381439 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));14401441 Ok(())1442 }14431444 pub fn approve_internal(1445 sender: T::AccountId,1446 spender: T::AccountId,1447 collection: &CollectionHandle<T>,1448 item_id: TokenId,1449 amount: u128,1450 ) -> DispatchResult {1451 Self::token_exists(&collection, item_id)?;14521453 // Transfer permissions check1454 let bypasses_limits = collection.limits.owner_can_transfer &&1455 Self::is_owner_or_admin_permissions(1456 &collection,1457 sender.clone(),1458 );14591460 let allowance_limit = if bypasses_limits {1461 None1462 } else if let Some(amount) = Self::owned_amount(1463 sender.clone(),1464 &collection,1465 item_id,1466 ) {1467 Some(amount)1468 } else {1469 fail!(Error::<T>::NoPermission);1470 };14711472 if collection.access == AccessMode::WhiteList {1473 Self::check_white_list(&collection, &sender)?;1474 Self::check_white_list(&collection, &spender)?;1475 }14761477 let allowance: u128 = amount1478 .checked_add(<Allowances<T>>::get(collection.id, (item_id, &sender, &spender)))1479 .ok_or(Error::<T>::NumOverflow)?;1480 if let Some(limit) = allowance_limit {1481 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1482 }1483 <Allowances<T>>::insert(collection.id, (item_id, &sender, &spender), allowance);14841485 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender, spender, allowance));1486 Ok(())1487 }14881489 pub fn transfer_from_internal(1490 sender: T::AccountId,1491 from: T::AccountId,1492 recipient: T::AccountId,1493 collection: &CollectionHandle<T>,1494 item_id: TokenId,1495 amount: u128,1496 ) -> DispatchResult {1497 // Check approval1498 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, &from, &sender));14991500 // Limits check1501 Self::is_correct_transfer(&collection, &recipient)?;15021503 // Transfer permissions check1504 ensure!(1505 approval >= amount || 1506 (1507 collection.limits.owner_can_transfer &&1508 Self::is_owner_or_admin_permissions(&collection, sender.clone())1509 ),1510 Error::<T>::NoPermission1511 );15121513 if collection.access == AccessMode::WhiteList {1514 Self::check_white_list(&collection, &sender)?;1515 Self::check_white_list(&collection, &recipient)?;1516 }15171518 // Reduce approval by transferred amount or remove if remaining approval drops to 01519 let allowance = approval.saturating_sub(amount);1520 if allowance > 0 {1521 <Allowances<T>>::insert(collection.id, (item_id, &from, &sender), allowance);1522 } else {1523 <Allowances<T>>::remove(collection.id, (item_id, &from, &sender));1524 }15251526 match collection.mode {1527 CollectionMode::NFT => {1528 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1529 }1530 CollectionMode::Fungible(_) => {1531 Self::transfer_fungible(&collection, amount, &from, &recipient)?1532 }1533 CollectionMode::ReFungible => {1534 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1535 }1536 _ => ()1537 };15381539 Ok(())1540 }15411542 pub fn set_variable_meta_data_internal(1543 sender: T::AccountId,1544 collection: &CollectionHandle<T>, 1545 item_id: TokenId,1546 data: Vec<u8>,1547 ) -> DispatchResult {1548 Self::token_exists(&collection, item_id)?;15491550 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15511552 // Modify permissions check1553 ensure!(Self::is_item_owner(sender.clone(), &collection, item_id) ||1554 Self::is_owner_or_admin_permissions(&collection, sender.clone()),1555 Error::<T>::NoPermission);15561557 match collection.mode1558 {1559 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1560 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1561 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1562 _ => fail!(Error::<T>::UnexpectedCollectionType)1563 };15641565 Ok(())1566 }15671568 pub fn create_multiple_items_internal(1569 sender: T::AccountId,1570 collection: &CollectionHandle<T>,1571 owner: T::AccountId,1572 items_data: Vec<CreateItemData>,1573 ) -> DispatchResult {1574 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;15751576 for data in &items_data {1577 Self::validate_create_item_args(&collection, data)?;1578 }1579 for data in &items_data {1580 Self::create_item_no_validation(&collection, owner.clone(), data.clone())?;1581 }15821583 Ok(())1584 }15851586 pub fn toggle_white_list_internal(1587 sender: &T::AccountId,1588 collection: &CollectionHandle<T>,1589 address: &T::AccountId,1590 whitelisted: bool,1591 ) -> DispatchResult {1592 Self::check_owner_or_admin_permissions(&collection, sender.clone())?;15931594 if whitelisted {1595 <WhiteList<T>>::insert(collection.id, address, true);1596 } else {1597 <WhiteList<T>>::remove(collection.id, address);1598 }15991600 Ok(())1601 }16021603 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::AccountId) -> DispatchResult {1604 let collection_id = collection.id;16051606 // check token limit and account token limit1607 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1608 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1609 1610 Ok(())1611 }16121613 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult {1614 let collection_id = collection.id;16151616 // check token limit and account token limit1617 let total_items: u32 = ItemListIndex::get(collection_id)1618 .checked_add(amount)1619 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1620 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner).len() as u32)1621 .checked_add(amount)1622 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1623 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1624 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);16251626 if !Self::is_owner_or_admin_permissions(collection, sender.clone()) {1627 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1628 Self::check_white_list(collection, owner)?;1629 Self::check_white_list(collection, sender)?;1630 }16311632 Ok(())1633 }16341635 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1636 match target_collection.mode1637 {1638 CollectionMode::NFT => {1639 if let CreateItemData::NFT(data) = data {1640 // check sizes1641 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1642 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1643 } else {1644 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1645 }1646 },1647 CollectionMode::Fungible(_) => {1648 if let CreateItemData::Fungible(_) = data {1649 } else {1650 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1651 }1652 },1653 CollectionMode::ReFungible => {1654 if let CreateItemData::ReFungible(data) = data {16551656 // check sizes1657 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1658 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16591660 // Check refungibility limits1661 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1662 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1663 } else {1664 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1665 }1666 },1667 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1668 };16691670 Ok(())1671 }16721673 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1674 match data1675 {1676 CreateItemData::NFT(data) => {1677 let item = NftItemType {1678 owner: owner.clone(),1679 const_data: data.const_data,1680 variable_data: data.variable_data1681 };16821683 Self::add_nft_item(collection, item)?;1684 },1685 CreateItemData::Fungible(data) => {1686 Self::add_fungible_item(collection, &owner, data.value)?;1687 },1688 CreateItemData::ReFungible(data) => {1689 let mut owner_list = Vec::new();1690 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16911692 let item = ReFungibleItemType {1693 owner: owner_list,1694 const_data: data.const_data,1695 variable_data: data.variable_data1696 };16971698 Self::add_refungible_item(collection, item)?;1699 }1700 };17011702 Ok(())1703 }17041705 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::AccountId, value: u128) -> DispatchResult {1706 let collection_id = collection.id;17071708 // Does new owner already have an account?1709 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner).value;17101711 // Mint 1712 let item = FungibleItemType {1713 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1714 };1715 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);17161717 // Update balance1718 let new_balance = <Balance<T>>::get(collection_id, owner)1719 .checked_add(value)1720 .ok_or(Error::<T>::NumOverflow)?;1721 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17221723 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1724 Ok(())1725 }17261727 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1728 let collection_id = collection.id;17291730 let current_index = <ItemListIndex>::get(collection_id)1731 .checked_add(1)1732 .ok_or(Error::<T>::NumOverflow)?;1733 let itemcopy = item.clone();17341735 ensure!(1736 item.owner.len() == 1,1737 Error::<T>::BadCreateRefungibleCall,1738 );1739 let item_owner = item.owner.first().expect("only one owner is defined");17401741 let value = item_owner.fraction;1742 let owner = item_owner.owner.clone();17431744 Self::add_token_index(collection_id, current_index, &owner)?;17451746 <ItemListIndex>::insert(collection_id, current_index);1747 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17481749 // Update balance1750 let new_balance = <Balance<T>>::get(collection_id, &owner)1751 .checked_add(value)1752 .ok_or(Error::<T>::NumOverflow)?;1753 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17541755 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1756 Ok(())1757 }17581759 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::AccountId>) -> DispatchResult {1760 let collection_id = collection.id;17611762 let current_index = <ItemListIndex>::get(collection_id)1763 .checked_add(1)1764 .ok_or(Error::<T>::NumOverflow)?;17651766 let item_owner = item.owner.clone();1767 Self::add_token_index(collection_id, current_index, &item.owner)?;17681769 <ItemListIndex>::insert(collection_id, current_index);1770 <NftItemList<T>>::insert(collection_id, current_index, item);17711772 // Update balance1773 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1774 .checked_add(1)1775 .ok_or(Error::<T>::NumOverflow)?;1776 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17771778 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1779 Ok(())1780 }17811782 fn burn_refungible_item(1783 collection: &CollectionHandle<T>,1784 item_id: TokenId,1785 owner: &T::AccountId,1786 ) -> DispatchResult {1787 let collection_id = collection.id;17881789 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1790 .ok_or(Error::<T>::TokenNotFound)?;1791 let rft_balance = token1792 .owner1793 .iter()1794 .find(|&i| i.owner == *owner)1795 .ok_or(Error::<T>::TokenNotFound)?;1796 Self::remove_token_index(collection_id, item_id, owner)?;17971798 // update balance1799 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1800 .checked_sub(rft_balance.fraction)1801 .ok_or(Error::<T>::NumOverflow)?;1802 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);18031804 // Re-create owners list with sender removed1805 let index = token1806 .owner1807 .iter()1808 .position(|i| i.owner == *owner)1809 .expect("owned item is exists");1810 token.owner.remove(index);1811 let owner_count = token.owner.len();18121813 // Burn the token completely if this was the last (only) owner1814 if owner_count == 0 {1815 <ReFungibleItemList<T>>::remove(collection_id, item_id);1816 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1817 }1818 else {1819 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1820 }18211822 Ok(())1823 }18241825 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1826 let collection_id = collection.id;18271828 let item = <NftItemList<T>>::get(collection_id, item_id)1829 .ok_or(Error::<T>::TokenNotFound)?;1830 Self::remove_token_index(collection_id, item_id, &item.owner)?;18311832 // update balance1833 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1834 .checked_sub(1)1835 .ok_or(Error::<T>::NumOverflow)?;1836 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1837 <NftItemList<T>>::remove(collection_id, item_id);1838 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);18391840 Ok(())1841 }18421843 fn burn_fungible_item(owner: &T::AccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1844 let collection_id = collection.id;18451846 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1847 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18481849 // update balance1850 let new_balance = <Balance<T>>::get(collection_id, owner)1851 .checked_sub(value)1852 .ok_or(Error::<T>::NumOverflow)?;1853 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18541855 if balance.value - value > 0 {1856 balance.value -= value;1857 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1858 }1859 else {1860 <FungibleItemList<T>>::remove(collection_id, owner);1861 }18621863 Ok(())1864 }18651866 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1867 Ok(<CollectionById<T>>::get(collection_id)1868 .map(|collection| CollectionHandle {1869 id: collection_id,1870 collection1871 })1872 .ok_or(Error::<T>::CollectionNotFound)?)1873 }18741875 fn save_collection(collection: CollectionHandle<T>) {1876 <CollectionById<T>>::insert(collection.id, collection.collection);1877 }18781879 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: T::AccountId) -> DispatchResult {1880 ensure!(1881 subject == target_collection.owner,1882 Error::<T>::NoPermission1883 );18841885 Ok(())1886 }18871888 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: T::AccountId) -> bool {1889 subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1890 }18911892 fn check_owner_or_admin_permissions(1893 collection: &CollectionHandle<T>,1894 subject: T::AccountId,1895 ) -> DispatchResult {1896 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18971898 Ok(())1899 }19001901 fn owned_amount(1902 subject: T::AccountId,1903 target_collection: &CollectionHandle<T>,1904 item_id: TokenId,1905 ) -> Option<u128> {1906 let collection_id = target_collection.id;19071908 match target_collection.mode {1909 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == subject)1910 .then(|| 1),1911 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject)1912 .value),1913 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1914 .owner1915 .iter()1916 .find(|i| i.owner == subject)1917 .map(|i| i.fraction),1918 CollectionMode::Invalid => None,1919 }1920 }19211922 fn is_item_owner(subject: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1923 match target_collection.mode {1924 CollectionMode::Fungible(_) => true,1925 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),1926 }1927 }19281929 fn check_white_list(collection: &CollectionHandle<T>, address: &T::AccountId) -> DispatchResult {1930 let collection_id = collection.id;19311932 let mes = Error::<T>::AddresNotInWhiteList;1933 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);19341935 Ok(())1936 }19371938 /// Check if token exists. In case of Fungible, check if there is an entry for 1939 /// the owner in fungible balances double map1940 fn token_exists(1941 target_collection: &CollectionHandle<T>,1942 item_id: TokenId,1943 ) -> DispatchResult {1944 let collection_id = target_collection.id;1945 let exists = match target_collection.mode1946 {1947 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1948 CollectionMode::Fungible(_) => true,1949 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1950 _ => false1951 };19521953 ensure!(exists == true, Error::<T>::TokenNotFound);1954 Ok(())1955 }19561957 fn transfer_fungible(1958 collection: &CollectionHandle<T>,1959 value: u128,1960 owner: &T::AccountId,1961 recipient: &T::AccountId,1962 ) -> DispatchResult {1963 let collection_id = collection.id;19641965 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1966 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19671968 // Send balance to recipient (updates balanceOf of recipient)1969 Self::add_fungible_item(collection, recipient, value)?;19701971 // update balanceOf of sender1972 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19731974 // Reduce or remove sender1975 if balance.value == value {1976 <FungibleItemList<T>>::remove(collection_id, owner);1977 }1978 else {1979 balance.value -= value;1980 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1981 }19821983 Ok(())1984 }19851986 fn transfer_refungible(1987 collection: &CollectionHandle<T>,1988 item_id: TokenId,1989 value: u128,1990 owner: T::AccountId,1991 new_owner: T::AccountId,1992 ) -> DispatchResult {1993 let collection_id = collection.id;1994 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1995 .ok_or(Error::<T>::TokenNotFound)?;19961997 let item = full_item1998 .owner1999 .iter()2000 .filter(|i| i.owner == owner)2001 .next()2002 .ok_or(Error::<T>::TokenNotFound)?;2003 let amount = item.fraction;20042005 ensure!(amount >= value, Error::<T>::TokenValueTooLow);20062007 // update balance2008 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2009 .checked_sub(value)2010 .ok_or(Error::<T>::NumOverflow)?;2011 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20122013 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2014 .checked_add(value)2015 .ok_or(Error::<T>::NumOverflow)?;2016 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20172018 let old_owner = item.owner.clone();2019 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20202021 // transfer2022 if amount == value && !new_owner_has_account {2023 // change owner2024 // new owner do not have account2025 let mut new_full_item = full_item.clone();2026 new_full_item2027 .owner2028 .iter_mut()2029 .find(|i| i.owner == owner)2030 .expect("old owner does present in refungible")2031 .owner = new_owner.clone();2032 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20332034 // update index collection2035 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2036 } else {2037 let mut new_full_item = full_item.clone();2038 new_full_item2039 .owner2040 .iter_mut()2041 .find(|i| i.owner == owner)2042 .expect("old owner does present in refungible")2043 .fraction -= value;20442045 // separate amount2046 if new_owner_has_account {2047 // new owner has account2048 new_full_item2049 .owner2050 .iter_mut()2051 .find(|i| i.owner == new_owner)2052 .expect("new owner has account")2053 .fraction += value;2054 } else {2055 // new owner do not have account2056 new_full_item.owner.push(Ownership {2057 owner: new_owner.clone(),2058 fraction: value,2059 });2060 Self::add_token_index(collection_id, item_id, &new_owner)?;2061 }20622063 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2064 }20652066 Ok(())2067 }20682069 fn transfer_nft(2070 collection: &CollectionHandle<T>,2071 item_id: TokenId,2072 sender: T::AccountId,2073 new_owner: T::AccountId,2074 ) -> DispatchResult {2075 let collection_id = collection.id;2076 let mut item = <NftItemList<T>>::get(collection_id, item_id)2077 .ok_or(Error::<T>::TokenNotFound)?;20782079 ensure!(2080 sender == item.owner,2081 Error::<T>::MustBeTokenOwner2082 );20832084 // update balance2085 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2086 .checked_sub(1)2087 .ok_or(Error::<T>::NumOverflow)?;2088 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20892090 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2091 .checked_add(1)2092 .ok_or(Error::<T>::NumOverflow)?;2093 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20942095 // change owner2096 let old_owner = item.owner.clone();2097 item.owner = new_owner.clone();2098 <NftItemList<T>>::insert(collection_id, item_id, item);20992100 // update index collection2101 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;21022103 Ok(())2104 }2105 2106 fn set_re_fungible_variable_data(2107 collection: &CollectionHandle<T>,2108 item_id: TokenId,2109 data: Vec<u8>2110 ) -> DispatchResult {2111 let collection_id = collection.id;2112 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2113 .ok_or(Error::<T>::TokenNotFound)?;21142115 item.variable_data = data;21162117 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);21182119 Ok(())2120 }21212122 fn set_nft_variable_data(2123 collection: &CollectionHandle<T>,2124 item_id: TokenId,2125 data: Vec<u8>2126 ) -> DispatchResult {2127 let collection_id = collection.id;2128 let mut item = <NftItemList<T>>::get(collection_id, item_id)2129 .ok_or(Error::<T>::TokenNotFound)?;2130 2131 item.variable_data = data;21322133 <NftItemList<T>>::insert(collection_id, item_id, item);2134 2135 Ok(())2136 }21372138 #[allow(dead_code)]2139 fn init_collection(item: &Collection<T>) {2140 // check params2141 assert!(2142 item.decimal_points <= MAX_DECIMAL_POINTS,2143 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2144 );2145 assert!(2146 item.name.len() <= 64,2147 "Collection name can not be longer than 63 char"2148 );2149 assert!(2150 item.name.len() <= 256,2151 "Collection description can not be longer than 255 char"2152 );2153 assert!(2154 item.token_prefix.len() <= 16,2155 "Token prefix can not be longer than 15 char"2156 );21572158 // Generate next collection ID2159 let next_id = CreatedCollectionCount::get()2160 .checked_add(1)2161 .unwrap();21622163 CreatedCollectionCount::put(next_id);2164 }21652166 #[allow(dead_code)]2167 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2168 let current_index = <ItemListIndex>::get(collection_id)2169 .checked_add(1)2170 .unwrap();21712172 let item_owner = item.owner.clone();2173 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21742175 <ItemListIndex>::insert(collection_id, current_index);21762177 // Update balance2178 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2179 .checked_add(1)2180 .unwrap();2181 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2182 }21832184 #[allow(dead_code)]2185 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2186 let current_index = <ItemListIndex>::get(collection_id)2187 .checked_add(1)2188 .unwrap();21892190 Self::add_token_index(collection_id, current_index, owner).unwrap();21912192 <ItemListIndex>::insert(collection_id, current_index);21932194 // Update balance2195 let new_balance = <Balance<T>>::get(collection_id, owner)2196 .checked_add(item.value)2197 .unwrap();2198 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2199 }22002201 #[allow(dead_code)]2202 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2203 let current_index = <ItemListIndex>::get(collection_id)2204 .checked_add(1)2205 .unwrap();22062207 let value = item.owner.first().unwrap().fraction;2208 let owner = item.owner.first().unwrap().owner.clone();22092210 Self::add_token_index(collection_id, current_index, &owner).unwrap();22112212 <ItemListIndex>::insert(collection_id, current_index);22132214 // Update balance2215 let new_balance = <Balance<T>>::get(collection_id, &owner)2216 .checked_add(value)2217 .unwrap();2218 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2219 }22202221 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {2222 // add to account limit2223 if <AccountItemCount<T>>::contains_key(owner) {22242225 // bound Owned tokens by a single address2226 let count = <AccountItemCount<T>>::get(owner);2227 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);22282229 <AccountItemCount<T>>::insert(owner.clone(), count2230 .checked_add(1)2231 .ok_or(Error::<T>::NumOverflow)?);2232 }2233 else {2234 <AccountItemCount<T>>::insert(owner.clone(), 1);2235 }22362237 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2238 if list_exists {2239 let mut list = <AddressTokens<T>>::get(collection_id, owner);2240 let item_contains = list.contains(&item_index.clone());22412242 if !item_contains {2243 list.push(item_index.clone());2244 }22452246 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2247 } else {2248 let mut itm = Vec::new();2249 itm.push(item_index.clone());2250 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2251 }22522253 Ok(())2254 }22552256 fn remove_token_index(2257 collection_id: CollectionId,2258 item_index: TokenId,2259 owner: &T::AccountId,2260 ) -> DispatchResult {22612262 // update counter2263 <AccountItemCount<T>>::insert(owner.clone(), 2264 <AccountItemCount<T>>::get(owner)2265 .checked_sub(1)2266 .ok_or(Error::<T>::NumOverflow)?);226722682269 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2270 if list_exists {2271 let mut list = <AddressTokens<T>>::get(collection_id, owner);2272 let item_contains = list.contains(&item_index.clone());22732274 if item_contains {2275 list.retain(|&item| item != item_index);2276 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2277 }2278 }22792280 Ok(())2281 }22822283 fn move_token_index(2284 collection_id: CollectionId,2285 item_index: TokenId,2286 old_owner: &T::AccountId,2287 new_owner: &T::AccountId,2288 ) -> DispatchResult {2289 Self::remove_token_index(collection_id, item_index, old_owner)?;2290 Self::add_token_index(collection_id, item_index, new_owner)?;22912292 Ok(())2293 }2294 2295 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2296 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);22972298 Ok(())2299 }2300}runtime/src/chain_extension.rsdiffbeforeafterboth--- a/runtime/src/chain_extension.rs
+++ b/runtime/src/chain_extension.rs
@@ -19,6 +19,8 @@
pub use pallet_nft::*;
use nft_data_structs::*;
+use crate::Vec;
+
/// Create item parameters
#[derive(Debug, PartialEq, Encode, Decode)]
pub struct NFTExtCreateItem<E: Ext> {
@@ -36,13 +38,54 @@
pub amount: u128,
}
+#[derive(Debug, PartialEq, Encode, Decode)]
+pub struct NFTExtCreateMultipleItems<E: Ext> {
+ pub owner: <E::T as SysConfig>::AccountId,
+ pub collection_id: u32,
+ pub data: Vec<CreateItemData>,
+}
+
+#[derive(Debug, PartialEq, Encode, Decode)]
+pub struct NFTExtApprove<E: Ext> {
+ pub spender: <E::T as SysConfig>::AccountId,
+ pub collection_id: u32,
+ pub item_id: u32,
+ pub amount: u128,
+}
+
+#[derive(Debug, PartialEq, Encode, Decode)]
+pub struct NFTExtTransferFrom<E: Ext> {
+ pub owner: <E::T as SysConfig>::AccountId,
+ pub recipient: <E::T as SysConfig>::AccountId,
+ pub collection_id: u32,
+ pub item_id: u32,
+ pub amount: u128,
+}
+
+#[derive(Debug, PartialEq, Encode, Decode)]
+pub struct NFTExtSetVariableMetaData {
+ pub collection_id: u32,
+ pub item_id: u32,
+ pub data: Vec<u8>,
+}
+
+#[derive(Debug, PartialEq, Encode, Decode)]
+pub struct NFTExtToggleWhiteList<E: Ext> {
+ pub collection_id: u32,
+ pub address: <E::T as SysConfig>::AccountId,
+ pub whitelisted: bool,
+}
+
/// The chain Extension of NFT pallet
pub struct NFTExtension;
+pub type NftWeightInfoOf<C> = <C as pallet_nft::Config>::WeightInfo;
+
impl<C: Config + pallet_contracts::Config> ChainExtension<C> for NFTExtension {
fn call<E: Ext>(func_id: u32, env: Environment<E, InitState>) -> Result<RetVal, DispatchError>
where
E: Ext<T = C>,
+ C: pallet_nft::Config,
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
// The memory of the vm stores buf in scale-codec
@@ -50,37 +93,129 @@
0 => {
let mut env = env.buf_in_buf_out();
let input: NFTExtTransfer<E> = env.read_as()?;
+ env.charge_weight(NftWeightInfoOf::<C>::transfer())?;
let collection = pallet_nft::Module::<C>::get_collection(input.collection_id)?;
- match pallet_nft::Module::<C>::transfer_internal(
- env.ext().caller().clone(),
+ pallet_nft::Module::<C>::transfer_internal(
+ env.ext().address().clone(),
input.recipient,
&collection,
input.token_id,
input.amount,
- ) {
- Ok(_) => Ok(RetVal::Converging(func_id)),
- _ => Err(DispatchError::Other("Transfer error"))
- }
+ )?;
+
+ Ok(RetVal::Converging(0))
},
1 => {
// Create Item
let mut env = env.buf_in_buf_out();
let input: NFTExtCreateItem<E> = env.read_as()?;
+ env.charge_weight(NftWeightInfoOf::<C>::create_item(input.data.len()))?;
- match pallet_nft::Module::<C>::create_item_internal(
+ pallet_nft::Module::<C>::create_item_internal(
env.ext().address().clone(),
input.collection_id,
input.owner,
input.data,
- ) {
- Ok(_) => Ok(RetVal::Converging(func_id)),
- _ => Err(DispatchError::Other("CreateItem error"))
- }
+ )?;
+
+ Ok(RetVal::Converging(0))
},
+ 2 => {
+ // Create multiple items
+ let mut env = env.buf_in_buf_out();
+ let input: NFTExtCreateMultipleItems<E> = env.read_as()?;
+ env.charge_weight(NftWeightInfoOf::<C>::create_item(
+ input.data.iter()
+ .map(|i| i.len())
+ .sum()
+ ))?;
+
+ let collection = pallet_nft::Module::<C>::get_collection(input.collection_id)?;
+
+ pallet_nft::Module::<C>::create_multiple_items_internal(
+ env.ext().address().clone(),
+ &collection,
+ input.owner,
+ input.data,
+ )?;
+
+ Ok(RetVal::Converging(0))
+ },
+ 3 => {
+ // Approve
+ let mut env = env.buf_in_buf_out();
+ let input: NFTExtApprove<E> = env.read_as()?;
+ env.charge_weight(NftWeightInfoOf::<C>::approve())?;
+
+ let collection = pallet_nft::Module::<C>::get_collection(input.collection_id)?;
+
+ pallet_nft::Module::<C>::approve_internal(
+ env.ext().address().clone(),
+ input.spender,
+ &collection,
+ input.item_id,
+ input.amount,
+ )?;
+
+ Ok(RetVal::Converging(0))
+ },
+ 4 => {
+ // Transfer from
+ let mut env = env.buf_in_buf_out();
+ let input: NFTExtTransferFrom<E> = env.read_as()?;
+ env.charge_weight(NftWeightInfoOf::<C>::transfer_from())?;
+
+ let collection = pallet_nft::Module::<C>::get_collection(input.collection_id)?;
+
+ pallet_nft::Module::<C>::transfer_from_internal(
+ env.ext().address().clone(),
+ input.owner,
+ input.recipient,
+ &collection,
+ input.item_id,
+ input.amount
+ )?;
+
+ Ok(RetVal::Converging(0))
+ },
+ 5 => {
+ // Set variable metadata
+ let mut env = env.buf_in_buf_out();
+ let input: NFTExtSetVariableMetaData = env.read_as()?;
+ env.charge_weight(NftWeightInfoOf::<C>::set_variable_meta_data())?;
+
+ let collection = pallet_nft::Module::<C>::get_collection(input.collection_id)?;
+
+ pallet_nft::Module::<C>::set_variable_meta_data_internal(
+ env.ext().address().clone(),
+ &collection,
+ input.item_id,
+ input.data,
+ )?;
+
+ Ok(RetVal::Converging(0))
+ },
+ 6 => {
+ // Toggle whitelist
+ let mut env = env.buf_in_buf_out();
+ let input: NFTExtToggleWhiteList<E> = env.read_as()?;
+ env.charge_weight(NftWeightInfoOf::<C>::add_to_white_list())?;
+
+ let collection = pallet_nft::Module::<C>::get_collection(input.collection_id)?;
+
+ pallet_nft::Module::<C>::toggle_white_list_internal(
+ &env.ext().address().clone(),
+ &collection,
+ &input.address,
+ input.whitelisted,
+ )?;
+
+ Ok(RetVal::Converging(0))
+ }
_ => {
- panic!("Passed unknown func_id to test chain extension: {}", func_id);
+ Err(DispatchError::Other("unknown chain_extension func_id"))
}
}
}
smart_contracs/transfer/Cargo.tomldiffbeforeafterboth--- a/smart_contracs/transfer/Cargo.toml
+++ b/smart_contracs/transfer/Cargo.toml
@@ -4,15 +4,17 @@
authors = ["[Greg Zaitsev] <[your_email]>"]
edition = "2018"
+[workspace]
+
[dependencies]
-ink_primitives = { git = "https://github.com/usetech-llc/ink", branch = "unique", default-features = false }
-ink_metadata = { git = "https://github.com/usetech-llc/ink", branch = "unique", default-features = false, features = ["derive"], optional = true }
-ink_env = { git = "https://github.com/usetech-llc/ink", branch = "unique", default-features = false }
-ink_storage = { git = "https://github.com/usetech-llc/ink", branch = "unique", default-features = false }
-ink_lang = { git = "https://github.com/usetech-llc/ink", branch = "unique", default-features = false }
+ink_primitives = { default-features = false }
+ink_metadata = { default-features = false, features = ["derive"], optional = true }
+ink_env = { default-features = false }
+ink_storage = { default-features = false }
+ink_lang = { default-features = false }
-scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] }
-scale-info = { version = "0.4.1", default-features = false, features = ["derive"], optional = true }
+scale = { package = "parity-scale-codec", version = "2.1.1", default-features = false, features = ["derive"] }
+scale-info = { version = "0.6.0", default-features = false, features = ["derive"] }
[lib]
name = "nft_transfer"
@@ -28,6 +30,7 @@
"ink_metadata/std",
"ink_env/std",
"ink_storage/std",
+ "ink_lang/std",
"ink_primitives/std",
"scale/std",
"scale-info/std",
smart_contracs/transfer/lib.rsdiffbeforeafterboth--- a/smart_contracs/transfer/lib.rs
+++ b/smart_contracs/transfer/lib.rs
@@ -1,4 +1,6 @@
#![cfg_attr(not(feature = "std"), no_std)]
+extern crate alloc;
+use alloc::vec::Vec;
use ink_lang as ink;
use ink_env::{Environment, DefaultEnvironment};
@@ -36,6 +38,24 @@
}
}
+#[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+pub enum CreateItemData {
+ Nft {
+ const_data: Vec<u8>,
+ variable_data: Vec<u8>,
+ },
+ Fungible {
+ value: u128,
+ },
+ ReFungible {
+ const_data: Vec<u8>,
+ variable_data: Vec<u8>,
+ pieces: u128,
+ },
+}
+
+type DefaultAccountId = <DefaultEnvironment as Environment>::AccountId;
+
#[ink::chain_extension]
pub trait NftChainExtension {
type ErrorCode = NftErrorCode;
@@ -43,11 +63,26 @@
/// Transfer one NFT token from sender
///
#[ink(extension = 0, returns_result = false)]
- fn transfer(recipient: <DefaultEnvironment as Environment>::AccountId, collection_id: u32, token_id: u32, amount: u128);
+ fn transfer(recipient: DefaultAccountId, collection_id: u32, token_id: u32, amount: u128);
+ #[ink(extension = 1, returns_result = false)]
+ fn create_item(owner: DefaultAccountId, collection_id: u32, data: CreateItemData);
+ #[ink(extension = 2, returns_result = false)]
+ fn create_multiple_items(owner: DefaultAccountId, collection_id: u32, data: Vec<CreateItemData>);
+ #[ink(extension = 3, returns_result = false)]
+ fn approve(spender: DefaultAccountId, collection_id: u32, item_id: u32, amount: u128);
+ #[ink(extension = 4, returns_result = false)]
+ fn transfer_from(owner: DefaultAccountId, recipient: DefaultAccountId, collection_id: u32, item_id: u32, amount: u128);
+ #[ink(extension = 5, returns_result = false)]
+ fn set_variable_meta_data(collection_id: u32, item_id: u32, data: Vec<u8>);
+ #[ink(extension = 6, returns_result = false)]
+ fn toggle_white_list(collection_id: u32, address: DefaultAccountId, whitelisted: bool);
}
-#[ink::contract(env = crate::NftEnvironment)]
+#[ink::contract(env = crate::NftEnvironment, dynamic_storage_allocator = true)]
mod nft_transfer {
+ use alloc::vec::Vec;
+ // use ink_storage::Vec;
+ use crate::CreateItemData;
#[ink(storage)]
pub struct NftTransfer {
@@ -69,6 +104,42 @@
.extension()
.transfer(recipient, collection_id, token_id, amount);
}
+ #[ink(message)]
+ pub fn create_item(&mut self, recipient: AccountId, collection_id: u32, data: CreateItemData) {
+ let _ = self.env()
+ .extension()
+ .create_item(recipient, collection_id, data);
+ }
+ #[ink(message)]
+ pub fn create_multiple_items(&mut self, owner: AccountId, collection_id: u32, data: Vec<CreateItemData>) {
+ let _ = self.env()
+ .extension()
+ .create_multiple_items(owner, collection_id, data);
+ }
+ #[ink(message)]
+ pub fn approve(&mut self, spender: AccountId, collection_id: u32, item_id: u32, amount: u128) {
+ let _ = self.env()
+ .extension()
+ .approve(spender, collection_id, item_id, amount);
+ }
+ #[ink(message)]
+ pub fn transfer_from(&mut self, owner: AccountId, recipient: AccountId, collection_id: u32, item_id: u32, amount: u128) {
+ let _ = self.env()
+ .extension()
+ .transfer_from(owner, recipient, collection_id, item_id, amount);
+ }
+ #[ink(message)]
+ pub fn set_variable_meta_data(&mut self, collection_id: u32, item_id: u32, data: Vec<u8>) {
+ let _ = self.env()
+ .extension()
+ .set_variable_meta_data(collection_id, item_id, data);
+ }
+ #[ink(message)]
+ pub fn toggle_white_list(&mut self, collection_id: u32, address: AccountId, whitelisted: bool) {
+ let _ = self.env()
+ .extension()
+ .toggle_white_list(collection_id, address, whitelisted);
+ }
}
tests/src/contracts.test.tsdiffbeforeafterboth--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -16,9 +16,15 @@
} from "./util/contracthelpers";
import {
+ addToWhiteListExpectSuccess,
+ approveExpectSuccess,
createCollectionExpectSuccess,
createItemExpectSuccess,
- getGenericResult
+ enablePublicMintingExpectSuccess,
+ enableWhiteListExpectSuccess,
+ getGenericResult,
+ isWhitelisted,
+ transferFromExpectSuccess
} from "./util/helpers";
@@ -26,7 +32,7 @@
const expect = chai.expect;
const value = 0;
-const gasLimit = 3000n * 1000000n;
+const gasLimit = 9000n * 1000000n;
const marketContractAddress = '5CYN9j3YvRkqxewoxeSvRbhAym4465C57uMmX5j4yz99L5H6';
describe('Contracts', () => {
@@ -53,8 +59,10 @@
expect(newContractInstance.address.toString()).to.equal(marketContractAddress);
});
});
+});
- it('Can transfer NFT using smart contract.', async () => {
+describe.only('Chain extensions', () => {
+ it('Transfer CE', async () => {
await usingApi(async api => {
const alice = privateKey("//Alice");
const bob = privateKey("//Bob");
@@ -63,6 +71,9 @@
const collectionId = await createCollectionExpectSuccess();
const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');
const [contract, deployer] = await deployTransferContract(api);
+ const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, contract.address);
+ await submitTransactionAsync(alice, changeAdminTx);
+
const tokenBefore: any = (await api.query.nft.nftItemList(collectionId, tokenId) as any).unwrap();
// Transfer
@@ -77,4 +88,165 @@
expect(tokenAfter.Owner.toString()).to.be.equal(bob.address);
});
});
+
+ it('Mint CE', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const collectionId = await createCollectionExpectSuccess();
+ const [contract, deployer] = await deployTransferContract(api);
+ await enablePublicMintingExpectSuccess(alice, collectionId);
+ await enableWhiteListExpectSuccess(alice, collectionId);
+ await addToWhiteListExpectSuccess(alice, collectionId, contract.address);
+ await addToWhiteListExpectSuccess(alice, collectionId, bob.address);
+
+ const transferTx = contract.tx.createItem(value, gasLimit, bob.address, collectionId, { Nft: {const_data: '0x010203', variable_data: '0x020304' }});
+ const events = await submitTransactionAsync(alice, transferTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+
+ const tokensAfter: any = (await api.query.nft.nftItemList.entries(collectionId) as any).map((kv: any) => kv[1].toJSON());
+ expect(tokensAfter).to.be.deep.equal([
+ {
+ Owner: bob.address,
+ ConstData: '0x010203',
+ VariableData: '0x020304',
+ },
+ ]);
+ });
+ });
+
+ it('Bulk mint CE', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const collectionId = await createCollectionExpectSuccess();
+ const [contract, deployer] = await deployTransferContract(api);
+ await enablePublicMintingExpectSuccess(alice, collectionId);
+ await enableWhiteListExpectSuccess(alice, collectionId);
+ await addToWhiteListExpectSuccess(alice, collectionId, contract.address);
+ await addToWhiteListExpectSuccess(alice, collectionId, bob.address);
+
+ const transferTx = contract.tx.createMultipleItems(value, gasLimit, bob.address, collectionId, [
+ { Nft: { const_data: '0x010203', variable_data: '0x020304' } },
+ { Nft: { const_data: '0x010204', variable_data: '0x020305' } },
+ { Nft: { const_data: '0x010205', variable_data: '0x020306' } }
+ ]);
+ const events = await submitTransactionAsync(alice, transferTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+
+ const tokensAfter: any = (await api.query.nft.nftItemList.entries(collectionId) as any)
+ .map((kv: any) => kv[1].toJSON())
+ .sort((a: any, b: any) => a.ConstData.localeCompare(b.ConstData));
+ expect(tokensAfter).to.be.deep.equal([
+ {
+ Owner: bob.address,
+ ConstData: '0x010203',
+ VariableData: '0x020304',
+ },
+ {
+ Owner: bob.address,
+ ConstData: '0x010204',
+ VariableData: '0x020305',
+ },
+ {
+ Owner: bob.address,
+ ConstData: '0x010205',
+ VariableData: '0x020306',
+ },
+ ]);
+ });
+ });
+
+ it('Approve CE', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+ const charlie = privateKey('//Charlie');
+
+ const collectionId = await createCollectionExpectSuccess();
+ const [contract, deployer] = await deployTransferContract(api);
+ const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', contract.address);
+
+ const transferTx = contract.tx.approve(value, gasLimit, bob.address, collectionId, tokenId, 1);
+ const events = await submitTransactionAsync(alice, transferTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+
+ await transferFromExpectSuccess(collectionId, tokenId, bob, contract.address.toString(), charlie, 1, 'NFT');
+ });
+ });
+
+ it('TransferFrom CE', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+ const charlie = privateKey('//Charlie');
+
+ const collectionId = await createCollectionExpectSuccess();
+ const [contract, deployer] = await deployTransferContract(api);
+ const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address);
+ await approveExpectSuccess(collectionId, tokenId, bob, contract.address.toString(), 1);
+
+ const transferTx = contract.tx.transferFrom(value, gasLimit, bob.address, charlie.address, collectionId, tokenId, 1);
+ const events = await submitTransactionAsync(alice, transferTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+
+ const token: any = (await api.query.nft.nftItemList(collectionId, tokenId) as any).unwrap()
+ expect(token.Owner.toString()).to.be.equal(charlie.address);
+ });
+ });
+
+ it('SetVariableMetaData CE', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+
+ const collectionId = await createCollectionExpectSuccess();
+ const [contract, deployer] = await deployTransferContract(api);
+ const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', contract.address);
+
+ const transferTx = contract.tx.setVariableMetaData(value, gasLimit, collectionId, tokenId, '0x121314');
+ const events = await submitTransactionAsync(alice, transferTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+
+ const token: any = (await api.query.nft.nftItemList(collectionId, tokenId) as any).unwrap()
+ expect(token.VariableData.toString()).to.be.equal('0x121314');
+ });
+ });
+
+ it('ToggleWhiteList CE', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const collectionId = await createCollectionExpectSuccess();
+ const [contract, deployer] = await deployTransferContract(api);
+ const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, contract.address);
+ await submitTransactionAsync(alice, changeAdminTx);
+
+ expect(await isWhitelisted(collectionId, bob.address)).to.be.false;
+
+ {
+ const transferTx = contract.tx.toggleWhiteList(value, gasLimit, collectionId, bob.address, true);
+ const events = await submitTransactionAsync(alice, transferTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+
+ expect(await isWhitelisted(collectionId, bob.address)).to.be.true;
+ }
+ {
+ const transferTx = contract.tx.toggleWhiteList(value, gasLimit, collectionId, bob.address, false);
+ const events = await submitTransactionAsync(alice, transferTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+
+ expect(await isWhitelisted(collectionId, bob.address)).to.be.false;
+ }
+ });
+ });
});
tests/src/transfer_contract/metadata.jsondiffbeforeafterboth--- a/tests/src/transfer_contract/metadata.json
+++ b/tests/src/transfer_contract/metadata.json
@@ -1,9 +1,9 @@
{
"metadataVersion": "0.1.0",
"source": {
- "hash": "0xfbbc0729acefed9d99f93f6cbb751d12e317958fd0fe183f5781329b37f1bf6e",
- "language": "ink! 3.0.0-rc2",
- "compiler": "rustc 1.51.0-nightly"
+ "hash": "0xc6c3f47adeafe86d1674ed72c7179605787842f2f05a2d7da0dbabf3c4fa1aa8",
+ "language": "ink! 3.0.0-rc3",
+ "compiler": "rustc 1.52.0-nightly"
},
"contract": {
"name": "nft_transfer",
@@ -24,7 +24,7 @@
"name": [
"default"
],
- "selector": "0x6a3712e2"
+ "selector": "0xed4b9d1b"
}
],
"docs": [],
@@ -78,7 +78,268 @@
],
"payable": false,
"returnType": null,
- "selector": "0xfae3a09d"
+ "selector": "0x84a15da1"
+ },
+ {
+ "args": [
+ {
+ "name": "recipient",
+ "type": {
+ "displayName": [
+ "AccountId"
+ ],
+ "type": 1
+ }
+ },
+ {
+ "name": "collection_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "data",
+ "type": {
+ "displayName": [
+ "CreateItemData"
+ ],
+ "type": 6
+ }
+ }
+ ],
+ "docs": [],
+ "mutates": true,
+ "name": [
+ "create_item"
+ ],
+ "payable": false,
+ "returnType": null,
+ "selector": "0xd7c3f083"
+ },
+ {
+ "args": [
+ {
+ "name": "owner",
+ "type": {
+ "displayName": [
+ "AccountId"
+ ],
+ "type": 1
+ }
+ },
+ {
+ "name": "collection_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "data",
+ "type": {
+ "displayName": [
+ "Vec"
+ ],
+ "type": 8
+ }
+ }
+ ],
+ "docs": [],
+ "mutates": true,
+ "name": [
+ "create_multiple_items"
+ ],
+ "payable": false,
+ "returnType": null,
+ "selector": "0x15f9a1eb"
+ },
+ {
+ "args": [
+ {
+ "name": "spender",
+ "type": {
+ "displayName": [
+ "AccountId"
+ ],
+ "type": 1
+ }
+ },
+ {
+ "name": "collection_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "item_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "amount",
+ "type": {
+ "displayName": [
+ "u128"
+ ],
+ "type": 5
+ }
+ }
+ ],
+ "docs": [],
+ "mutates": true,
+ "name": [
+ "approve"
+ ],
+ "payable": false,
+ "returnType": null,
+ "selector": "0x681266a0"
+ },
+ {
+ "args": [
+ {
+ "name": "owner",
+ "type": {
+ "displayName": [
+ "AccountId"
+ ],
+ "type": 1
+ }
+ },
+ {
+ "name": "recipient",
+ "type": {
+ "displayName": [
+ "AccountId"
+ ],
+ "type": 1
+ }
+ },
+ {
+ "name": "collection_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "item_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "amount",
+ "type": {
+ "displayName": [
+ "u128"
+ ],
+ "type": 5
+ }
+ }
+ ],
+ "docs": [],
+ "mutates": true,
+ "name": [
+ "transfer_from"
+ ],
+ "payable": false,
+ "returnType": null,
+ "selector": "0x0b396f18"
+ },
+ {
+ "args": [
+ {
+ "name": "collection_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "item_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "data",
+ "type": {
+ "displayName": [
+ "Vec"
+ ],
+ "type": 7
+ }
+ }
+ ],
+ "docs": [],
+ "mutates": true,
+ "name": [
+ "set_variable_meta_data"
+ ],
+ "payable": false,
+ "returnType": null,
+ "selector": "0xb0b26da2"
+ },
+ {
+ "args": [
+ {
+ "name": "collection_id",
+ "type": {
+ "displayName": [
+ "u32"
+ ],
+ "type": 4
+ }
+ },
+ {
+ "name": "address",
+ "type": {
+ "displayName": [
+ "AccountId"
+ ],
+ "type": 1
+ }
+ },
+ {
+ "name": "whitelisted",
+ "type": {
+ "displayName": [
+ "bool"
+ ],
+ "type": 9
+ }
+ }
+ ],
+ "docs": [],
+ "mutates": true,
+ "name": [
+ "toggle_white_list"
+ ],
+ "payable": false,
+ "returnType": null,
+ "selector": "0x98574dac"
}
]
},
@@ -93,7 +354,8 @@
"composite": {
"fields": [
{
- "type": 2
+ "type": 2,
+ "typeName": "[u8; 32]"
}
]
}
@@ -126,6 +388,82 @@
"def": {
"primitive": "u128"
}
+ },
+ {
+ "def": {
+ "variant": {
+ "variants": [
+ {
+ "fields": [
+ {
+ "name": "const_data",
+ "type": 7,
+ "typeName": "Vec<u8>"
+ },
+ {
+ "name": "variable_data",
+ "type": 7,
+ "typeName": "Vec<u8>"
+ }
+ ],
+ "name": "Nft"
+ },
+ {
+ "fields": [
+ {
+ "name": "value",
+ "type": 5,
+ "typeName": "u128"
+ }
+ ],
+ "name": "Fungible"
+ },
+ {
+ "fields": [
+ {
+ "name": "const_data",
+ "type": 7,
+ "typeName": "Vec<u8>"
+ },
+ {
+ "name": "variable_data",
+ "type": 7,
+ "typeName": "Vec<u8>"
+ },
+ {
+ "name": "pieces",
+ "type": 5,
+ "typeName": "u128"
+ }
+ ],
+ "name": "ReFungible"
+ }
+ ]
+ }
+ },
+ "path": [
+ "nft_transfer",
+ "CreateItemData"
+ ]
+ },
+ {
+ "def": {
+ "sequence": {
+ "type": 3
+ }
+ }
+ },
+ {
+ "def": {
+ "sequence": {
+ "type": 6
+ }
+ }
+ },
+ {
+ "def": {
+ "primitive": "bool"
+ }
}
]
}
\ No newline at end of file
tests/src/transfer_contract/nft_transfer.wasmdiffbeforeafterbothbinary blob — no preview
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -636,17 +636,19 @@
export async function
approveExpectSuccess(collectionId: number,
- tokenId: number, owner: IKeyringPair, approved: IKeyringPair, amount: number | bigint = 1) {
+ tokenId: number, owner: IKeyringPair, approved: IKeyringPair | string, amount: number | bigint = 1) {
+ if (typeof approved !== 'string')
+ approved = approved.address;
await usingApi(async (api: ApiPromise) => {
const allowanceBefore =
- await api.query.nft.allowances(collectionId, [tokenId, owner.address, approved.address]) as unknown as BN;
- const approveNftTx = await api.tx.nft.approve(approved.address, collectionId, tokenId, amount);
+ await api.query.nft.allowances(collectionId, [tokenId, owner.address, approved] as any) as unknown as BN;
+ const approveNftTx = await api.tx.nft.approve(approved, collectionId, tokenId, amount);
const events = await submitTransactionAsync(owner, approveNftTx);
const result = getCreateItemResult(events);
// tslint:disable-next-line:no-unused-expression
expect(result.success).to.be.true;
const allowanceAfter =
- await api.query.nft.allowances(collectionId, [tokenId, owner.address, approved.address]) as unknown as BN;
+ await api.query.nft.allowances(collectionId, [tokenId, owner.address, approved] as any) as unknown as BN;
expect(allowanceAfter.sub(allowanceBefore).toString()).to.be.equal(amount.toString());
});
}
@@ -655,17 +657,19 @@
transferFromExpectSuccess(collectionId: number,
tokenId: number,
accountApproved: IKeyringPair,
- accountFrom: IKeyringPair,
+ accountFrom: IKeyringPair | string,
accountTo: IKeyringPair,
value: number | bigint = 1,
type: string = 'NFT') {
+ if (typeof accountFrom !== 'string')
+ accountFrom = accountFrom.address;
await usingApi(async (api: ApiPromise) => {
let balanceBefore = new BN(0);
if (type === 'Fungible') {
balanceBefore = await api.query.nft.balance(collectionId, accountTo.address) as unknown as BN;
}
const transferFromTx = await api.tx.nft.transferFrom(
- accountFrom.address, accountTo.address, collectionId, tokenId, value);
+ accountFrom, accountTo.address, collectionId, tokenId, value);
const events = await submitTransactionAsync(accountApproved, transferFromTx);
const result = getCreateItemResult(events);
// tslint:disable-next-line:no-unused-expression
@@ -849,17 +853,13 @@
}
export async function createItemExpectSuccess(
- sender: IKeyringPair, collectionId: number, createMode: string, owner: string = '') {
+ sender: IKeyringPair, collectionId: number, createMode: string, owner: string | AccountId = sender.address) {
let newItemId: number = 0;
await usingApi(async (api) => {
const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString(), 10);
const Aitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();
const AItemBalance = new BigNumber(Aitem.Value);
- if (owner === '') {
- owner = sender.address;
- }
-
let tx;
if (createMode === 'Fungible') {
const createData = {fungible: {value: 10}};
@@ -887,7 +887,7 @@
}
expect(collectionId).to.be.equal(result.collectionId);
expect(BItemCount).to.be.equal(result.itemId);
- expect(owner).to.be.equal(result.recipient);
+ expect(owner.toString()).to.be.equal(result.recipient);
newItemId = result.itemId;
});
return newItemId;
@@ -974,7 +974,7 @@
return whitelisted;
}
-export async function addToWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) {
+export async function addToWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId) {
await usingApi(async (api) => {
const whiteListedBefore = (await api.query.nft.whiteList(collectionId, address)).toJSON();