difftreelog
Merge branch 'develop' into doc/architectural-changes
in: master
80 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5731,7 +5731,7 @@
[[package]]
name = "pallet-common"
-version = "0.1.0"
+version = "0.1.2"
dependencies = [
"ethereum",
"evm-coder",
@@ -5953,7 +5953,7 @@
[[package]]
name = "pallet-fungible"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"ethereum",
"evm-coder",
@@ -6198,7 +6198,7 @@
[[package]]
name = "pallet-nonfungible"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"ethereum",
"evm-coder",
@@ -6320,13 +6320,16 @@
[[package]]
name = "pallet-refungible"
-version = "0.1.0"
+version = "0.1.2"
dependencies = [
+ "ethereum",
+ "evm-coder",
"frame-benchmarking",
"frame-support",
"frame-system",
"pallet-common",
"pallet-evm",
+ "pallet-evm-coder-substrate",
"pallet-structure",
"parity-scale-codec 3.1.5",
"scale-info",
@@ -12374,7 +12377,7 @@
[[package]]
name = "uc-rpc"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"anyhow",
"jsonrpsee",
@@ -12733,7 +12736,7 @@
[[package]]
name = "up-data-structs"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"derivative",
"frame-support",
@@ -12751,7 +12754,7 @@
[[package]]
name = "up-rpc"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"pallet-common",
"pallet-evm",
Makefilediffbeforeafterboth--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,10 @@
NONFUNGIBLE_EVM_STUBS=./pallets/nonfungible/src/stubs
NONFUNGIBLE_EVM_ABI=./tests/src/eth/nonFungibleAbi.json
+REFUNGIBLE_EVM_STUBS=./pallets/refungible/src/stubs
+RENFUNGIBLE_EVM_ABI=./tests/src/eth/reFungibleAbi.json
+RENFUNGIBLE_TOKEN_EVM_ABI=./tests/src/eth/reFungibleTokenAbi.json
+
CONTRACT_HELPERS_STUBS=./pallets/evm-contract-helpers/src/stubs/
CONTRACT_HELPERS_ABI=./tests/src/eth/util/contractHelpersAbi.json
@@ -21,7 +25,7 @@
TESTS_API=./tests/src/eth/api/
.PHONY: regenerate_solidity
-regenerate_solidity: UniqueFungible.sol UniqueNFT.sol ContractHelpers.sol CollectionHelpers.sol
+regenerate_solidity: UniqueFungible.sol UniqueNFT.sol UniqueRefungibleToken.sol ContractHelpers.sol CollectionHelpers.sol
UniqueFungible.sol:
PACKAGE=pallet-fungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
@@ -30,6 +34,10 @@
UniqueNFT.sol:
PACKAGE=pallet-nonfungible NAME=erc::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
PACKAGE=pallet-nonfungible NAME=erc::gen_impl OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
+
+UniqueRefungibleToken.sol:
+ PACKAGE=pallet-refungible NAME=erc_token::gen_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
+ PACKAGE=pallet-refungible NAME=erc_token::gen_impl OUTPUT=$(REFUNGIBLE_EVM_STUBS)/$@ ./.maintain/scripts/generate_sol.sh
ContractHelpers.sol:
PACKAGE=pallet-evm-contract-helpers NAME=eth::contract_helpers_iface OUTPUT=$(TESTS_API)/$@ ./.maintain/scripts/generate_sol.sh
@@ -47,6 +55,10 @@
INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_STUBS)/UniqueNFT.raw ./.maintain/scripts/compile_stub.sh
INPUT=$(NONFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(NONFUNGIBLE_EVM_ABI) ./.maintain/scripts/generate_abi.sh
+UniqueRefungibleToken: UniqueRefungibleToken.sol
+ INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(REFUNGIBLE_EVM_STUBS)/UniqueRefungibleToken.raw ./.maintain/scripts/compile_stub.sh
+ INPUT=$(REFUNGIBLE_EVM_STUBS)/$< OUTPUT=$(RENFUNGIBLE_TOKEN_EVM_ABI) ./.maintain/scripts/generate_abi.sh
+
ContractHelpers: ContractHelpers.sol
INPUT=$(CONTRACT_HELPERS_STUBS)/$< OUTPUT=$(CONTRACT_HELPERS_STUBS)/ContractHelpers.raw ./.maintain/scripts/compile_stub.sh
INPUT=$(CONTRACT_HELPERS_STUBS)/$< OUTPUT=$(CONTRACT_HELPERS_ABI) ./.maintain/scripts/generate_abi.sh
@@ -55,7 +67,7 @@
INPUT=$(COLLECTION_HELPER_STUBS)/$< OUTPUT=$(COLLECTION_HELPER_STUBS)/CollectionHelpers.raw ./.maintain/scripts/compile_stub.sh
INPUT=$(COLLECTION_HELPER_STUBS)/$< OUTPUT=$(COLLECTION_HELPER_ABI) ./.maintain/scripts/generate_abi.sh
-evm_stubs: UniqueFungible UniqueNFT ContractHelpers CollectionHelpers
+evm_stubs: UniqueFungible UniqueNFT UniqueRefungibleToken ContractHelpers CollectionHelpers
.PHONY: _bench
_bench:
client/rpc/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/client/rpc/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
\ No newline at end of file
client/rpc/Cargo.tomldiffbeforeafterboth--- a/client/rpc/Cargo.toml
+++ b/client/rpc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "uc-rpc"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -77,6 +77,15 @@
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+ /// Returns 10 tokens owners in no particular order.
+ #[method(name = "unique_tokenOwners")]
+ fn token_owners(
+ &self,
+ collection: CollectionId,
+ token: TokenId,
+ at: Option<BlockHash>,
+ ) -> Result<Vec<CrossAccountId>>;
+
/// Get the topmost token owner in the hierarchy of a possibly nested token.
#[method(name = "unique_topmostTokenOwner")]
fn topmost_token_owner(
@@ -510,6 +519,7 @@
pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);
pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);
pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128>, unique_api);
+ pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);
}
#[allow(deprecated)]
pallets/common/CHANGELOG.MDdiffbeforeafterboth--- /dev/null
+++ b/pallets/common/CHANGELOG.MD
@@ -0,0 +1,10 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.2] - 2022-07-20
+
+### Fixed
+
+- Some methods in `#[solidity_interface]` for `CollectionHandle` had invalid
+ mutability modifiers, causing invalid stub/abi generation.
pallets/common/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/common/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
+
\ No newline at end of file
pallets/common/Cargo.tomldiffbeforeafterboth--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-common"
-version = "0.1.0"
+version = "0.1.2"
license = "GPLv3"
edition = "2021"
pallets/common/src/dispatch.rsdiffbeforeafterboth--- a/pallets/common/src/dispatch.rs
+++ b/pallets/common/src/dispatch.rs
@@ -1,3 +1,5 @@
+//! Module with interfaces for dispatching collections.
+
use frame_support::{
dispatch::{
DispatchResultWithPostInfo, PostDispatchInfo, Weight, DispatchErrorWithPostInfo,
@@ -20,7 +22,10 @@
// submit_logs is measured as part of collection pallets
}
-/// Helper function to implement substrate calls for common collection methods
+/// Helper function to implement substrate calls for common collection methods.
+///
+/// * `collection` - The collection on which to call the method.
+/// * `call` - The function in which to call the corresponding method from [`CommonCollectionOperations`].
pub fn dispatch_tx<
T: Config,
C: FnOnce(&dyn CommonCollectionOperations<T>) -> DispatchResultWithPostInfo,
@@ -64,15 +69,31 @@
result
}
+/// Interface for working with different collections through the dispatcher.
pub trait CollectionDispatch<T: Config> {
+ /// Create a collection. The collection will be created according to the value of [`data.mode`](CreateCollectionData::mode).
+ ///
+ /// * `sender` - The user who will become the owner of the collection.
+ /// * `data` - Description of the created collection.
fn create(
sender: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
) -> DispatchResult;
+
+ /// Delete the collection.
+ ///
+ /// * `sender` - The owner of the collection.
+ /// * `handle` - Collection handle.
fn destroy(sender: T::CrossAccountId, handle: CollectionHandle<T>) -> DispatchResult;
+ /// Get a specialized collection from the handle.
+ ///
+ /// * `handle` - Collection handle.
fn dispatch(handle: CollectionHandle<T>) -> Self;
+
+ /// Get the collection handle for the corresponding implementation.
fn into_inner(self) -> CollectionHandle<T>;
+ /// Get the implementation of [`CommonCollectionOperations`].
fn as_dyn(&self) -> &dyn CommonCollectionOperations<T>;
}
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! This module contains the implementation of pallet methods for evm.
+
use evm_coder::{
solidity_interface, solidity, ToLog,
types::*,
@@ -29,29 +31,40 @@
use crate::{Pallet, CollectionHandle, Config, CollectionProperties};
+/// Events for ethereum collection helper.
#[derive(ToLog)]
pub enum CollectionHelpersEvents {
+ /// The collection has been created.
CollectionCreated {
+ /// Collection owner.
#[indexed]
owner: address,
+
+ /// Collection ID.
#[indexed]
collection_id: address,
},
}
/// Does not always represent a full collection, for RFT it is either
-/// collection (Implementing ERC721), or specific collection token (Implementing ERC20)
+/// collection (Implementing ERC721), or specific collection token (Implementing ERC20).
pub trait CommonEvmHandler {
const CODE: &'static [u8];
+ /// Call precompiled handle.
fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;
}
+/// @title A contract that allows you to work with collections.
#[solidity_interface(name = "Collection")]
impl<T: Config> CollectionHandle<T>
where
T::AccountId: From<[u8; 32]>,
{
+ /// Set collection property.
+ ///
+ /// @param key Property key.
+ /// @param value Propery value.
fn set_collection_property(
&mut self,
caller: caller,
@@ -68,6 +81,9 @@
.map_err(dispatch_to_evm::<T>)
}
+ /// Delete collection property.
+ ///
+ /// @param key Property key.
fn delete_collection_property(&mut self, caller: caller, key: string) -> Result<()> {
let caller = T::CrossAccountId::from_eth(caller);
let key = <Vec<u8>>::from(key)
@@ -77,7 +93,12 @@
<Pallet<T>>::delete_collection_property(self, &caller, key).map_err(dispatch_to_evm::<T>)
}
- /// Throws error if key not found
+ /// Get collection property.
+ ///
+ /// @dev Throws error if key not found.
+ ///
+ /// @param key Property key.
+ /// @return bytes The property corresponding to the key.
fn collection_property(&self, key: string) -> Result<bytes> {
let key = <Vec<u8>>::from(key)
.try_into()
@@ -89,6 +110,11 @@
Ok(prop.to_vec())
}
+ /// Set the sponsor of the collection.
+ ///
+ /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ ///
+ /// @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.
fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result<void> {
check_is_owner_or_admin(caller, self)?;
@@ -98,6 +124,9 @@
save(self)
}
+ /// Collection sponsorship confirmation.
+ ///
+ /// @dev After setting the sponsor for the collection, it must be confirmed with this function.
fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
if !self
@@ -109,6 +138,16 @@
save(self)
}
+ /// Set limits for the collection.
+ /// @dev Throws error if limit not found.
+ /// @param limit Name of the limit. Valid names:
+ /// "accountTokenOwnershipLimit",
+ /// "sponsoredDataSize",
+ /// "sponsoredDataRateLimit",
+ /// "tokenLimit",
+ /// "sponsorTransferTimeout",
+ /// "sponsorApproveTimeout"
+ /// @param value Value of the limit.
#[solidity(rename_selector = "setCollectionLimit")]
fn set_int_limit(&mut self, caller: caller, limit: string, value: uint32) -> Result<void> {
check_is_owner_or_admin(caller, self)?;
@@ -145,6 +184,13 @@
save(self)
}
+ /// Set limits for the collection.
+ /// @dev Throws error if limit not found.
+ /// @param limit Name of the limit. Valid names:
+ /// "ownerCanTransfer",
+ /// "ownerCanDestroy",
+ /// "transfersEnabled"
+ /// @param value Value of the limit.
#[solidity(rename_selector = "setCollectionLimit")]
fn set_bool_limit(&mut self, caller: caller, limit: string, value: bool) -> Result<void> {
check_is_owner_or_admin(caller, self)?;
@@ -172,11 +218,18 @@
save(self)
}
+ /// Get contract address.
fn contract_address(&self, _caller: caller) -> Result<address> {
Ok(crate::eth::collection_id_to_address(self.id))
}
- fn add_collection_admin_substrate(&self, caller: caller, new_admin: uint256) -> Result<void> {
+ /// Add collection admin by substrate address.
+ /// @param new_admin Substrate administrator address.
+ fn add_collection_admin_substrate(
+ &mut self,
+ caller: caller,
+ new_admin: uint256,
+ ) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
let mut new_admin_arr: [u8; 32] = Default::default();
new_admin.to_big_endian(&mut new_admin_arr);
@@ -186,35 +239,44 @@
Ok(())
}
+ /// Remove collection admin by substrate address.
+ /// @param admin Substrate administrator address.
fn remove_collection_admin_substrate(
- &self,
+ &mut self,
caller: caller,
- new_admin: uint256,
+ admin: uint256,
) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
- let mut new_admin_arr: [u8; 32] = Default::default();
- new_admin.to_big_endian(&mut new_admin_arr);
- let account_id = T::AccountId::from(new_admin_arr);
- let new_admin = T::CrossAccountId::from_sub(account_id);
- <Pallet<T>>::toggle_admin(self, &caller, &new_admin, false)
- .map_err(dispatch_to_evm::<T>)?;
+ let mut admin_arr: [u8; 32] = Default::default();
+ admin.to_big_endian(&mut admin_arr);
+ let account_id = T::AccountId::from(admin_arr);
+ let admin = T::CrossAccountId::from_sub(account_id);
+ <Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
Ok(())
}
- fn add_collection_admin(&self, caller: caller, new_admin: address) -> Result<void> {
+ /// Add collection admin.
+ /// @param new_admin Address of the added administrator.
+ fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
let new_admin = T::CrossAccountId::from_eth(new_admin);
<Pallet<T>>::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::<T>)?;
Ok(())
}
- fn remove_collection_admin(&self, caller: caller, admin: address) -> Result<void> {
+ /// Remove collection admin.
+ ///
+ /// @param new_admin Address of the removed administrator.
+ fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
let admin = T::CrossAccountId::from_eth(admin);
<Pallet<T>>::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::<T>)?;
Ok(())
}
+ /// Toggle accessibility of collection nesting.
+ ///
+ /// @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'
#[solidity(rename_selector = "setCollectionNesting")]
fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result<void> {
check_is_owner_or_admin(caller, self)?;
@@ -235,6 +297,10 @@
save(self)
}
+ /// Toggle accessibility of collection nesting.
+ ///
+ /// @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
+ /// @param collections Addresses of collections that will be available for nesting.
#[solidity(rename_selector = "setCollectionNesting")]
fn set_nesting(
&mut self,
@@ -280,6 +346,10 @@
save(self)
}
+ /// Set the collection access method.
+ /// @param mode Access mode
+ /// 0 for Normal
+ /// 1 for AllowList
fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result<void> {
check_is_owner_or_admin(caller, self)?;
let permissions = CollectionPermissions {
@@ -300,20 +370,29 @@
save(self)
}
- fn add_to_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {
+ /// Add the user to the allowed list.
+ ///
+ /// @param user Address of a trusted user.
+ fn add_to_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
let user = T::CrossAccountId::from_eth(user);
<Pallet<T>>::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::<T>)?;
Ok(())
}
- fn remove_from_collection_allow_list(&self, caller: caller, user: address) -> Result<void> {
+ /// Remove the user from the allowed list.
+ ///
+ /// @param user Address of a removed user.
+ fn remove_from_collection_allow_list(&mut self, caller: caller, user: address) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
let user = T::CrossAccountId::from_eth(user);
<Pallet<T>>::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::<T>)?;
Ok(())
}
+ /// Switch permission for minting.
+ ///
+ /// @param mode Enable if "true".
fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {
check_is_owner_or_admin(caller, self)?;
let permissions = CollectionPermissions {
@@ -351,6 +430,7 @@
Ok(())
}
+/// Get the "tokenURI" key as [PropertyKey](up_data_structs::PropertyKey).
pub fn token_uri_key() -> up_data_structs::PropertyKey {
b"tokenURI"
.to_vec()
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! The module contains a number of functions for converting and checking ethereum identifiers.
+
use up_data_structs::CollectionId;
use sp_core::H160;
@@ -23,6 +25,7 @@
0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,
];
+/// Maps the ethereum address of the collection in substrate.
pub fn map_eth_to_id(eth: &H160) -> Option<CollectionId> {
if eth[0..16] != ETH_COLLECTION_PREFIX {
return None;
@@ -31,6 +34,8 @@
id_bytes.copy_from_slice(ð[16..20]);
Some(CollectionId(u32::from_be_bytes(id_bytes)))
}
+
+/// Maps the substrate collection id in ethereum.
pub fn collection_id_to_address(id: CollectionId) -> H160 {
let mut out = [0; 20];
out[0..16].copy_from_slice(Ð_COLLECTION_PREFIX);
@@ -38,6 +43,7 @@
H160(out)
}
+/// Check if the ethereum address is a collection.
pub fn is_collection(address: &H160) -> bool {
address[0..16] == ETH_COLLECTION_PREFIX
}
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -14,8 +14,43 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! # Common pallet
+//!
+//! The Common pallet provides functionality for handling collections.
+//!
+//! ## Overview
+//!
+//! The Common pallet provides an interface for common collection operations for different collection types
+//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.
+//! It also provides this functionality to EVM, see [erc] and [eth] modules.
+//!
+//! The Common pallet provides functions for:
+//!
+//! - Setting and approving collection sponsor.
+//! - Get\set\delete allow list.
+//! - Get\set\delete collection properties.
+//! - Get\set\delete collection property permissions.
+//! - Get\set\delete token property permissions.
+//! - Get\set\delete collection administrators.
+//! - Checking access permissions.
+//!
+//! ### Terminology
+//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will
+//! be possible to mint tokens.
+//!
+//! **Allow list** - List of users who have the right to minting tokens.
+//!
+//! **Collection properties** - Collection properties are simply key-value stores where various
+//! metadata can be placed.
+//!
+//! **Permissions on token properties** - For each property in the token can be set permission
+//! to change, see [`PropertyPermission`].
+//!
+//! **Collection administrator** - For a collection, you can set administrators who have the right
+//! to most actions on the collection.
+
+#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
-
extern crate alloc;
use core::ops::{Deref, DerefMut};
@@ -90,14 +125,24 @@
pub mod eth;
pub mod weights;
+/// Weight info.
pub type SelfWeightOf<T> = <T as Config>::WeightInfo;
+/// Collection handle contains information about collection data and id.
+/// Also provides functionality to count consumed gas.
+///
+/// CollectionHandle is used as a generic wrapper for collections of all types.
+/// It allows to perform common operations and queries on any collection type,
+/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].
#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]
pub struct CollectionHandle<T: Config> {
+ /// Collection id
pub id: CollectionId,
collection: Collection<T::AccountId>,
+ /// Substrate recorder for counting consumed gas
pub recorder: SubstrateRecorder<T>,
}
+
impl<T: Config> WithRecorder<T> for CollectionHandle<T> {
fn recorder(&self) -> &SubstrateRecorder<T> {
&self.recorder
@@ -106,7 +151,9 @@
self.recorder
}
}
+
impl<T: Config> CollectionHandle<T> {
+ /// Same as [CollectionHandle::new] but with an explicit gas limit.
pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {
<CollectionById<T>>::get(id).map(|collection| Self {
id,
@@ -115,6 +162,7 @@
})
}
+ /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].
pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {
<CollectionById<T>>::get(id).map(|collection| Self {
id,
@@ -123,14 +171,18 @@
})
}
+ /// Retrives collection data from storage and creates collection handle with default parameters.
+ /// If collection not found return `None`
pub fn new(id: CollectionId) -> Option<Self> {
Self::new_with_gas_limit(id, u64::MAX)
}
+ /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.
pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {
Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)
}
+ /// Consume gas for reading.
pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {
self.recorder
.consume_gas(T::GasWeightMapping::weight_to_gas(
@@ -140,6 +192,7 @@
))
}
+ /// Consume gas for writing.
pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {
self.recorder
.consume_gas(T::GasWeightMapping::weight_to_gas(
@@ -148,16 +201,27 @@
.saturating_mul(writes),
))
}
+
+ /// Save collection to storage.
pub fn save(self) -> DispatchResult {
<CollectionById<T>>::insert(self.id, self.collection);
Ok(())
}
+ /// Set collection sponsor.
+ ///
+ /// Unique collections allows sponsoring for certain actions.
+ /// This method allows you to set the sponsor of the collection.
+ /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].
pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {
self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);
Ok(())
}
+ /// Confirm sponsorship
+ ///
+ /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.
+ /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].
pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {
if self.collection.sponsorship.pending_sponsor() != Some(sender) {
return Ok(false);
@@ -168,7 +232,7 @@
}
/// Checks that the collection was created with, and must be operated upon through **Unique API**.
- /// Now check only the `external_collection` flag and if it's **true**, then return `CollectionIsExternal` error.
+ /// Now check only the `external_collection` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.
pub fn check_is_internal(&self) -> DispatchResult {
if self.external_collection {
return Err(<Error<T>>::CollectionIsExternal)?;
@@ -178,7 +242,7 @@
}
/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.
- /// Now check only the `external_collection` flag and if it's **false**, then return `CollectionIsInternal` error.
+ /// Now check only the `external_collection` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.
pub fn check_is_external(&self) -> DispatchResult {
if !self.external_collection {
return Err(<Error<T>>::CollectionIsInternal)?;
@@ -203,23 +267,34 @@
}
impl<T: Config> CollectionHandle<T> {
- pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {
- ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);
+ /// Checks if the `user` is the owner of the collection.
+ pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {
+ ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);
Ok(())
}
- pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {
- *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))
+
+ /// Returns **true** if the `user` is the owner or administrator of the collection.
+ pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {
+ *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))
}
- pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {
- ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);
+
+ /// Checks if the `user` is the owner or administrator of the collection.
+ pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {
+ ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);
Ok(())
}
+
+ /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.
pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {
self.limits.owner_can_transfer() && self.is_owner_or_admin(user)
}
+
+ /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.
pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {
self.limits.owner_can_transfer() && self.is_owner_or_admin(user)
}
+
+ /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.
pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {
ensure!(
<Allowlist<T>>::get((self.id, user)),
@@ -249,21 +324,34 @@
+ TypeInfo
+ account::Config
{
+ /// Weight information for functions of this pallet.
type WeightInfo: WeightInfo;
+
+ /// Events compatible with [`frame_system::Config::Event`].
type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
+ /// Handler of accounts and payment.
type Currency: Currency<Self::AccountId>;
+ /// Set price to create a collection.
#[pallet::constant]
type CollectionCreationPrice: Get<
<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,
>;
+
+ /// Dispatcher of operations on collections.
type CollectionDispatch: CollectionDispatch<Self>;
+ /// Account which holds the chain's treasury.
type TreasuryAccountId: Get<Self::AccountId>;
+
+ /// Address under which the CollectionHelper contract would be available.
type ContractAddress: Get<H160>;
+ /// Mapper for token addresses to Ethereum addresses.
type EvmTokenAddressMapping: TokenAddressMapping<H160>;
+
+ /// Mapper for token addresses to [`CrossAccountId`].
type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;
}
@@ -276,6 +364,7 @@
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
+ /// Maximum admins per collection.
pub fn collection_admins_limit() -> u32 {
COLLECTION_ADMINS_LIMIT
}
@@ -285,116 +374,116 @@
#[pallet::generate_deposit(pub fn deposit_event)]
pub enum Event<T: Config> {
/// New collection was created
- ///
- /// # Arguments
- ///
- /// * collection_id - Globally unique identifier of newly created collection.
- /// * mode - [CollectionMode] converted into u8.
- /// * account_id - Collection owner.
- CollectionCreated(CollectionId, u8, T::AccountId),
+ CollectionCreated(
+ /// Globally unique identifier of newly created collection.
+ CollectionId,
+ /// [`CollectionMode`] converted into _u8_.
+ u8,
+ /// Collection owner.
+ T::AccountId,
+ ),
/// New collection was destroyed
- ///
- /// # Arguments
- ///
- /// * collection_id - Globally unique identifier of collection that has been destroyed.
- CollectionDestroyed(CollectionId),
+ CollectionDestroyed(
+ /// Globally unique identifier of collection.
+ CollectionId,
+ ),
/// New item was created.
- ///
- /// # Arguments
- ///
- /// * collection_id - ID of the collection where the item was created.
- /// * item_id - ID of the item. Unique within the collection.
- /// * recipient - Owner of the newly created item.
- /// * amount - The amount of tokens that were created (always 1 for NFT).
- ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),
+ ItemCreated(
+ /// Id of the collection where item was created.
+ CollectionId,
+ /// Id of an item. Unique within the collection.
+ TokenId,
+ /// Owner of newly created item
+ T::CrossAccountId,
+ /// Always 1 for NFT
+ u128,
+ ),
/// Collection item was burned.
- ///
- /// # Arguments
- ///
- /// * collection_id - Identifier of the collection to which the burned NFT belonged.
- /// * item_id - Identifier of burned NFT.
- /// * owner - Which user has destroyed their tokens.
- /// * amount - Amount of tokens that were destroyed (always 1 for NFT).
- ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),
+ ItemDestroyed(
+ /// Id of the collection where item was destroyed.
+ CollectionId,
+ /// Identifier of burned NFT.
+ TokenId,
+ /// Which user has destroyed its tokens.
+ T::CrossAccountId,
+ /// Amount of token pieces destroed. Always 1 for NFT.
+ u128,
+ ),
- /// Item was transferred.
- ///
- /// # Arguments
- ///
- /// * collection_id - ID of the collection to which the item belongs.
- /// * item_id - ID of the item transferred.
- /// * sender - Original owner of the item.
- /// * recipient - New owner of the item.
- /// * amount - Amount of tokens that were transferred (always 1 for NFT).
+ /// Item was transferred
Transfer(
+ /// Id of collection to which item is belong.
CollectionId,
+ /// Id of an item.
TokenId,
+ /// Original owner of item.
T::CrossAccountId,
+ /// New owner of item.
T::CrossAccountId,
+ /// Amount of token pieces transfered. Always 1 for NFT.
u128,
),
- /// Sponsoring allowance was approved.
- ///
- /// # Arguments
- ///
- /// * collection_id - todo:doc flesh out
- /// * item_id
- /// * sender
- /// * spender
- /// * amount
+ /// Amount pieces of token owned by `sender` was approved for `spender`.
Approved(
+ /// Id of collection to which item is belong.
CollectionId,
+ /// Id of an item.
TokenId,
+ /// Original owner of item.
T::CrossAccountId,
+ /// Id for which the approval was granted.
T::CrossAccountId,
+ /// Amount of token pieces transfered. Always 1 for NFT.
u128,
),
- /// Collection property was added or edited.
- ///
- /// # Arguments
- ///
- /// * collection_id - ID of the collection, whose property was just set.
- /// * property_key - Key of the property that was just set.
- CollectionPropertySet(CollectionId, PropertyKey),
+ /// The colletion property has been added or edited.
+ CollectionPropertySet(
+ /// Id of collection to which property has been set.
+ CollectionId,
+ /// The property that was set.
+ PropertyKey,
+ ),
- /// Collection property was deleted.
- ///
- /// # Arguments
- ///
- /// * collection_id - ID of the collection, whose property was just deleted.
- /// * property_key - Key of the property that was just deleted.
- CollectionPropertyDeleted(CollectionId, PropertyKey),
+ /// The property has been deleted.
+ CollectionPropertyDeleted(
+ /// Id of collection to which property has been deleted.
+ CollectionId,
+ /// The property that was deleted.
+ PropertyKey,
+ ),
- /// Item property was added or edited.
- ///
- /// # Arguments
- ///
- /// * collection_id - ID of the collection, whose token's property was just set.
- /// * item_id - ID of the item, whose property was just set.
- /// * property_key - Key of the property that was just set.
- TokenPropertySet(CollectionId, TokenId, PropertyKey),
+ /// The token property has been added or edited.
+ TokenPropertySet(
+ /// Identifier of the collection whose token has the property set.
+ CollectionId,
+ /// The token for which the property was set.
+ TokenId,
+ /// The property that was set.
+ PropertyKey,
+ ),
- /// Item property was deleted.
- ///
- /// # Arguments
- ///
- /// * collection_id - ID of the collection, whose token's property was just deleted.
- /// * item_id - ID of the item, whose property was just deleted.
- /// * property_key - Key of the property that was just deleted.
- TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),
+ /// The token property has been deleted.
+ TokenPropertyDeleted(
+ /// Identifier of the collection whose token has the property deleted.
+ CollectionId,
+ /// The token for which the property was deleted.
+ TokenId,
+ /// The property that was deleted.
+ PropertyKey,
+ ),
- /// Token property permission was added or updated for a collection.
- ///
- /// # Arguments
- ///
- /// * collection_id - ID of the collection, whose permissions were just set/updated.
- /// * property_key - Key of the property of the set/updated permission.
- PropertyPermissionSet(CollectionId, PropertyKey),
+ /// The token property permission of a collection has been set.
+ PropertyPermissionSet(
+ /// ID of collection to which property permission has been set.
+ CollectionId,
+ /// The property permission that was set.
+ PropertyKey,
+ ),
}
#[pallet::error]
@@ -482,16 +571,16 @@
CollectionIsInternal,
}
- /// The number of created collections. Essentially contains the last collection ID.
+ /// Storage of the count of created collections. Essentially contains the last collection ID.
#[pallet::storage]
pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
- /// The number of destroyed collections
+ /// Storage of the count of deleted collections.
#[pallet::storage]
pub type DestroyedCollectionCount<T> =
StorageValue<Value = CollectionId, QueryKind = ValueQuery>;
- /// Collection info
+ /// Storage of collection info.
#[pallet::storage]
pub type CollectionById<T> = StorageMap<
Hasher = Blake2_128Concat,
@@ -500,7 +589,7 @@
QueryKind = OptionQuery,
>;
- /// Collection properties
+ /// Storage of collection properties.
#[pallet::storage]
#[pallet::getter(fn collection_properties)]
pub type CollectionProperties<T> = StorageMap<
@@ -511,7 +600,7 @@
OnEmpty = up_data_structs::CollectionProperties,
>;
- /// Token permissions of a collection
+ /// Storage of token property permissions of a collection.
#[pallet::storage]
#[pallet::getter(fn property_permissions)]
pub type CollectionPropertyPermissions<T> = StorageMap<
@@ -521,7 +610,7 @@
QueryKind = ValueQuery,
>;
- /// Amount of collection admins
+ /// Storage of the amount of collection admins.
#[pallet::storage]
pub type AdminAmount<T> = StorageMap<
Hasher = Blake2_128Concat,
@@ -530,7 +619,7 @@
QueryKind = ValueQuery,
>;
- /// List of collection admins
+ /// List of collection admins.
#[pallet::storage]
pub type IsAdmin<T: Config> = StorageNMap<
Key = (
@@ -541,7 +630,7 @@
QueryKind = ValueQuery,
>;
- /// Allowlisted collection users
+ /// Allowlisted collection users.
#[pallet::storage]
pub type Allowlist<T: Config> = StorageNMap<
Key = (
@@ -552,7 +641,7 @@
QueryKind = ValueQuery,
>;
- /// Not used by code, exists only to provide some types to metadata
+ /// Not used by code, exists only to provide some types to metadata.
#[pallet::storage]
pub type DummyStorageValue<T: Config> = StorageValue<
Value = (
@@ -646,7 +735,9 @@
}
impl<T: Config> Pallet<T> {
- /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens
+ /// Enshure that receiver address is correct.
+ ///
+ /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.
pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {
ensure!(
&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,
@@ -654,19 +745,27 @@
);
Ok(())
}
+
+ /// Get a vector of collection admins.
pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {
<IsAdmin<T>>::iter_prefix((collection,))
.map(|(a, _)| a)
.collect()
}
+
+ /// Get a vector of users allowed to mint tokens.
pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {
<Allowlist<T>>::iter_prefix((collection,))
.map(|(a, _)| a)
.collect()
}
+
+ /// Is `user` allowed to mint token in `collection`.
pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {
<Allowlist<T>>::get((collection, user))
}
+
+ /// Get statistics of collections.
pub fn collection_stats() -> CollectionStats {
let created = <CreatedCollectionCount<T>>::get();
let destroyed = <DestroyedCollectionCount<T>>::get();
@@ -677,6 +776,7 @@
}
}
+ /// Get the effective limits for the collection.
pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {
let collection = <CollectionById<T>>::get(collection);
if collection.is_none() {
@@ -710,6 +810,7 @@
Some(effective_limits)
}
+ /// Returns information about the `collection` adapted for rpc.
pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {
let Collection {
name,
@@ -785,6 +886,11 @@
}
impl<T: Config> Pallet<T> {
+ /// Create new collection.
+ ///
+ /// * `owner` - The owner of the collection.
+ /// * `data` - Description of the created collection.
+ /// * `is_external` - Marks that collection managet by not "Unique network".
pub fn init_collection(
owner: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
@@ -860,7 +966,7 @@
),
);
<T as Config>::Currency::settle(
- &owner.as_sub(),
+ owner.as_sub(),
imbalance,
WithdrawReasons::TRANSFER,
ExistenceRequirement::KeepAlive,
@@ -885,6 +991,10 @@
Ok(id)
}
+ /// Destroy collection.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `sender` - The owner or administrator of the collection.
pub fn destroy_collection(
collection: CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -913,6 +1023,11 @@
Ok(())
}
+ /// Set collection property.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `sender` - The owner or administrator of the collection.
+ /// * `property` - The property to set.
pub fn set_collection_property(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -931,6 +1046,11 @@
Ok(())
}
+ /// Set scouped collection property.
+ ///
+ /// * `collection_id` - ID of the collection for which the property is being set.
+ /// * `scope` - Property scope.
+ /// * `property` - The property to set.
pub fn set_scoped_collection_property(
collection_id: CollectionId,
scope: PropertyScope,
@@ -944,6 +1064,11 @@
Ok(())
}
+ /// Set scouped collection properties.
+ ///
+ /// * `collection_id` - ID of the collection for which the properties is being set.
+ /// * `scope` - Property scope.
+ /// * `properties` - The properties to set.
pub fn set_scoped_collection_properties(
collection_id: CollectionId,
scope: PropertyScope,
@@ -957,6 +1082,11 @@
Ok(())
}
+ /// Set collection properties.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `sender` - The owner or administrator of the collection.
+ /// * `properties` - The properties to set.
#[transactional]
pub fn set_collection_properties(
collection: &CollectionHandle<T>,
@@ -970,6 +1100,11 @@
Ok(())
}
+ /// Delete collection property.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `sender` - The owner or administrator of the collection.
+ /// * `property` - The property to delete.
pub fn delete_collection_property(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -990,6 +1125,11 @@
Ok(())
}
+ /// Delete collection properties.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `sender` - The owner or administrator of the collection.
+ /// * `properties` - The properties to delete.
#[transactional]
pub fn delete_collection_properties(
collection: &CollectionHandle<T>,
@@ -1003,7 +1143,12 @@
Ok(())
}
- // For migrations
+ /// Set collection propetry permission without any checks.
+ ///
+ /// Used for migrations.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `property_permissions` - Property permissions.
pub fn set_property_permission_unchecked(
collection: CollectionId,
property_permission: PropertyKeyPermission,
@@ -1015,6 +1160,11 @@
Ok(())
}
+ /// Set collection property permission.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `sender` - The owner or administrator of the collection.
+ /// * `property_permission` - Property permission.
pub fn set_property_permission(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -1045,6 +1195,11 @@
Ok(())
}
+ /// Set token property permission.
+ ///
+ /// * `collection` - Collection handler.
+ /// * `sender` - The owner or administrator of the collection.
+ /// * `property_permissions` - Property permissions.
#[transactional]
pub fn set_token_property_permissions(
collection: &CollectionHandle<T>,
@@ -1058,6 +1213,7 @@
Ok(())
}
+ /// Get collection property.
pub fn get_collection_property(
collection_id: CollectionId,
key: &PropertyKey,
@@ -1065,6 +1221,7 @@
Self::collection_properties(collection_id).get(key).cloned()
}
+ /// Convert byte vector to property key vector.
pub fn bytes_keys_to_property_keys(
keys: Vec<Vec<u8>>,
) -> Result<Vec<PropertyKey>, DispatchError> {
@@ -1076,6 +1233,7 @@
.collect::<Result<Vec<PropertyKey>, DispatchError>>()
}
+ /// Get properties according to given keys.
pub fn filter_collection_properties(
collection_id: CollectionId,
keys: Option<Vec<PropertyKey>>,
@@ -1103,6 +1261,7 @@
Ok(properties)
}
+ /// Get property permissions according to given keys.
pub fn filter_property_permissions(
collection_id: CollectionId,
keys: Option<Vec<PropertyKey>>,
@@ -1132,6 +1291,7 @@
Ok(key_permissions)
}
+ /// Toggle `user` participation in the `collection`'s allow list.
pub fn toggle_allowlist(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -1151,6 +1311,7 @@
Ok(())
}
+ /// Toggle `user` participation in the `collection`'s admin list.
pub fn toggle_admin(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -1186,6 +1347,7 @@
Ok(())
}
+ /// Merge set fields from `new_limit` to `old_limit`.
pub fn clamp_limits(
mode: CollectionMode,
old_limit: &CollectionLimits,
@@ -1231,20 +1393,22 @@
Ok(new_limit)
}
+ /// Merge set fields from `new_permission` to `old_permission`.
pub fn clamp_permissions(
_mode: CollectionMode,
- old_limit: &CollectionPermissions,
- mut new_limit: CollectionPermissions,
+ old_permission: &CollectionPermissions,
+ mut new_permission: CollectionPermissions,
) -> Result<CollectionPermissions, DispatchError> {
- limit_default_clone!(old_limit, new_limit,
+ limit_default_clone!(old_permission, new_permission,
access => {},
mint_mode => {},
nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },
);
- Ok(new_limit)
+ Ok(new_permission)
}
}
+/// Indicates unsupported methods by returning [Error::UnsupportedOperation].
#[macro_export]
macro_rules! unsupported {
() => {
@@ -1252,32 +1416,72 @@
};
}
-/// Worst cases
+/// Return weights for various worst-case operations.
pub trait CommonWeightInfo<CrossAccountId> {
+ /// Weight of item creation.
fn create_item() -> Weight;
+
+ /// Weight of items creation.
fn create_multiple_items(amount: &[CreateItemData]) -> Weight;
+
+ /// Weight of items creation.
fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;
+
+ /// The weight of the burning item.
fn burn_item() -> Weight;
+
+ /// Property setting weight.
+ ///
+ /// * `amount`- The number of properties to set.
fn set_collection_properties(amount: u32) -> Weight;
+
+ /// Collection property deletion weight.
+ ///
+ /// * `amount`- The number of properties to set.
fn delete_collection_properties(amount: u32) -> Weight;
+
+ /// Token property setting weight.
+ ///
+ /// * `amount`- The number of properties to set.
fn set_token_properties(amount: u32) -> Weight;
+
+ /// Token property deletion weight.
+ ///
+ /// * `amount`- The number of properties to delete.
fn delete_token_properties(amount: u32) -> Weight;
+
+ /// Token property permissions set weight.
+ ///
+ /// * `amount`- The number of property permissions to set.
fn set_token_property_permissions(amount: u32) -> Weight;
+
+ /// Transfer price of the token or its parts.
fn transfer() -> Weight;
+
+ /// The price of setting the permission of the operation from another user.
fn approve() -> Weight;
+
+ /// Transfer price from another user.
fn transfer_from() -> Weight;
+
+ /// The price of burning a token from another user.
fn burn_from() -> Weight;
/// Differs from burn_item in case of Fungible and Refungible, as it should burn
- /// whole users's balance
+ /// whole users's balance.
///
- /// This method shouldn't be used directly, as it doesn't count breadth price, use `burn_recursively` instead
+ /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead
fn burn_recursively_self_raw() -> Weight;
- /// Cost of iterating over `amount` children while burning, without counting child burning itself
+
+ /// Cost of iterating over `amount` children while burning, without counting child burning itself.
///
- /// This method shouldn't be used directly, as it doesn't count depth price, use `burn_recursively` instead
+ /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead
fn burn_recursively_breadth_raw(amount: u32) -> Weight;
+ /// The price of recursive burning a token.
+ ///
+ /// `max_selfs` - The maximum burning weight of the token itself.
+ /// `max_breadth` - The maximum number of nested tokens to burn.
fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {
Self::burn_recursively_self_raw()
.saturating_mul(max_selfs.max(1) as u64)
@@ -1285,11 +1489,23 @@
}
}
+/// Weight info extension trait for refungible pallet.
pub trait RefungibleExtensionsWeightInfo {
+ /// Weight of token repartition.
fn repartition() -> Weight;
}
+/// Common collection operations.
+///
+/// It wraps methods in Fungible, Nonfungible and Refungible pallets
+/// and adds weight info.
pub trait CommonCollectionOperations<T: Config> {
+ /// Create token.
+ ///
+ /// * `sender` - The user who mint the token and pays for the transaction.
+ /// * `to` - The user who will own the token.
+ /// * `data` - Token data.
+ /// * `nesting_budget` - A budget that can be spent on nesting tokens.
fn create_item(
&self,
sender: T::CrossAccountId,
@@ -1297,6 +1513,13 @@
data: CreateItemData,
nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Create multiple tokens.
+ ///
+ /// * `sender` - The user who mint the token and pays for the transaction.
+ /// * `to` - The user who will own the token.
+ /// * `data` - Token data.
+ /// * `nesting_budget` - A budget that can be spent on nesting tokens.
fn create_multiple_items(
&self,
sender: T::CrossAccountId,
@@ -1304,18 +1527,38 @@
data: Vec<CreateItemData>,
nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Create multiple tokens.
+ ///
+ /// * `sender` - The user who mint the token and pays for the transaction.
+ /// * `to` - The user who will own the token.
+ /// * `data` - Token data.
+ /// * `nesting_budget` - A budget that can be spent on nesting tokens.
fn create_multiple_items_ex(
&self,
sender: T::CrossAccountId,
data: CreateItemExData<T::CrossAccountId>,
nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Burn token.
+ ///
+ /// * `sender` - The user who owns the token.
+ /// * `token` - Token id that will burned.
+ /// * `amount` - The number of parts of the token that will be burned.
fn burn_item(
&self,
sender: T::CrossAccountId,
token: TokenId,
amount: u128,
) -> DispatchResultWithPostInfo;
+
+ /// Burn token and all nested tokens recursievly.
+ ///
+ /// * `sender` - The user who owns the token.
+ /// * `token` - Token id that will burned.
+ /// * `self_budget` - The budget that can be spent on burning tokens.
+ /// * `breadth_budget` - The budget that can be spent on burning nested tokens.
fn burn_item_recursively(
&self,
sender: T::CrossAccountId,
@@ -1323,43 +1566,95 @@
self_budget: &dyn Budget,
breadth_budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Set collection properties.
+ ///
+ /// * `sender` - Must be either the owner of the collection or its admin.
+ /// * `properties` - Properties to be set.
fn set_collection_properties(
&self,
sender: T::CrossAccountId,
properties: Vec<Property>,
) -> DispatchResultWithPostInfo;
+
+ /// Delete collection properties.
+ ///
+ /// * `sender` - Must be either the owner of the collection or its admin.
+ /// * `properties` - The properties to be removed.
fn delete_collection_properties(
&self,
sender: &T::CrossAccountId,
property_keys: Vec<PropertyKey>,
) -> DispatchResultWithPostInfo;
+
+ /// Set token properties.
+ ///
+ /// The appropriate [`PropertyPermission`] for the token property
+ /// must be set with [`Self::set_token_property_permissions`].
+ ///
+ /// * `sender` - Must be either the owner of the token or its admin.
+ /// * `token_id` - The token for which the properties are being set.
+ /// * `properties` - Properties to be set.
+ /// * `budget` - Budget for setting properties.
fn set_token_properties(
&self,
sender: T::CrossAccountId,
token_id: TokenId,
- property: Vec<Property>,
- nesting_budget: &dyn Budget,
+ properties: Vec<Property>,
+ budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Remove token properties.
+ ///
+ /// The appropriate [`PropertyPermission`] for the token property
+ /// must be set with [`Self::set_token_property_permissions`].
+ ///
+ /// * `sender` - Must be either the owner of the token or its admin.
+ /// * `token_id` - The token for which the properties are being remove.
+ /// * `property_keys` - Keys to remove corresponding properties.
+ /// * `budget` - Budget for removing properties.
fn delete_token_properties(
&self,
sender: T::CrossAccountId,
token_id: TokenId,
property_keys: Vec<PropertyKey>,
- nesting_budget: &dyn Budget,
+ budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Set token property permissions.
+ ///
+ /// * `sender` - Must be either the owner of the token or its admin.
+ /// * `token_id` - The token for which the properties are being set.
+ /// * `properties` - Properties to be set.
+ /// * `budget` - Budget for setting properties.
fn set_token_property_permissions(
&self,
sender: &T::CrossAccountId,
property_permissions: Vec<PropertyKeyPermission>,
) -> DispatchResultWithPostInfo;
+
+ /// Transfer amount of token pieces.
+ ///
+ /// * `sender` - Donor user.
+ /// * `to` - Recepient user.
+ /// * `token` - The token of which parts are being sent.
+ /// * `amount` - The number of parts of the token that will be transferred.
+ /// * `budget` - The maximum budget that can be spent on the transfer.
fn transfer(
&self,
sender: T::CrossAccountId,
to: T::CrossAccountId,
token: TokenId,
amount: u128,
- nesting_budget: &dyn Budget,
+ budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].
+ ///
+ /// * `sender` - The user who grants access to the token.
+ /// * `spender` - The user to whom the rights are granted.
+ /// * `token` - The token to which access is granted.
+ /// * `amount` - The amount of pieces that another user can dispose of.
fn approve(
&self,
sender: T::CrossAccountId,
@@ -1367,6 +1662,17 @@
token: TokenId,
amount: u128,
) -> DispatchResultWithPostInfo;
+
+ /// Send parts of a token owned by another user.
+ ///
+ /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].
+ ///
+ /// * `sender` - The user who must have access to the token (see [`Self::approve`]).
+ /// * `from` - The user who owns the token.
+ /// * `to` - Recepient user.
+ /// * `token` - The token of which parts are being sent.
+ /// * `amount` - The number of parts of the token that will be transferred.
+ /// * `budget` - The maximum budget that can be spent on the transfer.
fn transfer_from(
&self,
sender: T::CrossAccountId,
@@ -1374,67 +1680,145 @@
to: T::CrossAccountId,
token: TokenId,
amount: u128,
- nesting_budget: &dyn Budget,
+ budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+
+ /// Burn parts of a token owned by another user.
+ ///
+ /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].
+ ///
+ /// * `sender` - The user who must have access to the token (see [`Self::approve`]).
+ /// * `from` - The user who owns the token.
+ /// * `token` - The token of which parts are being sent.
+ /// * `amount` - The number of parts of the token that will be transferred.
+ /// * `budget` - The maximum budget that can be spent on the burn.
fn burn_from(
&self,
sender: T::CrossAccountId,
from: T::CrossAccountId,
token: TokenId,
amount: u128,
- nesting_budget: &dyn Budget,
+ budget: &dyn Budget,
) -> DispatchResultWithPostInfo;
+ /// Check permission to nest token.
+ ///
+ /// * `sender` - The user who initiated the check.
+ /// * `from` - The token that is checked for embedding.
+ /// * `under` - Token under which to check.
+ /// * `budget` - The maximum budget that can be spent on the check.
fn check_nesting(
&self,
sender: T::CrossAccountId,
from: (CollectionId, TokenId),
under: TokenId,
- nesting_budget: &dyn Budget,
+ budget: &dyn Budget,
) -> DispatchResult;
+ /// Nest one token into another.
+ ///
+ /// * `under` - Token holder.
+ /// * `to_nest` - Nested token.
fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));
+ /// Unnest token.
+ ///
+ /// * `under` - Token holder.
+ /// * `to_nest` - Token to unnest.
fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));
+ /// Get all user tokens.
+ ///
+ /// * `account` - Account for which you need to get tokens.
fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;
+
+ /// Get all the tokens in the collection.
fn collection_tokens(&self) -> Vec<TokenId>;
+
+ /// Check if the token exists.
+ ///
+ /// * `token` - Id token to check.
fn token_exists(&self, token: TokenId) -> bool;
+
+ /// Get the id of the last minted token.
fn last_token_id(&self) -> TokenId;
+ /// Get the owner of the token.
+ ///
+ /// * `token` - The token for which you need to find out the owner.
fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;
+
+ /// Returns 10 tokens owners in no particular order.
+ ///
+ /// * `token` - The token for which you need to find out the owners.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;
+
+ /// Get the value of the token property by key.
+ ///
+ /// * `token` - Token with the property to get.
+ /// * `key` - Property name.
fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;
- fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;
+
+ /// Get a set of token properties by key vector.
+ ///
+ /// * `token` - Token with the property to get.
+ /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),
+ /// then all properties are returned.
+ fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;
+
/// Amount of unique collection tokens
fn total_supply(&self) -> u32;
- /// Amount of different tokens account has (Applicable to nonfungible/refungible)
+
+ /// Amount of different tokens account has.
+ ///
+ /// * `account` - The account for which need to get the balance.
fn account_balance(&self, account: T::CrossAccountId) -> u32;
- /// Amount of specific token account have (Applicable to fungible/refungible)
+
+ /// Amount of specific token account have.
fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;
+
/// Amount of token pieces
fn total_pieces(&self, token: TokenId) -> Option<u128>;
+
+ /// Get the number of parts of the token that a trusted user can manage.
+ ///
+ /// * `sender` - Trusted user.
+ /// * `spender` - Owner of the token.
+ /// * `token` - The token for which to get the value.
fn allowance(
&self,
sender: T::CrossAccountId,
spender: T::CrossAccountId,
token: TokenId,
) -> u128;
+
+ /// Get extension for RFT collection.
fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;
}
+/// Extension for RFT collection.
pub trait RefungibleExtensions<T>
where
T: Config,
{
+ /// Change the number of parts of the token.
+ ///
+ /// When the value changes down, this function is equivalent to burning parts of the token.
+ ///
+ /// * `sender` - The user calling the repartition operation. Must be the owner of the token.
+ /// * `token` - The token for which you want to change the number of parts.
+ /// * `amount` - The new value of the parts of the token.
fn repartition(
&self,
- owner: &T::CrossAccountId,
+ sender: &T::CrossAccountId,
token: TokenId,
amount: u128,
) -> DispatchResultWithPostInfo;
}
-// Flexible enough for implementing CommonCollectionOperations
+/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].
+///
+/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.
pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {
let post_info = PostDispatchInfo {
actual_weight: Some(weight),
pallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/fungible/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
+
+
\ No newline at end of file
pallets/fungible/Cargo.tomldiffbeforeafterboth--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-fungible"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -105,6 +105,8 @@
}
}
+/// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
+/// methods and adds weight info.
impl<T: Config> CommonCollectionOperations<T> for FungibleHandle<T> {
fn create_item(
&self,
@@ -360,6 +362,11 @@
None
}
+ /// Returns 10 tokens owners in no particular order.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+ <Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
+ }
+
fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
None
}
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! ERC-20 standart support implementation.
+
use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
use core::convert::TryInto;
use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -14,6 +14,68 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! # Fungible Pallet
+//!
+//! The Fungible pallet provides functionality for dealing with fungible assets.
+//!
+//! - [`CreateItemData`]
+//! - [`Config`]
+//! - [`FungibleHandle`]
+//! - [`Pallet`]
+//! - [`TotalSupply`]
+//! - [`Balance`]
+//! - [`Allowance`]
+//! - [`Error`]
+//!
+//! ## Fungible tokens
+//!
+//! Fungible tokens or assets are divisible and non-unique. For instance,
+//! fiat currencies like the dollar are fungible: A $1 bill
+//! in New York City has the same value as a $1 bill in Miami.
+//! A fungible token can also be a cryptocurrency like Bitcoin: 1 BTC is worth 1 BTC,
+//! no matter where it is issued. Thus, the fungibility refers to a specific currency’s
+//! ability to maintain one standard value. As well, it needs to have uniform acceptance.
+//! This means that a currency’s history should not be able to affect its value,
+//! and this is due to the fact that each piece that is a part of the currency is equal
+//! in value when compared to every other piece of that exact same currency.
+//! In the world of cryptocurrencies, this is essentially a coin or a token
+//! that can be replaced by another identical coin or token, and they are
+//! both mutually interchangeable. A popular implementation of fungible tokens is
+//! the ERC-20 token standard.
+//!
+//! ### ERC-20
+//!
+//! The [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) (Ethereum Request for Comments 20), proposed by Fabian Vogelsteller in November 2015,
+//! is a Token Standard that implements an API for tokens within Smart Contracts.
+//!
+//! Example functionalities ERC-20 provides:
+//!
+//! * transfer tokens from one account to another
+//! * get the current token balance of an account
+//! * get the total supply of the token available on the network
+//! * approve whether an amount of token from an account can be spent by a third-party account
+//!
+//! ## Overview
+//!
+//! The module provides functionality for asset management of fungible asset, supports ERC-20 standart, includes:
+//!
+//! * Asset Issuance
+//! * Asset Transferal
+//! * Asset Destruction
+//! * Delegated Asset Transfers
+//!
+//! **NOTE:** The created fungible asset always has `token_id` = 0.
+//! So `tokenA` and `tokenB` will have different `collection_id`.
+//!
+//! ### Implementations
+//!
+//! The Fungible pallet provides implementations for the following traits.
+//!
+//! - [`WithRecorder`](pallet_evm_coder_substrate::WithRecorder): Trait for EVM support
+//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing with collections
+//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight
+//! - [`CommonEvmHandler`](pallet_common::erc::CommonEvmHandler): Function for handling EVM runtime calls
+
#![cfg_attr(not(feature = "std"), no_std)]
use core::ops::Deref;
@@ -33,7 +95,7 @@
use pallet_evm_coder_substrate::WithRecorder;
use sp_core::H160;
use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
-use sp_std::{collections::btree_map::BTreeMap};
+use sp_std::{collections::btree_map::BTreeMap, vec::Vec};
pub use pallet::*;
@@ -44,7 +106,6 @@
pub mod erc;
pub mod weights;
-/// todo:doc?
pub type CreateItemData<T> = (<T as pallet_evm::account::Config>::CrossAccountId, u128);
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
@@ -58,7 +119,7 @@
pub enum Error<T> {
/// Not Fungible item data used to mint in Fungible collection.
NotFungibleDataUsedToMintFungibleCollectionToken,
- /// Not default id passed as TokenId argument.
+ /// Fungible tokens hold no ID, and the default value of TokenId for Fungible collection is 0.
FungibleItemsHaveNoId,
/// Tried to set data for fungible item.
FungibleItemsDontHaveData,
@@ -95,7 +156,7 @@
QueryKind = ValueQuery,
>;
- /// todo:doc
+ /// Storage for assets delegated to a limited extent to other users.
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (
@@ -108,14 +169,22 @@
>;
}
+/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.
+/// Required for interaction with Fungible collections, type safety and implementation [`solidity_interface`][`evm_coder::solidity_interface`].
pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);
+
+/// Implementation of methods required for dispatching during runtime.
impl<T: Config> FungibleHandle<T> {
+ /// Casts [`CollectionHandle`][`pallet_common::CollectionHandle`] into [`FungibleHandle`].
pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {
Self(inner)
}
+
+ /// Casts [`FungibleHandle`] into [`CollectionHandle`][`pallet_common::CollectionHandle`].
pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {
self.0
}
+ /// Returns a mutable reference to the internal [`CollectionHandle`][`pallet_common::CollectionHandle`].
pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {
&mut self.0
}
@@ -136,13 +205,17 @@
}
}
+/// Pallet implementation for fungible assets
impl<T: Config> Pallet<T> {
+ /// Initializes the collection. Returns [CollectionId] on success, [DispatchError] otherwise.
pub fn init_collection(
owner: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
) -> Result<CollectionId, DispatchError> {
<PalletCommon<T>>::init_collection(owner, data, false)
}
+
+ /// Destroys a collection.
pub fn destroy_collection(
collection: FungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -163,10 +236,14 @@
Ok(())
}
+ ///Checks if collection has tokens. Return `true` if it has.
fn collection_has_tokens(collection_id: CollectionId) -> bool {
<TotalSupply<T>>::get(collection_id) != 0
}
+ /// Burns the specified amount of the token. If the token balance
+ /// or total supply is less than the given value,
+ /// it will return [DispatchError].
pub fn burn(
collection: &FungibleHandle<T>,
owner: &T::CrossAccountId,
@@ -211,6 +288,13 @@
Ok(())
}
+ /// Transfers the specified amount of tokens. Will check that
+ /// the transfer is allowed for the token.
+ ///
+ /// - `from`: Owner of tokens to transfer.
+ /// - `to`: Recepient of transfered tokens.
+ /// - `amount`: Amount of tokens to transfer.
+ /// - `collection`: Collection that contains the token
pub fn transfer(
collection: &FungibleHandle<T>,
from: &T::CrossAccountId,
@@ -281,6 +365,8 @@
Ok(())
}
+ /// Minting tokens for multiple IDs.
+ /// See [`create_item`][`Pallet::create_item`] for more details.
pub fn create_multiple_items(
collection: &FungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -382,6 +468,12 @@
));
}
+ /// Set allowance for the spender to `transfer` or `burn` owner's tokens.
+ ///
+ /// - `collection`: Collection that contains the token
+ /// - `owner`: Owner of tokens that sets the allowance.
+ /// - `spender`: Recipient of the allowance rights.
+ /// - `amount`: Amount of tokens the spender is allowed to `transfer` or `burn`.
pub fn set_allowance(
collection: &FungibleHandle<T>,
owner: &T::CrossAccountId,
@@ -406,6 +498,13 @@
Ok(())
}
+ /// Checks if a non-owner has (enough) allowance from the owner to perform operations on the tokens.
+ /// Returns the expected remaining allowance - it should be set manually if the transaction proceeds.
+ ///
+ /// - `collection`: Collection that contains the token.
+ /// - `spender`: CrossAccountId who has the allowance rights.
+ /// - `from`: The owner of the tokens who sets the allowance.
+ /// - `amount`: Amount of tokens by which the allowance sholud be reduced.
fn check_allowed(
collection: &FungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -445,6 +544,11 @@
Ok(allowance)
}
+ /// Transfer fungible tokens from one account to another.
+ /// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.
+ /// The owner should set allowance for the spender to transfer pieces.
+ /// See [`set_allowance`][`Pallet::set_allowance`] for more details.
+
pub fn transfer_from(
collection: &FungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -464,6 +568,11 @@
Ok(())
}
+ /// Burn fungible tokens from the account.
+ ///
+ /// Same as the [`burn`][`Pallet::burn`] but spender doesn't need to be an owner of the tokens. The `from` should
+ /// set allowance for the spender to burn tokens.
+ /// See [`set_allowance`][`Pallet::set_allowance`] for more details.
pub fn burn_from(
collection: &FungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -482,7 +591,13 @@
Ok(())
}
- /// Delegated to `create_multiple_items`
+ /// Creates fungible token.
+ ///
+ /// The sender should be the owner/admin of the collection or collection should be configured
+ /// to allow public minting.
+ ///
+ /// - `data`: Contains user who will become the owners of the tokens and amount
+ /// of tokens he will receive.
pub fn create_item(
collection: &FungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -496,4 +611,26 @@
nesting_budget,
)
}
+
+ /// Returns 10 tokens owners in no particular order
+ ///
+ /// There is no direct way to get token holders in ascending order,
+ /// since `iter_prefix` returns values in no particular order.
+ /// Therefore, getting the 10 largest holders with a large value of holders
+ /// can lead to impact memory allocation + sorting with `n * log (n)`.
+ pub fn token_owners(
+ collection: CollectionId,
+ _token: TokenId,
+ ) -> Option<Vec<T::CrossAccountId>> {
+ let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))
+ .map(|(owner, _amount)| owner)
+ .take(10)
+ .collect();
+
+ if res.is_empty() {
+ None
+ } else {
+ Some(res)
+ }
+ }
}
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -43,6 +43,233 @@
}
}
+// Selector: 7d9262e6
+contract Collection is Dummy, ERC165 {
+ // Set collection property.
+ //
+ // @param key Property key.
+ // @param value Propery value.
+ //
+ // Selector: setCollectionProperty(string,bytes) 2f073f66
+ function setCollectionProperty(string memory key, bytes memory value)
+ public
+ {
+ require(false, stub_error);
+ key;
+ value;
+ dummy = 0;
+ }
+
+ // Delete collection property.
+ //
+ // @param key Property key.
+ //
+ // Selector: deleteCollectionProperty(string) 7b7debce
+ function deleteCollectionProperty(string memory key) public {
+ require(false, stub_error);
+ key;
+ dummy = 0;
+ }
+
+ // Get collection property.
+ //
+ // @dev Throws error if key not found.
+ //
+ // @param key Property key.
+ // @return bytes The property corresponding to the key.
+ //
+ // Selector: collectionProperty(string) cf24fd6d
+ function collectionProperty(string memory key)
+ public
+ view
+ returns (bytes memory)
+ {
+ require(false, stub_error);
+ key;
+ dummy;
+ return hex"";
+ }
+
+ // Set the sponsor of the collection.
+ //
+ // @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ //
+ // @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.
+ //
+ // Selector: setCollectionSponsor(address) 7623402e
+ function setCollectionSponsor(address sponsor) public {
+ require(false, stub_error);
+ sponsor;
+ dummy = 0;
+ }
+
+ // Collection sponsorship confirmation.
+ //
+ // @dev After setting the sponsor for the collection, it must be confirmed with this function.
+ //
+ // Selector: confirmCollectionSponsorship() 3c50e97a
+ function confirmCollectionSponsorship() public {
+ require(false, stub_error);
+ dummy = 0;
+ }
+
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "accountTokenOwnershipLimit",
+ // "sponsoredDataSize",
+ // "sponsoredDataRateLimit",
+ // "tokenLimit",
+ // "sponsorTransferTimeout",
+ // "sponsorApproveTimeout"
+ // @param value Value of the limit.
+ //
+ // Selector: setCollectionLimit(string,uint32) 6a3841db
+ function setCollectionLimit(string memory limit, uint32 value) public {
+ require(false, stub_error);
+ limit;
+ value;
+ dummy = 0;
+ }
+
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "ownerCanTransfer",
+ // "ownerCanDestroy",
+ // "transfersEnabled"
+ // @param value Value of the limit.
+ //
+ // Selector: setCollectionLimit(string,bool) 993b7fba
+ function setCollectionLimit(string memory limit, bool value) public {
+ require(false, stub_error);
+ limit;
+ value;
+ dummy = 0;
+ }
+
+ // Get contract address.
+ //
+ // Selector: contractAddress() f6b4dfb4
+ function contractAddress() public view returns (address) {
+ require(false, stub_error);
+ dummy;
+ return 0x0000000000000000000000000000000000000000;
+ }
+
+ // Add collection admin by substrate address.
+ // @param new_admin Substrate administrator address.
+ //
+ // Selector: addCollectionAdminSubstrate(uint256) 5730062b
+ function addCollectionAdminSubstrate(uint256 newAdmin) public {
+ require(false, stub_error);
+ newAdmin;
+ dummy = 0;
+ }
+
+ // Remove collection admin by substrate address.
+ // @param admin Substrate administrator address.
+ //
+ // Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
+ function removeCollectionAdminSubstrate(uint256 admin) public {
+ require(false, stub_error);
+ admin;
+ dummy = 0;
+ }
+
+ // Add collection admin.
+ // @param new_admin Address of the added administrator.
+ //
+ // Selector: addCollectionAdmin(address) 92e462c7
+ function addCollectionAdmin(address newAdmin) public {
+ require(false, stub_error);
+ newAdmin;
+ dummy = 0;
+ }
+
+ // Remove collection admin.
+ //
+ // @param new_admin Address of the removed administrator.
+ //
+ // Selector: removeCollectionAdmin(address) fafd7b42
+ function removeCollectionAdmin(address admin) public {
+ require(false, stub_error);
+ admin;
+ dummy = 0;
+ }
+
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'
+ //
+ // Selector: setCollectionNesting(bool) 112d4586
+ function setCollectionNesting(bool enable) public {
+ require(false, stub_error);
+ enable;
+ dummy = 0;
+ }
+
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
+ // @param collections Addresses of collections that will be available for nesting.
+ //
+ // Selector: setCollectionNesting(bool,address[]) 64872396
+ function setCollectionNesting(bool enable, address[] memory collections)
+ public
+ {
+ require(false, stub_error);
+ enable;
+ collections;
+ dummy = 0;
+ }
+
+ // Set the collection access method.
+ // @param mode Access mode
+ // 0 for Normal
+ // 1 for AllowList
+ //
+ // Selector: setCollectionAccess(uint8) 41835d4c
+ function setCollectionAccess(uint8 mode) public {
+ require(false, stub_error);
+ mode;
+ dummy = 0;
+ }
+
+ // Add the user to the allowed list.
+ //
+ // @param user Address of a trusted user.
+ //
+ // Selector: addToCollectionAllowList(address) 67844fe6
+ function addToCollectionAllowList(address user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
+ // Remove the user from the allowed list.
+ //
+ // @param user Address of a removed user.
+ //
+ // Selector: removeFromCollectionAllowList(address) 85c51acb
+ function removeFromCollectionAllowList(address user) public {
+ require(false, stub_error);
+ user;
+ dummy = 0;
+ }
+
+ // Switch permission for minting.
+ //
+ // @param mode Enable if "true".
+ //
+ // Selector: setCollectionMintMode(bool) 00018e84
+ function setCollectionMintMode(bool mode) public {
+ require(false, stub_error);
+ mode;
+ dummy = 0;
+ }
+}
+
// Selector: 942e8b22
contract ERC20 is Dummy, ERC165, ERC20Events {
// Selector: name() 06fdde03
@@ -124,76 +351,6 @@
spender;
dummy;
return 0;
- }
-}
-
-// Selector: c894dc35
-contract Collection is Dummy, ERC165 {
- // Selector: setCollectionProperty(string,bytes) 2f073f66
- function setCollectionProperty(string memory key, bytes memory value)
- public
- {
- require(false, stub_error);
- key;
- value;
- dummy = 0;
- }
-
- // Selector: deleteCollectionProperty(string) 7b7debce
- function deleteCollectionProperty(string memory key) public {
- require(false, stub_error);
- key;
- dummy = 0;
- }
-
- // Throws error if key not found
- //
- // Selector: collectionProperty(string) cf24fd6d
- function collectionProperty(string memory key)
- public
- view
- returns (bytes memory)
- {
- require(false, stub_error);
- key;
- dummy;
- return hex"";
- }
-
- // Selector: ethSetSponsor(address) 8f9af356
- function ethSetSponsor(address sponsor) public {
- require(false, stub_error);
- sponsor;
- dummy = 0;
- }
-
- // Selector: ethConfirmSponsorship() a8580d1a
- function ethConfirmSponsorship() public {
- require(false, stub_error);
- dummy = 0;
- }
-
- // Selector: setLimit(string,uint32) 68db30ca
- function setLimit(string memory limit, uint32 value) public {
- require(false, stub_error);
- limit;
- value;
- dummy = 0;
- }
-
- // Selector: setLimit(string,bool) ea67e4c2
- function setLimit(string memory limit, bool value) public {
- require(false, stub_error);
- limit;
- value;
- dummy = 0;
- }
-
- // Selector: contractAddress() f6b4dfb4
- function contractAddress() public view returns (address) {
- require(false, stub_error);
- dummy;
- return 0x0000000000000000000000000000000000000000;
}
}
pallets/inflation/src/lib.rsdiffbeforeafterboth--- a/pallets/inflation/src/lib.rs
+++ b/pallets/inflation/src/lib.rs
@@ -14,6 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! # Inflation
+//!
+//! The inflation pallet is designed to increase the number of tokens at certain intervals.
+//! With each iteration, increases the `total_issuance` value for the native token.
+//! Executing an `on_initialize` hook at the beginning of each block, causing inflation to begin.
+//!
+//! ## Interface
+//!
+//! ### Dispatchable Functions
+//!
+//! * `start_inflation` - This method sets the inflation start date. Can be only called once.
+//! Inflation start block can be backdated and will catch up. The method will create Treasury
+//! account if it does not exist and perform the first inflation deposit.
+
// #![recursion_limit = "1024"]
#![cfg_attr(not(feature = "std"), no_std)]
pallets/nonfungible/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+- Implementation of RPC method `token_owners`.
+ For reasons of compatibility with this pallet, returns only one owner if token exists.
+ This was an internal request to improve the web interface and support fractionalization event.
\ No newline at end of file
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-nonfungible"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -183,7 +183,7 @@
value: property_value(),
}).collect::<Vec<_>>();
let item = create_max_item(&collection, &owner, owner.clone())?;
- }: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props, false, &Unlimited)?}
+ }: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?}
delete_token_properties {
let b in 0..MAX_PROPERTIES_PER_ITEM;
@@ -205,7 +205,7 @@
value: property_value(),
}).collect::<Vec<_>>();
let item = create_max_item(&collection, &owner, owner.clone())?;
- <Pallet<T>>::set_token_properties(&collection, &owner, item, props, false, &Unlimited)?;
+ <Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?;
let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
- }: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete, &Unlimited)?}
+ }: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -26,7 +26,7 @@
weights::WeightInfo as _,
};
use sp_runtime::DispatchError;
-use sp_std::vec::Vec;
+use sp_std::{vec::Vec, vec};
use crate::{
AccountBalance, Allowance, Config, CreateItemData, Error, NonfungibleHandle, Owned, Pallet,
@@ -133,6 +133,8 @@
}
}
+/// Implementation of `CommonCollectionOperations` for `NonfungibleHandle`. It wraps Nonfungible Pallete
+/// methods and adds weight info.
impl<T: Config> CommonCollectionOperations<T> for NonfungibleHandle<T> {
fn create_item(
&self,
@@ -420,6 +422,11 @@
<TokenData<T>>::get((self.id, token)).map(|t| t.owner)
}
+ /// Returns token owners.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+ self.token_owner(token).map_or_else(|| vec![], |t| vec![t])
+ }
+
fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {
<Pallet<T>>::token_properties((self.id, token_id))
.get(key)
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -14,6 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! # Nonfungible Pallet EVM API
+//!
+//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.
+//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.
+
extern crate alloc;
use core::{
char::{REPLACEMENT_CHARACTER, decode_utf16},
@@ -40,8 +45,15 @@
SelfWeightOf, weights::WeightInfo, TokenProperties,
};
+/// @title A contract that allows to set and delete token properties and change token property permissions.
#[solidity_interface(name = "TokenProperties")]
impl<T: Config> NonfungibleHandle<T> {
+ /// @notice Set permissions for token property.
+ /// @dev Throws error if `msg.sender` is not admin or owner of the collection.
+ /// @param key Property key.
+ /// @param is_mutable Permission to mutate property.
+ /// @param collection_admin Permission to mutate property by collection admin if property is mutable.
+ /// @param token_owner Permission to mutate property by token owner if property is mutable.
fn set_token_property_permission(
&mut self,
caller: caller,
@@ -68,6 +80,11 @@
.map_err(dispatch_to_evm::<T>)
}
+ /// @notice Set token property value.
+ /// @dev Throws error if `msg.sender` has no permission to edit the property.
+ /// @param tokenId ID of the token.
+ /// @param key Property key.
+ /// @param value Property value.
fn set_property(
&mut self,
caller: caller,
@@ -96,6 +113,10 @@
.map_err(dispatch_to_evm::<T>)
}
+ /// @notice Delete token property value.
+ /// @dev Throws error if `msg.sender` has no permission to edit the property.
+ /// @param tokenId ID of the token.
+ /// @param key Property key.
fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {
let caller = T::CrossAccountId::from_eth(caller);
let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
@@ -111,7 +132,11 @@
.map_err(dispatch_to_evm::<T>)
}
- /// Throws error if key not found
+ /// @notice Get token property value.
+ /// @dev Throws error if key not found
+ /// @param tokenId ID of the token.
+ /// @param key Property key.
+ /// @return Property value bytes
fn property(&self, token_id: uint256, key: string) -> Result<bytes> {
let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
let key = <Vec<u8>>::from(key)
@@ -127,6 +152,11 @@
#[derive(ToLog)]
pub enum ERC721Events {
+ /// @dev This emits when ownership of any NFT changes by any mechanism.
+ /// This event emits when NFTs are created (`from` == 0) and destroyed
+ /// (`to` == 0). Exception: during contract creation, any number of NFTs
+ /// may be created and assigned without emitting Transfer. At the time of
+ /// any transfer, the approved address for that NFT (if any) is reset to none.
Transfer {
#[indexed]
from: address,
@@ -135,6 +165,10 @@
#[indexed]
token_id: uint256,
},
+ /// @dev This emits when the approved address for an NFT is changed or
+ /// reaffirmed. The zero address indicates there is no approved address.
+ /// When a Transfer event emits, this also indicates that the approved
+ /// address for that NFT (if any) is reset to none.
Approval {
#[indexed]
owner: address,
@@ -143,6 +177,8 @@
#[indexed]
token_id: uint256,
},
+ /// @dev This emits when an operator is enabled or disabled for an owner.
+ /// The operator can manage all NFTs of the owner.
#[allow(dead_code)]
ApprovalForAll {
#[indexed]
@@ -159,19 +195,27 @@
MintingFinished {},
}
+/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
+/// @dev See https://eips.ethereum.org/EIPS/eip-721
#[solidity_interface(name = "ERC721Metadata")]
impl<T: Config> NonfungibleHandle<T> {
+ /// @notice A descriptive name for a collection of NFTs in this contract
fn name(&self) -> Result<string> {
Ok(decode_utf16(self.name.iter().copied())
.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
.collect::<string>())
}
+ /// @notice An abbreviated name for NFTs in this contract
fn symbol(&self) -> Result<string> {
Ok(string::from_utf8_lossy(&self.token_prefix).into())
}
- /// Returns token's const_metadata
+ /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ /// @dev Throws if `tokenId` is not a valid NFT. URIs are defined in RFC
+ /// 3986. The URI may point to a JSON file that conforms to the "ERC721
+ /// Metadata JSON Schema".
+ /// @return token's const_metadata
#[solidity(rename_selector = "tokenURI")]
fn token_uri(&self, token_id: uint256) -> Result<string> {
let key = token_uri_key();
@@ -192,32 +236,53 @@
}
}
+/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
+/// @dev See https://eips.ethereum.org/EIPS/eip-721
#[solidity_interface(name = "ERC721Enumerable")]
impl<T: Config> NonfungibleHandle<T> {
+ /// @notice Enumerate valid NFTs
+ /// @param index A counter less than `totalSupply()`
+ /// @return The token identifier for the `index`th NFT,
+ /// (sort order not specified)
fn token_by_index(&self, index: uint256) -> Result<uint256> {
Ok(index)
}
- /// Not implemented
+ /// @dev Not implemented
fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {
// TODO: Not implemetable
Err("not implemented".into())
}
+ /// @notice Count NFTs tracked by this contract
+ /// @return A count of valid NFTs tracked by this contract, where each one of
+ /// them has an assigned and queryable owner not equal to the zero address
fn total_supply(&self) -> Result<uint256> {
self.consume_store_reads(1)?;
Ok(<Pallet<T>>::total_supply(self).into())
}
}
+/// @title ERC-721 Non-Fungible Token Standard
+/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
#[solidity_interface(name = "ERC721", events(ERC721Events))]
impl<T: Config> NonfungibleHandle<T> {
+ /// @notice Count all NFTs assigned to an owner
+ /// @dev NFTs assigned to the zero address are considered invalid, and this
+ /// function throws for queries about the zero address.
+ /// @param owner An address for whom to query the balance
+ /// @return The number of NFTs owned by `owner`, possibly zero
fn balance_of(&self, owner: address) -> Result<uint256> {
self.consume_store_reads(1)?;
let owner = T::CrossAccountId::from_eth(owner);
let balance = <AccountBalance<T>>::get((self.id, owner));
Ok(balance.into())
}
+ /// @notice Find the owner of an NFT
+ /// @dev NFTs assigned to zero address are considered invalid, and queries
+ /// about them do throw.
+ /// @param tokenId The identifier for an NFT
+ /// @return The address of the owner of the NFT
fn owner_of(&self, token_id: uint256) -> Result<address> {
self.consume_store_reads(1)?;
let token: TokenId = token_id.try_into()?;
@@ -226,7 +291,7 @@
.owner
.as_eth())
}
- /// Not implemented
+ /// @dev Not implemented
fn safe_transfer_from_with_data(
&mut self,
_from: address,
@@ -238,7 +303,7 @@
// TODO: Not implemetable
Err("not implemented".into())
}
- /// Not implemented
+ /// @dev Not implemented
fn safe_transfer_from(
&mut self,
_from: address,
@@ -250,6 +315,16 @@
Err("not implemented".into())
}
+ /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
+ /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
+ /// THEY MAY BE PERMANENTLY LOST
+ /// @dev Throws unless `msg.sender` is the current owner or an authorized
+ /// operator for this NFT. Throws if `from` is not the current owner. Throws
+ /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ /// @param from The current owner of the NFT
+ /// @param to The new owner
+ /// @param tokenId The NFT to transfer
+ /// @param _value Not used for an NFT
#[weight(<SelfWeightOf<T>>::transfer_from())]
fn transfer_from(
&mut self,
@@ -272,6 +347,12 @@
Ok(())
}
+ /// @notice Set or reaffirm the approved address for an NFT
+ /// @dev The zero address indicates there is no approved address.
+ /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ /// operator of the current owner.
+ /// @param approved The new approved NFT controller
+ /// @param tokenId The NFT to approve
#[weight(<SelfWeightOf<T>>::approve())]
fn approve(
&mut self,
@@ -289,7 +370,7 @@
Ok(())
}
- /// Not implemented
+ /// @dev Not implemented
fn set_approval_for_all(
&mut self,
_caller: caller,
@@ -300,21 +381,26 @@
Err("not implemented".into())
}
- /// Not implemented
+ /// @dev Not implemented
fn get_approved(&self, _token_id: uint256) -> Result<address> {
// TODO: Not implemetable
Err("not implemented".into())
}
- /// Not implemented
+ /// @dev Not implemented
fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
// TODO: Not implemetable
Err("not implemented".into())
}
}
+/// @title ERC721 Token that can be irreversibly burned (destroyed).
#[solidity_interface(name = "ERC721Burnable")]
impl<T: Config> NonfungibleHandle<T> {
+ /// @notice Burns a specific ERC721 token.
+ /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ /// operator of the current owner.
+ /// @param tokenId The NFT to approve
#[weight(<SelfWeightOf<T>>::burn_item())]
fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -325,14 +411,18 @@
}
}
+/// @title ERC721 minting logic.
#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]
impl<T: Config> NonfungibleHandle<T> {
fn minting_finished(&self) -> Result<bool> {
Ok(false)
}
- /// `token_id` should be obtained with `next_token_id` method,
- /// unlike standard, you can't specify it manually
+ /// @notice Function to mint token.
+ /// @dev `tokenId` should be obtained with `nextTokenId` method,
+ /// unlike standard, you can't specify it manually
+ /// @param to The new owner
+ /// @param tokenId ID of the minted NFT
#[weight(<SelfWeightOf<T>>::create_item())]
fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -364,8 +454,12 @@
Ok(true)
}
- /// `token_id` should be obtained with `next_token_id` method,
- /// unlike standard, you can't specify it manually
+ /// @notice Function to mint token with the given tokenUri.
+ /// @dev `tokenId` should be obtained with `nextTokenId` method,
+ /// unlike standard, you can't specify it manually
+ /// @param to The new owner
+ /// @param tokenId ID of the minted NFT
+ /// @param tokenUri Token URI that would be stored in the NFT properties
#[solidity(rename_selector = "mintWithTokenURI")]
#[weight(<SelfWeightOf<T>>::create_item())]
fn mint_with_token_uri(
@@ -420,7 +514,7 @@
Ok(true)
}
- /// Not implemented
+ /// @dev Not implemented
fn finish_minting(&mut self, _caller: caller) -> Result<bool> {
Err("not implementable".into())
}
@@ -449,8 +543,15 @@
false
}
+/// @title Unique extensions for ERC721.
#[solidity_interface(name = "ERC721UniqueExtensions")]
impl<T: Config> NonfungibleHandle<T> {
+ /// @notice Transfer ownership of an NFT
+ /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ /// is the zero address. Throws if `tokenId` is not a valid NFT.
+ /// @param to The new owner
+ /// @param tokenId The NFT to transfer
+ /// @param _value Not used for an NFT
#[weight(<SelfWeightOf<T>>::transfer())]
fn transfer(
&mut self,
@@ -470,6 +571,13 @@
Ok(())
}
+ /// @notice Burns a specific ERC721 token.
+ /// @dev Throws unless `msg.sender` is the current owner or an authorized
+ /// operator for this NFT. Throws if `from` is not the current owner. Throws
+ /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ /// @param from The current owner of the NFT
+ /// @param tokenId The NFT to transfer
+ /// @param _value Not used for an NFT
#[weight(<SelfWeightOf<T>>::burn_from())]
fn burn_from(
&mut self,
@@ -490,6 +598,7 @@
Ok(())
}
+ /// @notice Returns next free NFT ID.
fn next_token_id(&self) -> Result<uint256> {
self.consume_store_reads(1)?;
Ok(<TokensMinted<T>>::get(self.id)
@@ -498,6 +607,11 @@
.into())
}
+ /// @notice Function to mint multiple tokens.
+ /// @dev `tokenIds` should be an array of consecutive numbers and first number
+ /// should be obtained with `nextTokenId` method
+ /// @param to The new owner
+ /// @param tokenIds IDs of the minted NFTs
#[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]
fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -529,6 +643,11 @@
Ok(true)
}
+ /// @notice Function to mint multiple tokens with the given tokenUris.
+ /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
+ /// numbers and first number should be obtained with `nextTokenId` method
+ /// @param to The new owner
+ /// @param tokens array of pairs of token ID and token URI for minted tokens
#[solidity(rename_selector = "mintBulkWithTokenURI")]
#[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]
fn mint_bulk_with_token_uri(
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -14,6 +14,80 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! # Nonfungible Pallet
+//!
+//! The Nonfungible pallet provides functionality for handling nonfungible collections and tokens.
+//!
+//! - [`Config`]
+//! - [`NonfungibleHandle`]
+//! - [`Pallet`]
+//! - [`CommonWeights`](common::CommonWeights)
+//!
+//! ## Overview
+//!
+//! The Nonfungible pallet provides functions for:
+//!
+//! - NFT collection creation and removal
+//! - Minting and burning of NFT tokens
+//! - Retrieving account balances
+//! - Transfering NFT tokens
+//! - Setting and checking allowance for NFT tokens
+//! - Setting properties and permissions for NFT collections and tokens
+//! - Nesting and unnesting tokens
+//!
+//! ### Terminology
+//!
+//! - **NFT token:** Non fungible token.
+//!
+//! - **NFT Collection:** A collection of NFT tokens. All NFT tokens are part of a collection.
+//! Each collection can define it's own properties, properties for it's tokens and set of permissions.
+//!
+//! - **Balance:** Number of NFT tokens owned by an account
+//!
+//! - **Allowance:** NFT tokens owned by one account that another account is allowed to make operations on
+//!
+//! - **Burning:** The process of “deleting” a token from a collection and from
+//! an account balance of the owner.
+//!
+//! - **Nesting:** Setting up parent-child relationship between tokens. Nested tokens are inhereting
+//! owner from their parent. There could be multiple levels of nesting. Token couldn't be nested in
+//! it's child token i.e. parent-child relationship graph shouldn't have cycles.
+//!
+//! - **Properties:** Key-Values pairs. Token properties are attached to a token. Collection properties are
+//! attached to a collection. Set of permissions could be defined for each property.
+//!
+//! ### Implementations
+//!
+//! The Nonfungible pallet provides implementations for the following traits. If these traits provide
+//! the functionality that you need, then you can avoid coupling with the Nonfungible pallet.
+//!
+//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight
+//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing
+//! with collections
+//!
+//! ## Interface
+//!
+//! ### Dispatchable Functions
+//!
+//! - `init_collection` - Create NFT collection. NFT collection can be configured to allow or deny access for
+//! some accounts.
+//! - `destroy_collection` - Destroy exising NFT collection. There should be no tokens in the collection.
+//! - `burn` - Burn NFT token owned by account.
+//! - `transfer` - Transfer NFT token. Transfers should be enabled for NFT collection.
+//! Nests the NFT token if it is sent to another token.
+//! - `create_item` - Mint NFT token in collection. Sender should have permission to mint tokens.
+//! - `set_allowance` - Set allowance for another account.
+//! - `set_token_property` - Set token property value.
+//! - `delete_token_property` - Remove property from the token.
+//! - `set_collection_properties` - Set collection properties.
+//! - `delete_collection_properties` - Remove properties from the collection.
+//! - `set_property_permission` - Set collection property permission.
+//! - `set_token_property_permissions` - Set token property permissions.
+//!
+//! ## Assumptions
+//!
+//! * To perform operations on tokens sender should be in collection's allow list if collection access mode is `AllowList`.
+
#![cfg_attr(not(feature = "std"), no_std)]
use erc::ERC721Events;
@@ -56,8 +130,8 @@
pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::account::Config>::CrossAccountId>;
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
-/// Token data, stored independently from other data used to describe it.
-/// Notably contains the owner account address.
+/// Token data, stored independently from other data used to describe it
+/// for the convenience of database access. Notably contains the owner account address.
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct ItemData<CrossAccountId> {
@@ -122,7 +196,7 @@
QueryKind = OptionQuery,
>;
- /// Key-value pairs, describing the metadata of a token.
+ /// Map of key-value pairs, describing the metadata of a token.
#[pallet::storage]
#[pallet::getter(fn token_properties)]
pub type TokenProperties<T: Config> = StorageNMap<
@@ -132,7 +206,15 @@
OnEmpty = up_data_structs::TokenProperties,
>;
- /// Scoped, auxiliary properties of a token, primarily used for on-chain operations.
+ /// Custom data of a token that is serialized to bytes,
+ /// primarily reserved for on-chain operations,
+ /// normally obscured from the external users.
+ ///
+ /// Auxiliary properties are slightly different from
+ /// usual [`TokenProperties`] due to an unlimited number
+ /// and separately stored and written-to key-value pairs.
+ ///
+ /// Currently used to store RMRK data.
#[pallet::storage]
#[pallet::getter(fn token_aux_property)]
pub type TokenAuxProperties<T: Config> = StorageNMap<
@@ -171,7 +253,7 @@
QueryKind = ValueQuery,
>;
- /// Amount of tokens owned in a collection.
+ /// Amount of tokens owned by an account in a collection.
#[pallet::storage]
pub type AccountBalance<T: Config> = StorageNMap<
Key = (
@@ -182,7 +264,7 @@
QueryKind = ValueQuery,
>;
- /// todo:doc
+ /// Allowance set by a token owner for another user to perform one of certain transactions on a token.
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
@@ -284,13 +366,21 @@
}
impl<T: Config> Pallet<T> {
+ /// Get number of NFT tokens in collection.
pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {
<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)
}
+
+ /// Check that NFT token exists.
+ ///
+ /// - `token`: Token ID.
pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {
<TokenData<T>>::contains_key((collection.id, token))
}
+ /// Set the token property with the scope.
+ ///
+ /// - `property`: Contains key-value pair.
pub fn set_scoped_token_property(
collection_id: CollectionId,
token_id: TokenId,
@@ -305,6 +395,7 @@
Ok(())
}
+ /// Batch operation to set multiple properties with the same scope.
pub fn set_scoped_token_properties(
collection_id: CollectionId,
token_id: TokenId,
@@ -319,6 +410,9 @@
Ok(())
}
+ /// Add or edit auxiliary data for the property.
+ ///
+ /// - `f`: function that adds or edits auxiliary data.
pub fn try_mutate_token_aux_property<R, E>(
collection_id: CollectionId,
token_id: TokenId,
@@ -329,6 +423,7 @@
<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)
}
+ /// Remove auxiliary data for the property.
pub fn remove_token_aux_property(
collection_id: CollectionId,
token_id: TokenId,
@@ -338,6 +433,9 @@
<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));
}
+ /// Get all auxiliary data in a given scope.
+ ///
+ /// Returns iterator over Property Key - Data pairs.
pub fn iterate_token_aux_properties(
collection_id: CollectionId,
token_id: TokenId,
@@ -346,6 +444,7 @@
<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))
}
+ /// Get ID of the last minted token
pub fn current_token_id(collection_id: CollectionId) -> TokenId {
TokenId(<TokensMinted<T>>::get(collection_id))
}
@@ -353,6 +452,11 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
+ /// Create NFT collection
+ ///
+ /// `init_collection` will take non-refundable deposit for collection creation.
+ ///
+ /// - `data`: Contains settings for collection limits and permissions.
pub fn init_collection(
owner: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
@@ -360,6 +464,11 @@
) -> Result<CollectionId, DispatchError> {
<PalletCommon<T>>::init_collection(owner, data, is_external)
}
+
+ /// Destroy NFT collection
+ ///
+ /// `destroy_collection` will throw error if collection contains any tokens.
+ /// Only owner can destroy collection.
pub fn destroy_collection(
collection: NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -384,6 +493,15 @@
Ok(())
}
+ /// Burn NFT token
+ ///
+ /// `burn` removes `token` from the `collection`, from it's owner and from the parent token
+ /// if the token is nested.
+ /// Only the owner can `burn` the token. The `token` shouldn't have any nested tokens.
+ /// Also removes all corresponding properties and auxiliary properties.
+ ///
+ /// - `token`: Token that should be burned
+ /// - `collection`: Collection that contains the token
pub fn burn(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -453,6 +571,12 @@
Ok(())
}
+ /// Same as [`burn`] but burns all the tokens that are nested in the token first
+ ///
+ /// - `self_budget`: Limit for searching children in depth.
+ /// - `breadth_budget`: Limit of breadth of searching children.
+ ///
+ /// [`burn`]: struct.Pallet.html#method.burn
#[transactional]
pub fn burn_recursively(
collection: &NonfungibleHandle<T>,
@@ -492,6 +616,14 @@
})
}
+ /// Batch operation to add, edit or remove properties for the token
+ ///
+ /// All affected properties should have mutable permission and sender should have
+ /// permission to edit those properties.
+ ///
+ /// - `nesting_budget`: Limit for searching parents in depth to check ownership.
+ /// - `is_token_create`: Indicates that method is called during token initialization.
+ /// Allows to bypass ownership check.
#[transactional]
fn modify_token_properties(
collection: &NonfungibleHandle<T>,
@@ -585,6 +717,11 @@
Ok(())
}
+ /// Batch operation to add or edit properties for the token
+ ///
+ /// Same as [`modify_token_properties`] but doesn't allow to remove properties
+ ///
+ /// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties
pub fn set_token_properties(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -603,6 +740,11 @@
)
}
+ /// Add or edit single property for the token
+ ///
+ /// Calls [`set_token_properties`] internally
+ ///
+ /// [`set_token_properties`]: struct.Pallet.html#method.set_token_properties
pub fn set_token_property(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -622,6 +764,11 @@
)
}
+ /// Batch operation to remove properties from the token
+ ///
+ /// Same as [`modify_token_properties`] but doesn't allow to add or edit properties
+ ///
+ /// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties
pub fn delete_token_properties(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -641,6 +788,11 @@
)
}
+ /// Remove single property from the token
+ ///
+ /// Calls [`delete_token_properties`] internally
+ ///
+ /// [`delete_token_properties`]: struct.Pallet.html#method.delete_token_properties
pub fn delete_token_property(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -657,6 +809,7 @@
)
}
+ /// Add or edit properties for the collection
pub fn set_collection_properties(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -665,6 +818,7 @@
<PalletCommon<T>>::set_collection_properties(collection, sender, properties)
}
+ /// Remove properties from the collection
pub fn delete_collection_properties(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -673,6 +827,9 @@
<PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)
}
+ /// Set property permissions for the token.
+ ///
+ /// Sender should be the owner or admin of token's collection.
pub fn set_token_property_permissions(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -681,6 +838,9 @@
<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)
}
+ /// Set property permissions for the collection.
+ ///
+ /// Sender should be the owner or admin of the collection.
pub fn set_property_permission(
collection: &CollectionHandle<T>,
sender: &T::CrossAccountId,
@@ -689,6 +849,15 @@
<PalletCommon<T>>::set_property_permission(collection, sender, permission)
}
+ /// Transfer NFT token from one account to another.
+ ///
+ /// `from` account stops being the owner and `to` account becomes the owner of the token.
+ /// If `to` is token than `to` becomes owner of the token and the token become nested.
+ /// Unnests token from previous parent if it was nested before.
+ /// Removes allowance for the token if there was any.
+ /// Throws if transfers aren't allowed for collection or if receiver reached token ownership limit.
+ ///
+ /// - `nesting_budget`: Limit for token nesting depth
pub fn transfer(
collection: &NonfungibleHandle<T>,
from: &T::CrossAccountId,
@@ -780,6 +949,16 @@
Ok(())
}
+ /// Batch operation to mint multiple NFT tokens.
+ ///
+ /// The sender should be the owner/admin of the collection or collection should be configured
+ /// to allow public minting.
+ /// Throws if amount of tokens reached it's limit for the collection or if caller reached
+ /// token ownership limit.
+ ///
+ /// - `data`: Contains list of token properties and users who will become the owners of the
+ /// corresponging tokens.
+ /// - `nesting_budget`: Limit for token nesting depth
pub fn create_multiple_items(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -964,6 +1143,9 @@
}
}
+ /// Set allowance for the spender to `transfer` or `burn` sender's token.
+ ///
+ /// - `token`: Token the spender is allowed to `transfer` or `burn`.
pub fn set_allowance(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -996,6 +1178,7 @@
Ok(())
}
+ /// Checks allowance for the spender to use the token.
fn check_allowed(
collection: &NonfungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -1038,6 +1221,12 @@
Ok(())
}
+ /// Transfer NFT token from one account to another.
+ ///
+ /// Same as the [`transfer`] but spender doesn't needs to be the owner of the token.
+ /// The owner should set allowance for the spender to transfer token.
+ ///
+ /// [`transfer`]: struct.Pallet.html#method.transfer
pub fn transfer_from(
collection: &NonfungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -1054,6 +1243,12 @@
Self::transfer(collection, from, to, token, nesting_budget)
}
+ /// Burn NFT token for `from` account.
+ ///
+ /// Same as the [`burn`] but spender doesn't need to be an owner of the token. The owner should
+ /// set allowance for the spender to burn token.
+ ///
+ /// [`burn`]: struct.Pallet.html#method.burn
pub fn burn_from(
collection: &NonfungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -1068,6 +1263,8 @@
Self::burn(collection, from, token)
}
+ /// Check that `from` token could be nested in `under` token.
+ ///
pub fn check_nesting(
handle: &NonfungibleHandle<T>,
sender: T::CrossAccountId,
@@ -1137,7 +1334,11 @@
.collect()
}
- /// Delegated to `create_multiple_items`
+ /// Mint single NFT token.
+ ///
+ /// Delegated to [`create_multiple_items`]
+ ///
+ /// [`create_multiple_items`]: struct.Pallet.html#method.create_multiple_items
pub fn create_item(
collection: &NonfungibleHandle<T>,
sender: &T::CrossAccountId,
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -53,6 +53,13 @@
// Selector: 41369377
contract TokenProperties is Dummy, ERC165 {
+ // @notice Set permissions for token property.
+ // @dev Throws error if `msg.sender` is not admin or owner of the collection.
+ // @param key Property key.
+ // @param is_mutable Permission to mutate property.
+ // @param collection_admin Permission to mutate property by collection admin if property is mutable.
+ // @param token_owner Permission to mutate property by token owner if property is mutable.
+ //
// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
function setTokenPropertyPermission(
string memory key,
@@ -68,6 +75,12 @@
dummy = 0;
}
+ // @notice Set token property value.
+ // @dev Throws error if `msg.sender` has no permission to edit the property.
+ // @param tokenId ID of the token.
+ // @param key Property key.
+ // @param value Property value.
+ //
// Selector: setProperty(uint256,string,bytes) 1752d67b
function setProperty(
uint256 tokenId,
@@ -81,6 +94,11 @@
dummy = 0;
}
+ // @notice Delete token property value.
+ // @dev Throws error if `msg.sender` has no permission to edit the property.
+ // @param tokenId ID of the token.
+ // @param key Property key.
+ //
// Selector: deleteProperty(uint256,string) 066111d1
function deleteProperty(uint256 tokenId, string memory key) public {
require(false, stub_error);
@@ -89,7 +107,11 @@
dummy = 0;
}
- // Throws error if key not found
+ // @notice Get token property value.
+ // @dev Throws error if key not found
+ // @param tokenId ID of the token.
+ // @param key Property key.
+ // @return Property value bytes
//
// Selector: property(uint256,string) 7228c327
function property(uint256 tokenId, string memory key)
@@ -107,6 +129,11 @@
// Selector: 42966c68
contract ERC721Burnable is Dummy, ERC165 {
+ // @notice Burns a specific ERC721 token.
+ // @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ // operator of the current owner.
+ // @param tokenId The NFT to approve
+ //
// Selector: burn(uint256) 42966c68
function burn(uint256 tokenId) public {
require(false, stub_error);
@@ -117,6 +144,12 @@
// Selector: 58800161
contract ERC721 is Dummy, ERC165, ERC721Events {
+ // @notice Count all NFTs assigned to an owner
+ // @dev NFTs assigned to the zero address are considered invalid, and this
+ // function throws for queries about the zero address.
+ // @param owner An address for whom to query the balance
+ // @return The number of NFTs owned by `owner`, possibly zero
+ //
// Selector: balanceOf(address) 70a08231
function balanceOf(address owner) public view returns (uint256) {
require(false, stub_error);
@@ -125,6 +158,12 @@
return 0;
}
+ // @notice Find the owner of an NFT
+ // @dev NFTs assigned to zero address are considered invalid, and queries
+ // about them do throw.
+ // @param tokenId The identifier for an NFT
+ // @return The address of the owner of the NFT
+ //
// Selector: ownerOf(uint256) 6352211e
function ownerOf(uint256 tokenId) public view returns (address) {
require(false, stub_error);
@@ -133,7 +172,7 @@
return 0x0000000000000000000000000000000000000000;
}
- // Not implemented
+ // @dev Not implemented
//
// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672
function safeTransferFromWithData(
@@ -150,7 +189,7 @@
dummy = 0;
}
- // Not implemented
+ // @dev Not implemented
//
// Selector: safeTransferFrom(address,address,uint256) 42842e0e
function safeTransferFrom(
@@ -165,6 +204,17 @@
dummy = 0;
}
+ // @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
+ // TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
+ // THEY MAY BE PERMANENTLY LOST
+ // @dev Throws unless `msg.sender` is the current owner or an authorized
+ // operator for this NFT. Throws if `from` is not the current owner. Throws
+ // if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ // @param from The current owner of the NFT
+ // @param to The new owner
+ // @param tokenId The NFT to transfer
+ // @param _value Not used for an NFT
+ //
// Selector: transferFrom(address,address,uint256) 23b872dd
function transferFrom(
address from,
@@ -178,6 +228,13 @@
dummy = 0;
}
+ // @notice Set or reaffirm the approved address for an NFT
+ // @dev The zero address indicates there is no approved address.
+ // @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ // operator of the current owner.
+ // @param approved The new approved NFT controller
+ // @param tokenId The NFT to approve
+ //
// Selector: approve(address,uint256) 095ea7b3
function approve(address approved, uint256 tokenId) public {
require(false, stub_error);
@@ -186,7 +243,7 @@
dummy = 0;
}
- // Not implemented
+ // @dev Not implemented
//
// Selector: setApprovalForAll(address,bool) a22cb465
function setApprovalForAll(address operator, bool approved) public {
@@ -196,7 +253,7 @@
dummy = 0;
}
- // Not implemented
+ // @dev Not implemented
//
// Selector: getApproved(uint256) 081812fc
function getApproved(uint256 tokenId) public view returns (address) {
@@ -206,7 +263,7 @@
return 0x0000000000000000000000000000000000000000;
}
- // Not implemented
+ // @dev Not implemented
//
// Selector: isApprovedForAll(address,address) e985e9c5
function isApprovedForAll(address owner, address operator)
@@ -224,6 +281,8 @@
// Selector: 5b5e139f
contract ERC721Metadata is Dummy, ERC165 {
+ // @notice A descriptive name for a collection of NFTs in this contract
+ //
// Selector: name() 06fdde03
function name() public view returns (string memory) {
require(false, stub_error);
@@ -231,6 +290,8 @@
return "";
}
+ // @notice An abbreviated name for NFTs in this contract
+ //
// Selector: symbol() 95d89b41
function symbol() public view returns (string memory) {
require(false, stub_error);
@@ -238,7 +299,11 @@
return "";
}
- // Returns token's const_metadata
+ // @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ // @dev Throws if `tokenId` is not a valid NFT. URIs are defined in RFC
+ // 3986. The URI may point to a JSON file that conforms to the "ERC721
+ // Metadata JSON Schema".
+ // @return token's const_metadata
//
// Selector: tokenURI(uint256) c87b56dd
function tokenURI(uint256 tokenId) public view returns (string memory) {
@@ -258,8 +323,11 @@
return false;
}
- // `token_id` should be obtained with `next_token_id` method,
- // unlike standard, you can't specify it manually
+ // @notice Function to mint token.
+ // @dev `tokenId` should be obtained with `nextTokenId` method,
+ // unlike standard, you can't specify it manually
+ // @param to The new owner
+ // @param tokenId ID of the minted NFT
//
// Selector: mint(address,uint256) 40c10f19
function mint(address to, uint256 tokenId) public returns (bool) {
@@ -270,8 +338,12 @@
return false;
}
- // `token_id` should be obtained with `next_token_id` method,
- // unlike standard, you can't specify it manually
+ // @notice Function to mint token with the given tokenUri.
+ // @dev `tokenId` should be obtained with `nextTokenId` method,
+ // unlike standard, you can't specify it manually
+ // @param to The new owner
+ // @param tokenId ID of the minted NFT
+ // @param tokenUri Token URI that would be stored in the NFT properties
//
// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
function mintWithTokenURI(
@@ -287,7 +359,7 @@
return false;
}
- // Not implemented
+ // @dev Not implemented
//
// Selector: finishMinting() 7d64bcb4
function finishMinting() public returns (bool) {
@@ -299,6 +371,11 @@
// Selector: 780e9d63
contract ERC721Enumerable is Dummy, ERC165 {
+ // @notice Enumerate valid NFTs
+ // @param index A counter less than `totalSupply()`
+ // @return The token identifier for the `index`th NFT,
+ // (sort order not specified)
+ //
// Selector: tokenByIndex(uint256) 4f6ccce7
function tokenByIndex(uint256 index) public view returns (uint256) {
require(false, stub_error);
@@ -307,7 +384,7 @@
return 0;
}
- // Not implemented
+ // @dev Not implemented
//
// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
function tokenOfOwnerByIndex(address owner, uint256 index)
@@ -322,6 +399,10 @@
return 0;
}
+ // @notice Count NFTs tracked by this contract
+ // @return A count of valid NFTs tracked by this contract, where each one of
+ // them has an assigned and queryable owner not equal to the zero address
+ //
// Selector: totalSupply() 18160ddd
function totalSupply() public view returns (uint256) {
require(false, stub_error);
@@ -332,6 +413,11 @@
// Selector: 7d9262e6
contract Collection is Dummy, ERC165 {
+ // Set collection property.
+ //
+ // @param key Property key.
+ // @param value Propery value.
+ //
// Selector: setCollectionProperty(string,bytes) 2f073f66
function setCollectionProperty(string memory key, bytes memory value)
public
@@ -342,6 +428,10 @@
dummy = 0;
}
+ // Delete collection property.
+ //
+ // @param key Property key.
+ //
// Selector: deleteCollectionProperty(string) 7b7debce
function deleteCollectionProperty(string memory key) public {
require(false, stub_error);
@@ -349,8 +439,13 @@
dummy = 0;
}
- // Throws error if key not found
+ // Get collection property.
+ //
+ // @dev Throws error if key not found.
//
+ // @param key Property key.
+ // @return bytes The property corresponding to the key.
+ //
// Selector: collectionProperty(string) cf24fd6d
function collectionProperty(string memory key)
public
@@ -363,6 +458,12 @@
return hex"";
}
+ // Set the sponsor of the collection.
+ //
+ // @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ //
+ // @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.
+ //
// Selector: setCollectionSponsor(address) 7623402e
function setCollectionSponsor(address sponsor) public {
require(false, stub_error);
@@ -370,12 +471,27 @@
dummy = 0;
}
+ // Collection sponsorship confirmation.
+ //
+ // @dev After setting the sponsor for the collection, it must be confirmed with this function.
+ //
// Selector: confirmCollectionSponsorship() 3c50e97a
function confirmCollectionSponsorship() public {
require(false, stub_error);
dummy = 0;
}
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "accountTokenOwnershipLimit",
+ // "sponsoredDataSize",
+ // "sponsoredDataRateLimit",
+ // "tokenLimit",
+ // "sponsorTransferTimeout",
+ // "sponsorApproveTimeout"
+ // @param value Value of the limit.
+ //
// Selector: setCollectionLimit(string,uint32) 6a3841db
function setCollectionLimit(string memory limit, uint32 value) public {
require(false, stub_error);
@@ -384,6 +500,14 @@
dummy = 0;
}
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "ownerCanTransfer",
+ // "ownerCanDestroy",
+ // "transfersEnabled"
+ // @param value Value of the limit.
+ //
// Selector: setCollectionLimit(string,bool) 993b7fba
function setCollectionLimit(string memory limit, bool value) public {
require(false, stub_error);
@@ -392,6 +516,8 @@
dummy = 0;
}
+ // Get contract address.
+ //
// Selector: contractAddress() f6b4dfb4
function contractAddress() public view returns (address) {
require(false, stub_error);
@@ -399,34 +525,51 @@
return 0x0000000000000000000000000000000000000000;
}
+ // Add collection admin by substrate address.
+ // @param new_admin Substrate administrator address.
+ //
// Selector: addCollectionAdminSubstrate(uint256) 5730062b
- function addCollectionAdminSubstrate(uint256 newAdmin) public view {
+ function addCollectionAdminSubstrate(uint256 newAdmin) public {
require(false, stub_error);
newAdmin;
- dummy;
+ dummy = 0;
}
+ // Remove collection admin by substrate address.
+ // @param admin Substrate administrator address.
+ //
// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
- function removeCollectionAdminSubstrate(uint256 newAdmin) public view {
+ function removeCollectionAdminSubstrate(uint256 admin) public {
require(false, stub_error);
- newAdmin;
- dummy;
+ admin;
+ dummy = 0;
}
+ // Add collection admin.
+ // @param new_admin Address of the added administrator.
+ //
// Selector: addCollectionAdmin(address) 92e462c7
- function addCollectionAdmin(address newAdmin) public view {
+ function addCollectionAdmin(address newAdmin) public {
require(false, stub_error);
newAdmin;
- dummy;
+ dummy = 0;
}
+ // Remove collection admin.
+ //
+ // @param new_admin Address of the removed administrator.
+ //
// Selector: removeCollectionAdmin(address) fafd7b42
- function removeCollectionAdmin(address admin) public view {
+ function removeCollectionAdmin(address admin) public {
require(false, stub_error);
admin;
- dummy;
+ dummy = 0;
}
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'
+ //
// Selector: setCollectionNesting(bool) 112d4586
function setCollectionNesting(bool enable) public {
require(false, stub_error);
@@ -434,6 +577,11 @@
dummy = 0;
}
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
+ // @param collections Addresses of collections that will be available for nesting.
+ //
// Selector: setCollectionNesting(bool,address[]) 64872396
function setCollectionNesting(bool enable, address[] memory collections)
public
@@ -444,6 +592,11 @@
dummy = 0;
}
+ // Set the collection access method.
+ // @param mode Access mode
+ // 0 for Normal
+ // 1 for AllowList
+ //
// Selector: setCollectionAccess(uint8) 41835d4c
function setCollectionAccess(uint8 mode) public {
require(false, stub_error);
@@ -451,20 +604,32 @@
dummy = 0;
}
+ // Add the user to the allowed list.
+ //
+ // @param user Address of a trusted user.
+ //
// Selector: addToCollectionAllowList(address) 67844fe6
- function addToCollectionAllowList(address user) public view {
+ function addToCollectionAllowList(address user) public {
require(false, stub_error);
user;
- dummy;
+ dummy = 0;
}
+ // Remove the user from the allowed list.
+ //
+ // @param user Address of a removed user.
+ //
// Selector: removeFromCollectionAllowList(address) 85c51acb
- function removeFromCollectionAllowList(address user) public view {
+ function removeFromCollectionAllowList(address user) public {
require(false, stub_error);
user;
- dummy;
+ dummy = 0;
}
+ // Switch permission for minting.
+ //
+ // @param mode Enable if "true".
+ //
// Selector: setCollectionMintMode(bool) 00018e84
function setCollectionMintMode(bool mode) public {
require(false, stub_error);
@@ -475,6 +640,13 @@
// Selector: d74d154f
contract ERC721UniqueExtensions is Dummy, ERC165 {
+ // @notice Transfer ownership of an NFT
+ // @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ // is the zero address. Throws if `tokenId` is not a valid NFT.
+ // @param to The new owner
+ // @param tokenId The NFT to transfer
+ // @param _value Not used for an NFT
+ //
// Selector: transfer(address,uint256) a9059cbb
function transfer(address to, uint256 tokenId) public {
require(false, stub_error);
@@ -483,6 +655,14 @@
dummy = 0;
}
+ // @notice Burns a specific ERC721 token.
+ // @dev Throws unless `msg.sender` is the current owner or an authorized
+ // operator for this NFT. Throws if `from` is not the current owner. Throws
+ // if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ // @param from The current owner of the NFT
+ // @param tokenId The NFT to transfer
+ // @param _value Not used for an NFT
+ //
// Selector: burnFrom(address,uint256) 79cc6790
function burnFrom(address from, uint256 tokenId) public {
require(false, stub_error);
@@ -491,6 +671,8 @@
dummy = 0;
}
+ // @notice Returns next free NFT ID.
+ //
// Selector: nextTokenId() 75794a3c
function nextTokenId() public view returns (uint256) {
require(false, stub_error);
@@ -498,6 +680,12 @@
return 0;
}
+ // @notice Function to mint multiple tokens.
+ // @dev `tokenIds` should be an array of consecutive numbers and first number
+ // should be obtained with `nextTokenId` method
+ // @param to The new owner
+ // @param tokenIds IDs of the minted NFTs
+ //
// Selector: mintBulk(address,uint256[]) 44a9945e
function mintBulk(address to, uint256[] memory tokenIds)
public
@@ -510,6 +698,12 @@
return false;
}
+ // @notice Function to mint multiple tokens with the given tokenUris.
+ // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
+ // numbers and first number should be obtained with `nextTokenId` method
+ // @param to The new owner
+ // @param tokens array of pairs of token ID and token URI for minted tokens
+ //
// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
public
pallets/nonfungible/src/weights.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -3,7 +3,7 @@
//! Autogenerated weights for pallet_nonfungible
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-06-15, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
// Executed Command:
@@ -56,7 +56,7 @@
// Storage: Nonfungible TokenData (r:0 w:1)
// Storage: Nonfungible Owned (r:0 w:1)
fn create_item() -> Weight {
- (24_135_000 as Weight)
+ (20_328_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
@@ -65,9 +65,9 @@
// Storage: Nonfungible TokenData (r:0 w:4)
// Storage: Nonfungible Owned (r:0 w:4)
fn create_multiple_items(b: u32, ) -> Weight {
- (21_952_000 as Weight)
- // Standard Error: 5_000
- .saturating_add((4_727_000 as Weight).saturating_mul(b as Weight))
+ (10_134_000 as Weight)
+ // Standard Error: 3_000
+ .saturating_add((4_927_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
.saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
@@ -77,9 +77,9 @@
// Storage: Nonfungible TokenData (r:0 w:4)
// Storage: Nonfungible Owned (r:0 w:4)
fn create_multiple_items_ex(b: u32, ) -> Weight {
- (10_432_000 as Weight)
- // Standard Error: 6_000
- .saturating_add((7_383_000 as Weight).saturating_mul(b as Weight))
+ (5_710_000 as Weight)
+ // Standard Error: 4_000
+ .saturating_add((7_578_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
@@ -93,7 +93,7 @@
// Storage: Nonfungible Owned (r:0 w:1)
// Storage: Nonfungible TokenProperties (r:0 w:1)
fn burn_item() -> Weight {
- (29_798_000 as Weight)
+ (28_433_000 as Weight)
.saturating_add(T::DbWeight::get().reads(5 as Weight))
.saturating_add(T::DbWeight::get().writes(5 as Weight))
}
@@ -105,7 +105,7 @@
// Storage: Nonfungible Owned (r:0 w:1)
// Storage: Nonfungible TokenProperties (r:0 w:1)
fn burn_recursively_self_raw() -> Weight {
- (37_955_000 as Weight)
+ (34_435_000 as Weight)
.saturating_add(T::DbWeight::get().reads(5 as Weight))
.saturating_add(T::DbWeight::get().writes(5 as Weight))
}
@@ -119,8 +119,8 @@
// Storage: Common CollectionById (r:1 w:0)
fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
(0 as Weight)
- // Standard Error: 1_349_000
- .saturating_add((275_145_000 as Weight).saturating_mul(b as Weight))
+ // Standard Error: 1_539_000
+ .saturating_add((304_456_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(7 as Weight))
.saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
.saturating_add(T::DbWeight::get().writes(6 as Weight))
@@ -131,14 +131,14 @@
// Storage: Nonfungible Allowance (r:1 w:0)
// Storage: Nonfungible Owned (r:0 w:2)
fn transfer() -> Weight {
- (27_867_000 as Weight)
+ (24_376_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(5 as Weight))
}
// Storage: Nonfungible TokenData (r:1 w:0)
// Storage: Nonfungible Allowance (r:1 w:1)
fn approve() -> Weight {
- (18_824_000 as Weight)
+ (15_890_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
@@ -147,7 +147,7 @@
// Storage: Nonfungible AccountBalance (r:2 w:2)
// Storage: Nonfungible Owned (r:0 w:2)
fn transfer_from() -> Weight {
- (32_879_000 as Weight)
+ (28_634_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(6 as Weight))
}
@@ -159,7 +159,7 @@
// Storage: Nonfungible Owned (r:0 w:1)
// Storage: Nonfungible TokenProperties (r:0 w:1)
fn burn_from() -> Weight {
- (37_061_000 as Weight)
+ (32_201_000 as Weight)
.saturating_add(T::DbWeight::get().reads(5 as Weight))
.saturating_add(T::DbWeight::get().writes(6 as Weight))
}
@@ -167,28 +167,26 @@
fn set_token_property_permissions(b: u32, ) -> Weight {
(0 as Weight)
// Standard Error: 57_000
- .saturating_add((15_149_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add((15_232_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: Common CollectionPropertyPermissions (r:1 w:0)
- // Storage: Nonfungible TokenData (r:1 w:0)
// Storage: Nonfungible TokenProperties (r:1 w:1)
fn set_token_properties(b: u32, ) -> Weight {
(0 as Weight)
- // Standard Error: 2_278_000
- .saturating_add((409_613_000 as Weight).saturating_mul(b as Weight))
- .saturating_add(T::DbWeight::get().reads(3 as Weight))
+ // Standard Error: 1_648_000
+ .saturating_add((288_654_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: Common CollectionPropertyPermissions (r:1 w:0)
- // Storage: Nonfungible TokenData (r:1 w:0)
// Storage: Nonfungible TokenProperties (r:1 w:1)
fn delete_token_properties(b: u32, ) -> Weight {
(0 as Weight)
- // Standard Error: 2_234_000
- .saturating_add((408_185_000 as Weight).saturating_mul(b as Weight))
- .saturating_add(T::DbWeight::get().reads(3 as Weight))
+ // Standard Error: 1_632_000
+ .saturating_add((289_190_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
}
@@ -200,7 +198,7 @@
// Storage: Nonfungible TokenData (r:0 w:1)
// Storage: Nonfungible Owned (r:0 w:1)
fn create_item() -> Weight {
- (24_135_000 as Weight)
+ (20_328_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
@@ -209,9 +207,9 @@
// Storage: Nonfungible TokenData (r:0 w:4)
// Storage: Nonfungible Owned (r:0 w:4)
fn create_multiple_items(b: u32, ) -> Weight {
- (21_952_000 as Weight)
- // Standard Error: 5_000
- .saturating_add((4_727_000 as Weight).saturating_mul(b as Weight))
+ (10_134_000 as Weight)
+ // Standard Error: 3_000
+ .saturating_add((4_927_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
.saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(b as Weight)))
@@ -221,9 +219,9 @@
// Storage: Nonfungible TokenData (r:0 w:4)
// Storage: Nonfungible Owned (r:0 w:4)
fn create_multiple_items_ex(b: u32, ) -> Weight {
- (10_432_000 as Weight)
- // Standard Error: 6_000
- .saturating_add((7_383_000 as Weight).saturating_mul(b as Weight))
+ (5_710_000 as Weight)
+ // Standard Error: 4_000
+ .saturating_add((7_578_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
@@ -237,7 +235,7 @@
// Storage: Nonfungible Owned (r:0 w:1)
// Storage: Nonfungible TokenProperties (r:0 w:1)
fn burn_item() -> Weight {
- (29_798_000 as Weight)
+ (28_433_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
}
@@ -249,7 +247,7 @@
// Storage: Nonfungible Owned (r:0 w:1)
// Storage: Nonfungible TokenProperties (r:0 w:1)
fn burn_recursively_self_raw() -> Weight {
- (37_955_000 as Weight)
+ (34_435_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
}
@@ -263,8 +261,8 @@
// Storage: Common CollectionById (r:1 w:0)
fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight {
(0 as Weight)
- // Standard Error: 1_349_000
- .saturating_add((275_145_000 as Weight).saturating_mul(b as Weight))
+ // Standard Error: 1_539_000
+ .saturating_add((304_456_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(7 as Weight))
.saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(b as Weight)))
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
@@ -275,14 +273,14 @@
// Storage: Nonfungible Allowance (r:1 w:0)
// Storage: Nonfungible Owned (r:0 w:2)
fn transfer() -> Weight {
- (27_867_000 as Weight)
+ (24_376_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
}
// Storage: Nonfungible TokenData (r:1 w:0)
// Storage: Nonfungible Allowance (r:1 w:1)
fn approve() -> Weight {
- (18_824_000 as Weight)
+ (15_890_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
@@ -291,7 +289,7 @@
// Storage: Nonfungible AccountBalance (r:2 w:2)
// Storage: Nonfungible Owned (r:0 w:2)
fn transfer_from() -> Weight {
- (32_879_000 as Weight)
+ (28_634_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
}
@@ -303,7 +301,7 @@
// Storage: Nonfungible Owned (r:0 w:1)
// Storage: Nonfungible TokenProperties (r:0 w:1)
fn burn_from() -> Weight {
- (37_061_000 as Weight)
+ (32_201_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
}
@@ -311,28 +309,26 @@
fn set_token_property_permissions(b: u32, ) -> Weight {
(0 as Weight)
// Standard Error: 57_000
- .saturating_add((15_149_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add((15_232_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
// Storage: Common CollectionPropertyPermissions (r:1 w:0)
- // Storage: Nonfungible TokenData (r:1 w:0)
// Storage: Nonfungible TokenProperties (r:1 w:1)
fn set_token_properties(b: u32, ) -> Weight {
(0 as Weight)
- // Standard Error: 2_278_000
- .saturating_add((409_613_000 as Weight).saturating_mul(b as Weight))
- .saturating_add(RocksDbWeight::get().reads(3 as Weight))
+ // Standard Error: 1_648_000
+ .saturating_add((288_654_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
// Storage: Common CollectionPropertyPermissions (r:1 w:0)
- // Storage: Nonfungible TokenData (r:1 w:0)
// Storage: Nonfungible TokenProperties (r:1 w:1)
fn delete_token_properties(b: u32, ) -> Weight {
(0 as Weight)
- // Standard Error: 2_234_000
- .saturating_add((408_185_000 as Weight).saturating_mul(b as Weight))
- .saturating_add(RocksDbWeight::get().reads(3 as Weight))
+ // Standard Error: 1_632_000
+ .saturating_add((289_190_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
}
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/refungible/CHANGELOG.md
@@ -0,0 +1,18 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [v0.1.2] - 2022-07-14
+
+### Other changes
+
+feat(refungible-pallet): add ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
+test(refungible-pallet): add tests for ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
+
+## [v0.1.1] - 2022-07-14
+
+### Other changes
+
+- feat: RPC method `token_owners` returning 10 owners in no particular order.
+
+This was an internal request to improve the web interface and support fractionalization event.
pallets/refungible/Cargo.tomldiffbeforeafterboth--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-refungible"
-version = "0.1.0"
+version = "0.1.2"
license = "GPLv3"
edition = "2021"
@@ -11,36 +11,43 @@
version = '3.1.2'
[dependencies]
-frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
pallet-common = { default-features = false, path = '../common' }
pallet-structure = { default-features = false, path = '../structure' }
+struct-versioning = { path = "../../crates/struct-versioning" }
up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
+
+ethereum = { version = "0.12.0", default-features = false }
+scale-info = { version = "2.0.1", default-features = false, features = ["derive",] }
+
frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
-scale-info = { version = "2.0.1", default-features = false, features = [
- "derive",
-] }
-struct-versioning = { path = "../../crates/struct-versioning" }
+frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" }
+sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
+sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" }
[features]
default = ["std"]
std = [
+ "ethereum/std",
+ "evm-coder/std",
+ 'frame-benchmarking/std',
"frame-support/std",
"frame-system/std",
+ "pallet-common/std",
+ "pallet-evm/std",
+ "pallet-evm-coder-substrate/std",
+ "pallet-structure/std",
"sp-runtime/std",
"sp-std/std",
"up-data-structs/std",
- "pallet-common/std",
- "pallet-structure/std",
- 'frame-benchmarking/std',
- "pallet-evm/std",
]
runtime-benchmarks = [
'frame-benchmarking',
'frame-support/runtime-benchmarks',
'frame-system/runtime-benchmarks',
+ 'up-data-structs/runtime-benchmarks',
]
pallets/refungible/Changelog.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/refungible/Changelog.md
@@ -0,0 +1,3 @@
+### 0.1.1
+---
+* Added support for properties for RFT collections and tokens.
pallets/refungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -18,9 +18,12 @@
use crate::{Pallet, Config, RefungibleHandle};
use sp_std::prelude::*;
-use pallet_common::benchmarking::{create_collection_raw, create_data};
+use pallet_common::benchmarking::{create_collection_raw, property_key, property_value, create_data};
use frame_benchmarking::{benchmarks, account};
-use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, CUSTOM_DATA_LIMIT, budget::Unlimited};
+use up_data_structs::{
+ CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT,
+ budget::Unlimited,
+};
use pallet_common::bench_init;
use core::convert::TryInto;
use core::iter::IntoIterator;
@@ -38,6 +41,7 @@
.collect::<BTreeMap<_, _>>()
.try_into()
.unwrap(),
+ properties: Default::default(),
}
}
fn create_max_item<T: Config>(
@@ -204,6 +208,68 @@
<Pallet<T>>::set_allowance(&collection, &sender, &burner, item, 200)?;
}: {<Pallet<T>>::burn_from(&collection, &burner, &sender, item, 200, &Unlimited)?}
+ set_token_property_permissions {
+ let b in 0..MAX_PROPERTIES_PER_ITEM;
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ owner: cross_from_sub;
+ };
+ let perms = (0..b).map(|k| PropertyKeyPermission {
+ key: property_key(k as usize),
+ permission: PropertyPermission {
+ mutable: false,
+ collection_admin: false,
+ token_owner: false,
+ },
+ }).collect::<Vec<_>>();
+ }: {<Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?}
+
+ set_token_properties {
+ let b in 0..MAX_PROPERTIES_PER_ITEM;
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ owner: cross_from_sub;
+ };
+ let perms = (0..b).map(|k| PropertyKeyPermission {
+ key: property_key(k as usize),
+ permission: PropertyPermission {
+ mutable: false,
+ collection_admin: true,
+ token_owner: true,
+ },
+ }).collect::<Vec<_>>();
+ <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+ let props = (0..b).map(|k| Property {
+ key: property_key(k as usize),
+ value: property_value(),
+ }).collect::<Vec<_>>();
+ let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+ }: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?}
+
+ delete_token_properties {
+ let b in 0..MAX_PROPERTIES_PER_ITEM;
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ owner: cross_from_sub;
+ };
+ let perms = (0..b).map(|k| PropertyKeyPermission {
+ key: property_key(k as usize),
+ permission: PropertyPermission {
+ mutable: true,
+ collection_admin: true,
+ token_owner: true,
+ },
+ }).collect::<Vec<_>>();
+ <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+ let props = (0..b).map(|k| Property {
+ key: property_key(k as usize),
+ value: property_value(),
+ }).collect::<Vec<_>>();
+ let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+ <Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?;
+ let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
+ }: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
+
repartition_item {
bench_init!{
owner: sub; collection: collection(owner);
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -20,9 +20,12 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
use up_data_structs::{
CollectionId, TokenId, CreateItemExData, CreateRefungibleExData, budget::Budget, Property,
- PropertyKey, PropertyValue, PropertyKeyPermission, CreateItemData,
+ PropertyKey, PropertyValue, PropertyKeyPermission, CreateItemData, CollectionPropertiesVec,
+};
+use pallet_common::{
+ CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
+ weights::WeightInfo as _,
};
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight};
use pallet_structure::Error as StructureError;
use sp_runtime::{DispatchError};
use sp_std::{vec::Vec, vec};
@@ -41,6 +44,14 @@
};
}
+fn properties_weight<T: Config>(properties: &CollectionPropertiesVec) -> u64 {
+ if properties.len() > 0 {
+ <CommonWeights<T>>::set_token_properties(properties.len() as u32)
+ } else {
+ 0
+ }
+}
+
pub struct CommonWeights<T: Config>(PhantomData<T>);
impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
fn create_item() -> Weight {
@@ -48,16 +59,31 @@
}
fn create_multiple_items(data: &[CreateItemData]) -> Weight {
- <SelfWeightOf<T>>::create_multiple_items(data.len() as u32)
+ <SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
+ data.iter()
+ .map(|data| match data {
+ CreateItemData::ReFungible(rft_data) => {
+ properties_weight::<T>(&rft_data.properties)
+ }
+ _ => 0,
+ })
+ .fold(0, |a, b| a.saturating_add(b)),
+ )
}
fn create_multiple_items_ex(call: &CreateItemExData<T::CrossAccountId>) -> Weight {
match call {
CreateItemExData::RefungibleMultipleOwners(i) => {
<SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)
+ .saturating_add(properties_weight::<T>(&i.properties))
}
CreateItemExData::RefungibleMultipleItems(i) => {
<SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)
+ .saturating_add(
+ i.iter()
+ .map(|d| properties_weight::<T>(&d.properties))
+ .fold(0, |a, b| a.saturating_add(b)),
+ )
}
_ => 0,
}
@@ -67,29 +93,24 @@
max_weight_of!(burn_item_partial(), burn_item_fully())
}
- fn set_collection_properties(_amount: u32) -> Weight {
- // Error
- 0
+ fn set_collection_properties(amount: u32) -> Weight {
+ <pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)
}
- fn delete_collection_properties(_amount: u32) -> Weight {
- // Error
- 0
+ fn delete_collection_properties(amount: u32) -> Weight {
+ <pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)
}
- fn set_token_properties(_amount: u32) -> Weight {
- // Error
- 0
+ fn set_token_properties(amount: u32) -> Weight {
+ <SelfWeightOf<T>>::set_token_properties(amount)
}
- fn delete_token_properties(_amount: u32) -> Weight {
- // Error
- 0
+ fn delete_token_properties(amount: u32) -> Weight {
+ <SelfWeightOf<T>>::delete_token_properties(amount)
}
- fn set_token_property_permissions(_amount: u32) -> Weight {
- // Error
- 0
+ fn set_token_property_permissions(amount: u32) -> Weight {
+ <SelfWeightOf<T>>::set_token_property_permissions(amount)
}
fn transfer() -> Weight {
@@ -140,11 +161,14 @@
out.insert(to.clone(), data.pieces);
out.try_into().expect("limit > 0")
},
+ properties: data.properties,
}),
_ => fail!(<Error<T>>::NotRefungibleDataUsedToMintFungibleCollectionToken),
}
}
+/// Implementation of `CommonCollectionOperations` for `RefungibleHandle`. It wraps Refungible Pallete
+/// methods and adds weight info.
impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {
fn create_item(
&self,
@@ -295,46 +319,85 @@
fn set_collection_properties(
&self,
- _sender: T::CrossAccountId,
- _property: Vec<Property>,
+ sender: T::CrossAccountId,
+ properties: Vec<Property>,
) -> DispatchResultWithPostInfo {
- fail!(<Error<T>>::SettingPropertiesNotAllowed)
+ let weight = <CommonWeights<T>>::set_collection_properties(properties.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::set_collection_properties(self, &sender, properties),
+ weight,
+ )
}
fn delete_collection_properties(
&self,
- _sender: &T::CrossAccountId,
- _property_keys: Vec<PropertyKey>,
+ sender: &T::CrossAccountId,
+ property_keys: Vec<PropertyKey>,
) -> DispatchResultWithPostInfo {
- fail!(<Error<T>>::SettingPropertiesNotAllowed)
+ let weight = <CommonWeights<T>>::delete_collection_properties(property_keys.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::delete_collection_properties(self, sender, property_keys),
+ weight,
+ )
}
fn set_token_properties(
&self,
- _sender: T::CrossAccountId,
- _token_id: TokenId,
- _property: Vec<Property>,
- _nesting_budget: &dyn Budget,
+ sender: T::CrossAccountId,
+ token_id: TokenId,
+ properties: Vec<Property>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
- fail!(<Error<T>>::SettingPropertiesNotAllowed)
+ let weight = <CommonWeights<T>>::set_token_properties(properties.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::set_token_properties(
+ self,
+ &sender,
+ token_id,
+ properties.into_iter(),
+ false,
+ nesting_budget,
+ ),
+ weight,
+ )
}
fn set_token_property_permissions(
&self,
- _sender: &T::CrossAccountId,
- _property_permissions: Vec<PropertyKeyPermission>,
+ sender: &T::CrossAccountId,
+ property_permissions: Vec<PropertyKeyPermission>,
) -> DispatchResultWithPostInfo {
- fail!(<Error<T>>::SettingPropertiesNotAllowed)
+ let weight =
+ <CommonWeights<T>>::set_token_property_permissions(property_permissions.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::set_token_property_permissions(self, sender, property_permissions),
+ weight,
+ )
}
fn delete_token_properties(
&self,
- _sender: T::CrossAccountId,
- _token_id: TokenId,
- _property_keys: Vec<PropertyKey>,
- _nesting_budget: &dyn Budget,
+ sender: T::CrossAccountId,
+ token_id: TokenId,
+ property_keys: Vec<PropertyKey>,
+ nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
- fail!(<Error<T>>::SettingPropertiesNotAllowed)
+ let weight = <CommonWeights<T>>::delete_token_properties(property_keys.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::delete_token_properties(
+ self,
+ &sender,
+ token_id,
+ property_keys.into_iter(),
+ nesting_budget,
+ ),
+ weight,
+ )
}
fn check_nesting(
@@ -371,8 +434,13 @@
TokenId(<TokensMinted<T>>::get(self.id))
}
- fn token_owner(&self, _token: TokenId) -> Option<T::CrossAccountId> {
- None
+ fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {
+ <Pallet<T>>::token_owner(self.id, token)
+ }
+
+ /// Returns 10 token in no particular order.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+ <Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
}
fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -14,34 +14,35 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-use up_data_structs::TokenId;
-use pallet_common::erc::CommonEvmHandler;
-use pallet_evm::PrecompileHandle;
+extern crate alloc;
+use evm_coder::{generate_stubgen, solidity_interface, types::*};
-use crate::{Config, RefungibleHandle};
+use pallet_common::{CollectionHandle, erc::CollectionCall, erc::CommonEvmHandler};
-impl<T: Config> CommonEvmHandler for RefungibleHandle<T> {
- const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");
+use pallet_evm::PrecompileHandle;
+use pallet_evm_coder_substrate::call;
- fn call(
- self,
- _handle: &mut impl PrecompileHandle,
- ) -> Option<pallet_common::erc::PrecompileResult> {
- // TODO: Implement RFT variant of ERC721
- None
- }
-}
+use crate::{Config, RefungibleHandle};
-pub struct RefungibleTokenHandle<T: Config>(pub RefungibleHandle<T>, pub TokenId);
+#[solidity_interface(
+ name = "UniqueRFT",
+ is(via("CollectionHandle<T>", common_mut, Collection),)
+)]
+impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> {}
-impl<T: Config> CommonEvmHandler for RefungibleTokenHandle<T> {
- const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw");
+// Not a tests, but code generators
+generate_stubgen!(gen_impl, UniqueRFTCall<()>, true);
+generate_stubgen!(gen_iface, UniqueRFTCall<()>, false);
+impl<T: Config> CommonEvmHandler for RefungibleHandle<T>
+where
+ T::AccountId: From<[u8; 32]>,
+{
+ const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");
fn call(
self,
- _handle: &mut impl PrecompileHandle,
+ handle: &mut impl PrecompileHandle,
) -> Option<pallet_common::erc::PrecompileResult> {
- // TODO: Implement RFT variant of ERC20
- None
+ call::<T, UniqueRFTCall<T>, _, _>(handle, self)
}
}
pallets/refungible/src/erc_token.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/refungible/src/erc_token.rs
@@ -0,0 +1,257 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! # Refungible Pallet EVM API for token pieces
+//!
+//! Provides ERC-20 standart support implementation and EVM API for unique extensions for Refungible Pallet.
+//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.
+
+extern crate alloc;
+use core::{
+ char::{REPLACEMENT_CHARACTER, decode_utf16},
+ convert::TryInto,
+ ops::Deref,
+};
+use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
+use pallet_common::{
+ CommonWeightInfo,
+ erc::{CommonEvmHandler, PrecompileResult},
+};
+use pallet_evm::{account::CrossAccountId, PrecompileHandle};
+use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder};
+use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
+use sp_std::vec::Vec;
+use up_data_structs::TokenId;
+
+use crate::{
+ Allowance, Balance, common::CommonWeights, Config, Pallet, RefungibleHandle, SelfWeightOf,
+ weights::WeightInfo, TotalSupply,
+};
+
+pub struct RefungibleTokenHandle<T: Config>(pub RefungibleHandle<T>, pub TokenId);
+
+#[derive(ToLog)]
+pub enum ERC20Events {
+ /// @dev This event is emitted when the amount of tokens (value) is sent
+ /// from the from address to the to address. In the case of minting new
+ /// tokens, the transfer is usually from the 0 address while in the case
+ /// of burning tokens the transfer is to 0.
+ Transfer {
+ #[indexed]
+ from: address,
+ #[indexed]
+ to: address,
+ value: uint256,
+ },
+ /// @dev This event is emitted when the amount of tokens (value) is approved
+ /// by the owner to be used by the spender.
+ Approval {
+ #[indexed]
+ owner: address,
+ #[indexed]
+ spender: address,
+ value: uint256,
+ },
+}
+
+/// @title Standard ERC20 token
+///
+/// @dev Implementation of the basic standard token.
+/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
+#[solidity_interface(name = "ERC20", events(ERC20Events))]
+impl<T: Config> RefungibleTokenHandle<T> {
+ /// @return the name of the token.
+ fn name(&self) -> Result<string> {
+ Ok(decode_utf16(self.name.iter().copied())
+ .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
+ .collect::<string>())
+ }
+
+ /// @return the symbol of the token.
+ fn symbol(&self) -> Result<string> {
+ Ok(string::from_utf8_lossy(&self.token_prefix).into())
+ }
+
+ /// @dev Total number of tokens in existence
+ fn total_supply(&self) -> Result<uint256> {
+ self.consume_store_reads(1)?;
+ Ok(<TotalSupply<T>>::get((self.id, self.1)).into())
+ }
+
+ /// @dev Not supported
+ fn decimals(&self) -> Result<uint8> {
+ // Decimals aren't supported for refungible tokens
+ Ok(0)
+ }
+
+ /// @dev Gets the balance of the specified address.
+ /// @param owner The address to query the balance of.
+ /// @return An uint256 representing the amount owned by the passed address.
+ fn balance_of(&self, owner: address) -> Result<uint256> {
+ self.consume_store_reads(1)?;
+ let owner = T::CrossAccountId::from_eth(owner);
+ let balance = <Balance<T>>::get((self.id, self.1, owner));
+ Ok(balance.into())
+ }
+
+ /// @dev Transfer token for a specified address
+ /// @param to The address to transfer to.
+ /// @param amount The amount to be transferred.
+ #[weight(<CommonWeights<T>>::transfer())]
+ fn transfer(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let to = T::CrossAccountId::from_eth(to);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)
+ .map_err(|_| "transfer error")?;
+ Ok(true)
+ }
+
+ /// @dev Transfer tokens from one address to another
+ /// @param from address The address which you want to send tokens from
+ /// @param to address The address which you want to transfer to
+ /// @param amount uint256 the amount of tokens to be transferred
+ #[weight(<CommonWeights<T>>::transfer_from())]
+ fn transfer_from(
+ &mut self,
+ caller: caller,
+ from: address,
+ to: address,
+ amount: uint256,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = T::CrossAccountId::from_eth(from);
+ let to = T::CrossAccountId::from_eth(to);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::transfer_from(self, &caller, &from, &to, self.1, amount, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
+ /// @dev Approve the passed address to spend the specified amount of tokens on behalf of `msg.sender`.
+ /// Beware that changing an allowance with this method brings the risk that someone may use both the old
+ /// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
+ /// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
+ /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
+ /// @param spender The address which will spend the funds.
+ /// @param amount The amount of tokens to be spent.
+ #[weight(<SelfWeightOf<T>>::approve())]
+ fn approve(&mut self, caller: caller, spender: address, amount: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let spender = T::CrossAccountId::from_eth(spender);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+ <Pallet<T>>::set_allowance(self, &caller, &spender, self.1, amount)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner address The address which owns the funds.
+ /// @param spender address The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ fn allowance(&self, owner: address, spender: address) -> Result<uint256> {
+ self.consume_store_reads(1)?;
+ let owner = T::CrossAccountId::from_eth(owner);
+ let spender = T::CrossAccountId::from_eth(spender);
+
+ Ok(<Allowance<T>>::get((self.id, self.1, owner, spender)).into())
+ }
+}
+
+#[solidity_interface(name = "ERC20UniqueExtensions")]
+impl<T: Config> RefungibleTokenHandle<T> {
+ /// @dev Function that burns an amount of the token of a given account,
+ /// deducting from the sender's allowance for said account.
+ /// @param from The account whose tokens will be burnt.
+ /// @param amount The amount that will be burnt.
+ #[weight(<SelfWeightOf<T>>::burn_from())]
+ fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let from = T::CrossAccountId::from_eth(from);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ <Pallet<T>>::burn_from(self, &caller, &from, self.1, amount, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
+ /// @dev Function that changes total amount of the tokens.
+ /// Throws if `msg.sender` doesn't owns all of the tokens.
+ /// @param amount New total amount of the tokens.
+ #[weight(<SelfWeightOf<T>>::repartition_item())]
+ fn repartition(&mut self, caller: caller, amount: uint256) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let amount = amount.try_into().map_err(|_| "amount overflow")?;
+
+ <Pallet<T>>::repartition(self, &caller, self.1, amount).map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+}
+
+impl<T: Config> RefungibleTokenHandle<T> {
+ pub fn into_inner(self) -> RefungibleHandle<T> {
+ self.0
+ }
+ pub fn common_mut(&mut self) -> &mut RefungibleHandle<T> {
+ &mut self.0
+ }
+}
+
+impl<T: Config> WithRecorder<T> for RefungibleTokenHandle<T> {
+ fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
+ self.0.recorder()
+ }
+ fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {
+ self.0.into_recorder()
+ }
+}
+
+impl<T: Config> Deref for RefungibleTokenHandle<T> {
+ type Target = RefungibleHandle<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+#[solidity_interface(name = "UniqueRefungibleToken", is(ERC20, ERC20UniqueExtensions,))]
+impl<T: Config> RefungibleTokenHandle<T> where T::AccountId: From<[u8; 32]> {}
+
+generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true);
+generate_stubgen!(gen_iface, UniqueRefungibleTokenCall<()>, false);
+
+impl<T: Config> CommonEvmHandler for RefungibleTokenHandle<T>
+where
+ T::AccountId: From<[u8; 32]>,
+{
+ const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungibleToken.raw");
+
+ fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+ call::<T, UniqueRefungibleTokenCall<T>, _, _>(handle, self)
+ }
+}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -14,32 +14,113 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! # Refungible Pallet
+//!
+//! The Refungible pallet provides functionality for handling refungible collections and tokens.
+//!
+//! - [`Config`]
+//! - [`RefungibleHandle`]
+//! - [`Pallet`]
+//! - [`CommonWeights`](common::CommonWeights)
+//!
+//! ## Overview
+//!
+//! The Refungible pallet provides functions for:
+//!
+//! - RFT collection creation and removal
+//! - Minting and burning of RFT tokens
+//! - Partition and repartition of RFT tokens
+//! - Retrieving number of pieces of RFT token
+//! - Retrieving account balances
+//! - Transfering RFT token pieces
+//! - Burning RFT token pieces
+//! - Setting and checking allowance for RFT tokens
+//!
+//! ### Terminology
+//!
+//! - **RFT token:** Non fungible token that was partitioned to pieces. If an account owns all
+//! of the RFT token pieces than it owns the RFT token and can repartition it.
+//!
+//! - **RFT Collection:** A collection of RFT tokens. All RFT tokens are part of a collection.
+//! Each collection has its own settings and set of permissions.
+//!
+//! - **RFT token piece:** A fungible part of an RFT token.
+//!
+//! - **Balance:** RFT token pieces owned by an account
+//!
+//! - **Allowance:** Maximum number of RFT token pieces that one account is allowed to
+//! transfer from the balance of another account
+//!
+//! - **Burning:** The process of “deleting” a token from a collection or removing token pieces from
+//! an account balance.
+//!
+//! ### Implementations
+//!
+//! The Refungible pallet provides implementations for the following traits. If these traits provide
+//! the functionality that you need, then you can avoid coupling with the Refungible pallet.
+//!
+//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight
+//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing
+//! with collections
+//! - [`RefungibleExtensions`](pallet_common::RefungibleExtensions): Functions specific for refungible
+//! collection
+//!
+//! ## Interface
+//!
+//! ### Dispatchable Functions
+//!
+//! - `init_collection` - Create RFT collection. RFT collection can be configured to allow or deny access for
+//! some accounts.
+//! - `destroy_collection` - Destroy exising RFT collection. There should be no tokens in the collection.
+//! - `burn` - Burn some amount of RFT token pieces owned by account. Burns the RFT token if no pieces left.
+//! - `transfer` - Transfer some amount of RFT token pieces. Transfers should be enabled for RFT collection.
+//! Nests the RFT token if RFT token pieces are sent to another token.
+//! - `create_item` - Mint RFT token in collection. Sender should have permission to mint tokens.
+//! - `set_allowance` - Set allowance for another account to transfer balance from sender's account.
+//! - `repartition` - Repartition token to selected number of pieces. Sender should own all existing pieces.
+//!
+//! ## Assumptions
+//!
+//! * Total number of pieces for one token shouldn't exceed `up_data_structs::MAX_REFUNGIBLE_PIECES`.
+//! * Total number of tokens of all types shouldn't be greater than `up_data_structs::MAX_TOKEN_PREFIX_LENGTH`.
+//! * Sender should be in collection's allow list to perform operations on tokens.
+
#![cfg_attr(not(feature = "std"), no_std)]
-use frame_support::{ensure, BoundedVec};
-use up_data_structs::{
- AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,
- CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping, budget::Budget,
+use crate::erc_token::ERC20Events;
+
+use codec::{Encode, Decode, MaxEncodedLen};
+use core::ops::Deref;
+use evm_coder::ToLog;
+use frame_support::{BoundedVec, ensure, fail, storage::with_transaction, transactional};
+use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
+use pallet_evm_coder_substrate::WithRecorder;
+use pallet_common::{
+ CommonCollectionOperations, Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
};
-use pallet_evm::account::CrossAccountId;
-use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};
use pallet_structure::Pallet as PalletStructure;
-use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
+use scale_info::TypeInfo;
+use sp_core::H160;
+use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};
use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};
-use core::ops::Deref;
-use codec::{Encode, Decode, MaxEncodedLen};
-use scale_info::TypeInfo;
+use up_data_structs::{
+ AccessMode, budget::Budget, CollectionId, CreateCollectionData, CreateRefungibleExData,
+ CustomDataLimit, mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, TokenId, Property,
+ PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,
+ TrySetProperty,
+};
pub use pallet::*;
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod common;
pub mod erc;
+pub mod erc_token;
pub mod weights;
pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
-/// Token data, stored independently from other data used to describe it.
-/// Notably contains the token metadata.
+/// Token data, stored independently from other data used to describe it
+/// for the convenience of database access. Notably contains the token metadata.
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]
pub struct ItemData {
@@ -108,6 +189,16 @@
/// Amount of pieces a refungible token is split into.
#[pallet::storage]
+ #[pallet::getter(fn token_properties)]
+ pub type TokenProperties<T: Config> = StorageNMap<
+ Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
+ Value = up_data_structs::Properties,
+ QueryKind = ValueQuery,
+ OnEmpty = up_data_structs::TokenProperties,
+ >;
+
+ /// Total amount of pieces for token
+ #[pallet::storage]
pub type TotalSupply<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
Value = u128,
@@ -138,7 +229,7 @@
QueryKind = ValueQuery,
>;
- /// Amount of pieces of a token owned by an account.
+ /// Amount of token pieces owned by account.
#[pallet::storage]
pub type Balance<T: Config> = StorageNMap<
Key = (
@@ -151,7 +242,7 @@
QueryKind = ValueQuery,
>;
- /// todo:doc
+ /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (
@@ -188,7 +279,11 @@
pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {
self.0
}
+ pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {
+ &mut self.0
+ }
}
+
impl<T: Config> Deref for RefungibleHandle<T> {
type Target = pallet_common::CollectionHandle<T>;
@@ -197,23 +292,75 @@
}
}
+impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {
+ fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
+ self.0.recorder()
+ }
+ fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {
+ self.0.into_recorder()
+ }
+}
+
impl<T: Config> Pallet<T> {
+ /// Get number of RFT tokens in collection
pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {
<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)
}
+
+ /// Check that RFT token exists
+ ///
+ /// - `token`: Token ID.
pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {
<TotalSupply<T>>::contains_key((collection.id, token))
}
+
+ pub fn set_scoped_token_property(
+ collection_id: CollectionId,
+ token_id: TokenId,
+ scope: PropertyScope,
+ property: Property,
+ ) -> DispatchResult {
+ TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {
+ properties.try_scoped_set(scope, property.key, property.value)
+ })
+ .map_err(<CommonError<T>>::from)?;
+
+ Ok(())
+ }
+
+ pub fn set_scoped_token_properties(
+ collection_id: CollectionId,
+ token_id: TokenId,
+ scope: PropertyScope,
+ properties: impl Iterator<Item = Property>,
+ ) -> DispatchResult {
+ TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {
+ stored_properties.try_scoped_set_from_iter(scope, properties)
+ })
+ .map_err(<CommonError<T>>::from)?;
+
+ Ok(())
+ }
}
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
+ /// Create RFT collection
+ ///
+ /// `init_collection` will take non-refundable deposit for collection creation.
+ ///
+ /// - `data`: Contains settings for collection limits and permissions.
pub fn init_collection(
owner: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
) -> Result<CollectionId, DispatchError> {
<PalletCommon<T>>::init_collection(owner, data, false)
}
+
+ /// Destroy RFT collection
+ ///
+ /// `destroy_collection` will throw error if collection contains any tokens.
+ /// Only owner can destroy collection.
pub fn destroy_collection(
collection: RefungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -245,13 +392,17 @@
.is_some()
}
- pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {
+ pub fn burn_token_unchecked(
+ collection: &RefungibleHandle<T>,
+ token_id: TokenId,
+ ) -> DispatchResult {
let burnt = <TokensBurnt<T>>::get(collection.id)
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
<TokensBurnt<T>>::insert(collection.id, burnt);
<TokenData<T>>::remove((collection.id, token_id));
+ <TokenProperties<T>>::remove((collection.id, token_id));
<TotalSupply<T>>::remove((collection.id, token_id));
<Balance<T>>::remove_prefix((collection.id, token_id), None);
<Allowance<T>>::remove_prefix((collection.id, token_id), None);
@@ -259,6 +410,17 @@
Ok(())
}
+ /// Burn RFT token pieces
+ ///
+ /// `burn` will decrease total amount of token pieces and amount owned by sender.
+ /// `burn` can be called even if there are multiple owners of the RFT token.
+ /// If sender wouldn't have any pieces left after `burn` than she will stop being
+ /// one of the owners of the token. If there is no account that owns any pieces of
+ /// the token than token will be burned too.
+ ///
+ /// - `amount`: Amount of token pieces to burn.
+ /// - `token`: Token who's pieces should be burned
+ /// - `collection`: Collection that contains the token
pub fn burn(
collection: &RefungibleHandle<T>,
owner: &T::CrossAccountId,
@@ -286,7 +448,7 @@
<Owned<T>>::remove((collection.id, owner, token));
<PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);
<AccountBalance<T>>::insert((collection.id, owner), account_balance);
- Self::burn_token(collection, token)?;
+ Self::burn_token_unchecked(collection, token)?;
<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
collection.id,
token,
@@ -319,7 +481,18 @@
<Balance<T>>::insert((collection.id, token, owner), balance);
}
<TotalSupply<T>>::insert((collection.id, token), total_supply);
- // TODO: ERC20 transfer event
+
+ <PalletEvm<T>>::deposit_log(
+ ERC20Events::Transfer {
+ from: *owner.as_eth(),
+ to: H160::default(),
+ value: amount.into(),
+ }
+ .to_log(T::EvmTokenAddressMapping::token_to_address(
+ collection.id,
+ token,
+ )),
+ );
<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
collection.id,
token,
@@ -329,6 +502,179 @@
Ok(())
}
+ #[transactional]
+ fn modify_token_properties(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,
+ is_token_create: bool,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResult {
+ let is_collection_admin = || collection.is_owner_or_admin(sender);
+ let is_token_owner = || -> Result<bool, DispatchError> {
+ let balance = collection.balance(sender.clone(), token_id);
+ let total_pieces: u128 =
+ Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);
+ if balance != total_pieces {
+ return Ok(false);
+ }
+
+ let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(
+ sender.clone(),
+ collection.id,
+ token_id,
+ None,
+ nesting_budget,
+ )?;
+
+ Ok(is_bundle_owner)
+ };
+
+ for (key, value) in properties {
+ let permission = <PalletCommon<T>>::property_permissions(collection.id)
+ .get(&key)
+ .cloned()
+ .unwrap_or_else(PropertyPermission::none);
+
+ let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))
+ .get(&key)
+ .is_some();
+
+ match permission {
+ PropertyPermission { mutable: false, .. } if is_property_exists => {
+ return Err(<CommonError<T>>::NoPermission.into());
+ }
+
+ PropertyPermission {
+ collection_admin,
+ token_owner,
+ ..
+ } => {
+ //TODO: investigate threats during public minting.
+ let is_token_create =
+ is_token_create && (collection_admin || token_owner) && value.is_some();
+ if !(is_token_create
+ || (collection_admin && is_collection_admin())
+ || (token_owner && is_token_owner()?))
+ {
+ fail!(<CommonError<T>>::NoPermission);
+ }
+ }
+ }
+
+ match value {
+ Some(value) => {
+ <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
+ properties.try_set(key.clone(), value)
+ })
+ .map_err(<CommonError<T>>::from)?;
+
+ <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(
+ collection.id,
+ token_id,
+ key,
+ ));
+ }
+ None => {
+ <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
+ properties.remove(&key)
+ })
+ .map_err(<CommonError<T>>::from)?;
+
+ <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(
+ collection.id,
+ token_id,
+ key,
+ ));
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn set_token_properties(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ properties: impl Iterator<Item = Property>,
+ is_token_create: bool,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResult {
+ Self::modify_token_properties(
+ collection,
+ sender,
+ token_id,
+ properties.map(|p| (p.key, Some(p.value))),
+ is_token_create,
+ nesting_budget,
+ )
+ }
+
+ pub fn set_token_property(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ property: Property,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResult {
+ let is_token_create = false;
+
+ Self::set_token_properties(
+ collection,
+ sender,
+ token_id,
+ [property].into_iter(),
+ is_token_create,
+ nesting_budget,
+ )
+ }
+
+ pub fn delete_token_properties(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ property_keys: impl Iterator<Item = PropertyKey>,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResult {
+ let is_token_create = false;
+
+ Self::modify_token_properties(
+ collection,
+ sender,
+ token_id,
+ property_keys.into_iter().map(|key| (key, None)),
+ is_token_create,
+ nesting_budget,
+ )
+ }
+
+ pub fn delete_token_property(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ token_id: TokenId,
+ property_key: PropertyKey,
+ nesting_budget: &dyn Budget,
+ ) -> DispatchResult {
+ Self::delete_token_properties(
+ collection,
+ sender,
+ token_id,
+ [property_key].into_iter(),
+ nesting_budget,
+ )
+ }
+
+ /// Transfer RFT token pieces from one account to another.
+ ///
+ /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.
+ ///
+ /// - `from`: Owner of token pieces to transfer.
+ /// - `to`: Recepient of transfered token pieces.
+ /// - `amount`: Amount of token pieces to transfer.
+ /// - `token`: Token whos pieces should be transfered
+ /// - `collection`: Collection that contains the token
pub fn transfer(
collection: &RefungibleHandle<T>,
from: &T::CrossAccountId,
@@ -422,7 +768,17 @@
}
}
- // TODO: ERC20 transfer event
+ <PalletEvm<T>>::deposit_log(
+ ERC20Events::Transfer {
+ from: *from.as_eth(),
+ to: *to.as_eth(),
+ value: amount.into(),
+ }
+ .to_log(T::EvmTokenAddressMapping::token_to_address(
+ collection.id,
+ token,
+ )),
+ );
<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(
collection.id,
token,
@@ -433,6 +789,11 @@
Ok(())
}
+ /// Batched operation to create multiple RFT tokens.
+ ///
+ /// Same as `create_item` but creates multiple tokens.
+ ///
+ /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.
pub fn create_multiple_items(
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -517,34 +878,70 @@
// =========
+ with_transaction(|| {
+ for (i, data) in data.iter().enumerate() {
+ let token_id = first_token_id + i as u32 + 1;
+ <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);
+
+ <TokenData<T>>::insert(
+ (collection.id, token_id),
+ ItemData {
+ const_data: data.const_data.clone(),
+ },
+ );
+
+ for (user, amount) in data.users.iter() {
+ if *amount == 0 {
+ continue;
+ }
+ <Balance<T>>::insert((collection.id, token_id, &user), amount);
+ <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
+ <PalletStructure<T>>::nest_if_sent_to_token_unchecked(
+ user,
+ collection.id,
+ TokenId(token_id),
+ );
+ }
+
+ if let Err(e) = Self::set_token_properties(
+ collection,
+ sender,
+ TokenId(token_id),
+ data.properties.clone().into_iter(),
+ true,
+ nesting_budget,
+ ) {
+ return TransactionOutcome::Rollback(Err(e));
+ }
+ }
+ TransactionOutcome::Commit(Ok(()))
+ })?;
+
<TokensMinted<T>>::insert(collection.id, tokens_minted);
+
for (account, balance) in balances {
<AccountBalance<T>>::insert((collection.id, account), balance);
}
+
for (i, token) in data.into_iter().enumerate() {
let token_id = first_token_id + i as u32 + 1;
- <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);
-
- <TokenData<T>>::insert(
- (collection.id, token_id),
- ItemData {
- const_data: token.const_data,
- },
- );
for (user, amount) in token.users.into_iter() {
if amount == 0 {
continue;
}
- <Balance<T>>::insert((collection.id, token_id, &user), amount);
- <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
- <PalletStructure<T>>::nest_if_sent_to_token_unchecked(
- &user,
- collection.id,
- TokenId(token_id),
+
+ <PalletEvm<T>>::deposit_log(
+ ERC20Events::Transfer {
+ from: H160::default(),
+ to: *user.as_eth(),
+ value: amount.into(),
+ }
+ .to_log(T::EvmTokenAddressMapping::token_to_address(
+ collection.id,
+ TokenId(token_id),
+ )),
);
-
- // TODO: ERC20 transfer event
<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
collection.id,
TokenId(token_id),
@@ -568,7 +965,18 @@
} else {
<Allowance<T>>::insert((collection.id, token, sender, spender), amount);
}
- // TODO: ERC20 approval event
+
+ <PalletEvm<T>>::deposit_log(
+ ERC20Events::Approval {
+ owner: *sender.as_eth(),
+ spender: *spender.as_eth(),
+ value: amount.into(),
+ }
+ .to_log(T::EvmTokenAddressMapping::token_to_address(
+ collection.id,
+ token,
+ )),
+ );
<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
collection.id,
token,
@@ -578,6 +986,9 @@
))
}
+ /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.
+ ///
+ /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.
pub fn set_allowance(
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -646,6 +1057,12 @@
Ok(allowance)
}
+ /// Transfer RFT token pieces from one account to another.
+ ///
+ /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.
+ /// The owner should set allowance for the spender to transfer pieces.
+ ///
+ /// [`transfer`]: struct.Pallet.html#method.transfer
pub fn transfer_from(
collection: &RefungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -667,6 +1084,12 @@
Ok(())
}
+ /// Burn RFT token pieces from the account.
+ ///
+ /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should
+ /// set allowance for the spender to burn pieces
+ ///
+ /// [`burn`]: struct.Pallet.html#method.burn
pub fn burn_from(
collection: &RefungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -687,7 +1110,13 @@
Ok(())
}
- /// Delegated to `create_multiple_items`
+ /// Create RFT token.
+ ///
+ /// The sender should be the owner/admin of the collection or collection should be configured
+ /// to allow public minting.
+ ///
+ /// - `data`: Contains list of users who will become the owners of the token pieces and amount
+ /// of token pieces they will receive.
pub fn create_item(
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -697,6 +1126,13 @@
Self::create_multiple_items(collection, sender, vec![data], nesting_budget)
}
+ /// Repartition RFT token.
+ ///
+ /// `repartition` will set token balance of the sender and total amount of token pieces.
+ /// Sender should own all of the token pieces. `repartition' could be done even if some
+ /// token pieces were burned before.
+ ///
+ /// - `amount`: Total amount of token pieces that the token will have after `repartition`.
pub fn repartition(
collection: &RefungibleHandle<T>,
owner: &T::CrossAccountId,
@@ -709,19 +1145,119 @@
);
ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);
// Ensure user owns all pieces
- let total_supply = <TotalSupply<T>>::get((collection.id, token));
+ let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);
let balance = <Balance<T>>::get((collection.id, token, owner));
ensure!(
- total_supply == balance,
+ total_pieces == balance,
<Error<T>>::RepartitionWhileNotOwningAllPieces
);
<Balance<T>>::insert((collection.id, token, owner), amount);
<TotalSupply<T>>::insert((collection.id, token), amount);
+
+ if amount > total_pieces {
+ let mint_amount = amount - total_pieces;
+ <PalletEvm<T>>::deposit_log(
+ ERC20Events::Transfer {
+ from: H160::default(),
+ to: *owner.as_eth(),
+ value: mint_amount.into(),
+ }
+ .to_log(T::EvmTokenAddressMapping::token_to_address(
+ collection.id,
+ token,
+ )),
+ );
+ <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
+ collection.id,
+ token,
+ owner.clone(),
+ mint_amount,
+ ));
+ } else if total_pieces > amount {
+ let burn_amount = total_pieces - amount;
+ <PalletEvm<T>>::deposit_log(
+ ERC20Events::Transfer {
+ from: *owner.as_eth(),
+ to: H160::default(),
+ value: burn_amount.into(),
+ }
+ .to_log(T::EvmTokenAddressMapping::token_to_address(
+ collection.id,
+ token,
+ )),
+ );
+ <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
+ collection.id,
+ token,
+ owner.clone(),
+ burn_amount,
+ ));
+ }
+
Ok(())
}
+ fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {
+ let mut owner = None;
+ let mut count = 0;
+ for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {
+ count += 1;
+ if count > 1 {
+ return None;
+ }
+ owner = Some(key);
+ }
+ owner
+ }
+
fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {
<TotalSupply<T>>::try_get((collection_id, token_id)).ok()
}
+
+ pub fn set_collection_properties(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ properties: Vec<Property>,
+ ) -> DispatchResult {
+ <PalletCommon<T>>::set_collection_properties(collection, sender, properties)
+ }
+
+ pub fn delete_collection_properties(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ property_keys: Vec<PropertyKey>,
+ ) -> DispatchResult {
+ <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)
+ }
+
+ pub fn set_token_property_permissions(
+ collection: &RefungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ property_permissions: Vec<PropertyKeyPermission>,
+ ) -> DispatchResult {
+ <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)
+ }
+
+ /// Returns 10 token in no particular order.
+ ///
+ /// There is no direct way to get token holders in ascending order,
+ /// since `iter_prefix` returns values in no particular order.
+ /// Therefore, getting the 10 largest holders with a large value of holders
+ /// can lead to impact memory allocation + sorting with `n * log (n)`.
+ pub fn token_owners(
+ collection_id: CollectionId,
+ token: TokenId,
+ ) -> Option<Vec<T::CrossAccountId>> {
+ let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))
+ .map(|(owner, _amount)| owner)
+ .take(10)
+ .collect();
+
+ if res.is_empty() {
+ None
+ } else {
+ Some(res)
+ }
+ }
}
pallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungibleToken.soldiffbeforeafterboth--- /dev/null
+++ b/pallets/refungible/src/stubs/UniqueRefungibleToken.sol
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+contract Dummy {
+ uint8 dummy;
+ string stub_error = "this contract is implemented in native";
+}
+
+contract ERC165 is Dummy {
+ function supportsInterface(bytes4 interfaceID)
+ external
+ view
+ returns (bool)
+ {
+ require(false, stub_error);
+ interfaceID;
+ return true;
+ }
+}
+
+// Inline
+contract ERC20Events {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(
+ address indexed owner,
+ address indexed spender,
+ uint256 value
+ );
+}
+
+// Selector: 942e8b22
+contract ERC20 is Dummy, ERC165, ERC20Events {
+ // Selector: name() 06fdde03
+ function name() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ // Selector: symbol() 95d89b41
+ function symbol() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ // Selector: totalSupply() 18160ddd
+ function totalSupply() public view returns (uint256) {
+ require(false, stub_error);
+ dummy;
+ return 0;
+ }
+
+ // Selector: decimals() 313ce567
+ function decimals() public view returns (uint8) {
+ require(false, stub_error);
+ dummy;
+ return 0;
+ }
+
+ // Selector: balanceOf(address) 70a08231
+ function balanceOf(address owner) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ dummy;
+ return 0;
+ }
+
+ // Selector: transfer(address,uint256) a9059cbb
+ function transfer(address to, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
+ // Selector: transferFrom(address,address,uint256) 23b872dd
+ function transferFrom(
+ address from,
+ address to,
+ uint256 amount
+ ) public returns (bool) {
+ require(false, stub_error);
+ from;
+ to;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
+ // Selector: approve(address,uint256) 095ea7b3
+ function approve(address spender, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ spender;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
+ // Selector: allowance(address,address) dd62ed3e
+ function allowance(address owner, address spender)
+ public
+ view
+ returns (uint256)
+ {
+ require(false, stub_error);
+ owner;
+ spender;
+ dummy;
+ return 0;
+ }
+}
+
+// Selector: ab8deb37
+contract ERC20UniqueExtensions is Dummy, ERC165 {
+ // Selector: burnFrom(address,uint256) 79cc6790
+ function burnFrom(address from, uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ from;
+ amount;
+ dummy = 0;
+ return false;
+ }
+
+ // Selector: repartition(uint256) d2418ca7
+ function repartition(uint256 amount) public returns (bool) {
+ require(false, stub_error);
+ amount;
+ dummy = 0;
+ return false;
+ }
+}
+
+contract UniqueRefungibleToken is Dummy, ERC165, ERC20, ERC20UniqueExtensions {}
pallets/refungible/src/weights.rsdiffbeforeafterboth--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -3,7 +3,7 @@
//! Autogenerated weights for pallet_refungible
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-06-27, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
// Executed Command:
@@ -49,6 +49,9 @@
fn transfer_from_removing() -> Weight;
fn transfer_from_creating_removing() -> Weight;
fn burn_from() -> Weight;
+ fn set_token_property_permissions(b: u32, ) -> Weight;
+ fn set_token_properties(b: u32, ) -> Weight;
+ fn delete_token_properties(b: u32, ) -> Weight;
fn repartition_item() -> Weight;
}
@@ -62,7 +65,7 @@
// Storage: Refungible TokenData (r:0 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn create_item() -> Weight {
- (17_553_000 as Weight)
+ (21_310_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(6 as Weight))
}
@@ -73,9 +76,9 @@
// Storage: Refungible TokenData (r:0 w:4)
// Storage: Refungible Owned (r:0 w:4)
fn create_multiple_items(b: u32, ) -> Weight {
- (10_654_000 as Weight)
- // Standard Error: 1_000
- .saturating_add((5_114_000 as Weight).saturating_mul(b as Weight))
+ (9_552_000 as Weight)
+ // Standard Error: 2_000
+ .saturating_add((7_056_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
.saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
@@ -87,9 +90,9 @@
// Storage: Refungible TokenData (r:0 w:4)
// Storage: Refungible Owned (r:0 w:4)
fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
- (3_587_000 as Weight)
+ (4_857_000 as Weight)
// Standard Error: 2_000
- .saturating_add((7_931_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add((9_838_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
@@ -102,9 +105,9 @@
// Storage: Refungible Balance (r:0 w:4)
// Storage: Refungible Owned (r:0 w:4)
fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
- (1_980_000 as Weight)
+ (11_335_000 as Weight)
// Standard Error: 2_000
- .saturating_add((6_305_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add((6_784_000 as Weight).saturating_mul(b as Weight))
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
.saturating_add(T::DbWeight::get().writes(3 as Weight))
@@ -115,7 +118,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn burn_item_partial() -> Weight {
- (21_010_000 as Weight)
+ (21_239_000 as Weight)
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
@@ -125,14 +128,15 @@
// Storage: Refungible TokensBurnt (r:1 w:1)
// Storage: Refungible TokenData (r:0 w:1)
// Storage: Refungible Owned (r:0 w:1)
+ // Storage: Refungible TokenProperties (r:0 w:1)
fn burn_item_fully() -> Weight {
- (28_413_000 as Weight)
+ (29_426_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
- .saturating_add(T::DbWeight::get().writes(6 as Weight))
+ .saturating_add(T::DbWeight::get().writes(7 as Weight))
}
// Storage: Refungible Balance (r:2 w:2)
fn transfer_normal() -> Weight {
- (17_513_000 as Weight)
+ (17_743_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
@@ -140,7 +144,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_creating() -> Weight {
- (20_469_000 as Weight)
+ (20_699_000 as Weight)
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
@@ -148,7 +152,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_removing() -> Weight {
- (22_472_000 as Weight)
+ (22_833_000 as Weight)
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
@@ -156,21 +160,21 @@
// Storage: Refungible AccountBalance (r:2 w:2)
// Storage: Refungible Owned (r:0 w:2)
fn transfer_creating_removing() -> Weight {
- (24_866_000 as Weight)
+ (24_936_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(6 as Weight))
}
// Storage: Refungible Balance (r:1 w:0)
// Storage: Refungible Allowance (r:0 w:1)
fn approve() -> Weight {
- (13_475_000 as Weight)
+ (13_446_000 as Weight)
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: Refungible Allowance (r:1 w:1)
// Storage: Refungible Balance (r:2 w:2)
fn transfer_from_normal() -> Weight {
- (24_707_000 as Weight)
+ (24_777_000 as Weight)
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(3 as Weight))
}
@@ -179,7 +183,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_from_creating() -> Weight {
- (27_812_000 as Weight)
+ (28_483_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(5 as Weight))
}
@@ -188,7 +192,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_from_removing() -> Weight {
- (29_966_000 as Weight)
+ (29_896_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(5 as Weight))
}
@@ -197,7 +201,7 @@
// Storage: Refungible AccountBalance (r:2 w:2)
// Storage: Refungible Owned (r:0 w:2)
fn transfer_from_creating_removing() -> Weight {
- (31_660_000 as Weight)
+ (32_070_000 as Weight)
.saturating_add(T::DbWeight::get().reads(5 as Weight))
.saturating_add(T::DbWeight::get().writes(7 as Weight))
}
@@ -208,15 +212,42 @@
// Storage: Refungible TokensBurnt (r:1 w:1)
// Storage: Refungible TokenData (r:0 w:1)
// Storage: Refungible Owned (r:0 w:1)
+ // Storage: Refungible TokenProperties (r:0 w:1)
fn burn_from() -> Weight {
- (36_248_000 as Weight)
+ (36_789_000 as Weight)
.saturating_add(T::DbWeight::get().reads(5 as Weight))
- .saturating_add(T::DbWeight::get().writes(7 as Weight))
+ .saturating_add(T::DbWeight::get().writes(8 as Weight))
+ }
+ // Storage: Common CollectionPropertyPermissions (r:1 w:1)
+ fn set_token_property_permissions(b: u32, ) -> Weight {
+ (0 as Weight)
+ // Standard Error: 62_000
+ .saturating_add((15_803_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(T::DbWeight::get().reads(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+ }
+ // Storage: Common CollectionPropertyPermissions (r:1 w:0)
+ // Storage: Refungible TokenProperties (r:1 w:1)
+ fn set_token_properties(b: u32, ) -> Weight {
+ (0 as Weight)
+ // Standard Error: 1_668_000
+ .saturating_add((302_308_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(T::DbWeight::get().reads(2 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+ }
+ // Storage: Common CollectionPropertyPermissions (r:1 w:0)
+ // Storage: Refungible TokenProperties (r:1 w:1)
+ fn delete_token_properties(b: u32, ) -> Weight {
+ (0 as Weight)
+ // Standard Error: 1_619_000
+ .saturating_add((294_574_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(T::DbWeight::get().reads(2 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: Refungible TotalSupply (r:1 w:1)
// Storage: Refungible Balance (r:1 w:1)
fn repartition_item() -> Weight {
- (8_226_000 as Weight)
+ (8_325_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
@@ -231,7 +262,7 @@
// Storage: Refungible TokenData (r:0 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn create_item() -> Weight {
- (17_553_000 as Weight)
+ (21_310_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
}
@@ -242,9 +273,9 @@
// Storage: Refungible TokenData (r:0 w:4)
// Storage: Refungible Owned (r:0 w:4)
fn create_multiple_items(b: u32, ) -> Weight {
- (10_654_000 as Weight)
- // Standard Error: 1_000
- .saturating_add((5_114_000 as Weight).saturating_mul(b as Weight))
+ (9_552_000 as Weight)
+ // Standard Error: 2_000
+ .saturating_add((7_056_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
.saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(b as Weight)))
@@ -256,9 +287,9 @@
// Storage: Refungible TokenData (r:0 w:4)
// Storage: Refungible Owned (r:0 w:4)
fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight {
- (3_587_000 as Weight)
+ (4_857_000 as Weight)
// Standard Error: 2_000
- .saturating_add((7_931_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add((9_838_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
@@ -271,9 +302,9 @@
// Storage: Refungible Balance (r:0 w:4)
// Storage: Refungible Owned (r:0 w:4)
fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight {
- (1_980_000 as Weight)
+ (11_335_000 as Weight)
// Standard Error: 2_000
- .saturating_add((6_305_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add((6_784_000 as Weight).saturating_mul(b as Weight))
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight)))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
@@ -284,7 +315,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn burn_item_partial() -> Weight {
- (21_010_000 as Weight)
+ (21_239_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
@@ -294,14 +325,15 @@
// Storage: Refungible TokensBurnt (r:1 w:1)
// Storage: Refungible TokenData (r:0 w:1)
// Storage: Refungible Owned (r:0 w:1)
+ // Storage: Refungible TokenProperties (r:0 w:1)
fn burn_item_fully() -> Weight {
- (28_413_000 as Weight)
+ (29_426_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
- .saturating_add(RocksDbWeight::get().writes(6 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(7 as Weight))
}
// Storage: Refungible Balance (r:2 w:2)
fn transfer_normal() -> Weight {
- (17_513_000 as Weight)
+ (17_743_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
@@ -309,7 +341,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_creating() -> Weight {
- (20_469_000 as Weight)
+ (20_699_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
@@ -317,7 +349,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_removing() -> Weight {
- (22_472_000 as Weight)
+ (22_833_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
@@ -325,21 +357,21 @@
// Storage: Refungible AccountBalance (r:2 w:2)
// Storage: Refungible Owned (r:0 w:2)
fn transfer_creating_removing() -> Weight {
- (24_866_000 as Weight)
+ (24_936_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(6 as Weight))
}
// Storage: Refungible Balance (r:1 w:0)
// Storage: Refungible Allowance (r:0 w:1)
fn approve() -> Weight {
- (13_475_000 as Weight)
+ (13_446_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
// Storage: Refungible Allowance (r:1 w:1)
// Storage: Refungible Balance (r:2 w:2)
fn transfer_from_normal() -> Weight {
- (24_707_000 as Weight)
+ (24_777_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
@@ -348,7 +380,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_from_creating() -> Weight {
- (27_812_000 as Weight)
+ (28_483_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
}
@@ -357,7 +389,7 @@
// Storage: Refungible AccountBalance (r:1 w:1)
// Storage: Refungible Owned (r:0 w:1)
fn transfer_from_removing() -> Weight {
- (29_966_000 as Weight)
+ (29_896_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(5 as Weight))
}
@@ -366,7 +398,7 @@
// Storage: Refungible AccountBalance (r:2 w:2)
// Storage: Refungible Owned (r:0 w:2)
fn transfer_from_creating_removing() -> Weight {
- (31_660_000 as Weight)
+ (32_070_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
.saturating_add(RocksDbWeight::get().writes(7 as Weight))
}
@@ -377,15 +409,42 @@
// Storage: Refungible TokensBurnt (r:1 w:1)
// Storage: Refungible TokenData (r:0 w:1)
// Storage: Refungible Owned (r:0 w:1)
+ // Storage: Refungible TokenProperties (r:0 w:1)
fn burn_from() -> Weight {
- (36_248_000 as Weight)
+ (36_789_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
- .saturating_add(RocksDbWeight::get().writes(7 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(8 as Weight))
+ }
+ // Storage: Common CollectionPropertyPermissions (r:1 w:1)
+ fn set_token_property_permissions(b: u32, ) -> Weight {
+ (0 as Weight)
+ // Standard Error: 62_000
+ .saturating_add((15_803_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ }
+ // Storage: Common CollectionPropertyPermissions (r:1 w:0)
+ // Storage: Refungible TokenProperties (r:1 w:1)
+ fn set_token_properties(b: u32, ) -> Weight {
+ (0 as Weight)
+ // Standard Error: 1_668_000
+ .saturating_add((302_308_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
+ // Storage: Common CollectionPropertyPermissions (r:1 w:0)
+ // Storage: Refungible TokenProperties (r:1 w:1)
+ fn delete_token_properties(b: u32, ) -> Weight {
+ (0 as Weight)
+ // Standard Error: 1_619_000
+ .saturating_add((294_574_000 as Weight).saturating_mul(b as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ }
// Storage: Refungible TotalSupply (r:1 w:1)
// Storage: Refungible Balance (r:1 w:1)
fn repartition_item() -> Weight {
- (8_226_000 as Weight)
+ (8_325_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
pallets/scheduler/src/lib.rsdiffbeforeafterboth--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -24,7 +24,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// <http://www.apache.org/licenses/LICENSE-2.0>
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -32,27 +32,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! # Schedulerdo_reschedule
+//! # Unique scheduler
+//! A Pallet for scheduling dispatches.
+//!
+//! - [`Config`]
+//! - [`Call`]
+//! - [`Pallet`]
+//!
+//! ## Overview
//!
//! This Pallet exposes capabilities for scheduling dispatches to occur at a
//! specified block number or at a specified period. These scheduled dispatches
-//! may be named or anonymous and may be canceled.
+//! should be named and may be canceled.
//!
-//! **NOTE:** The scheduled calls will be dispatched with the default filter
-//! for the origin: namely `frame_system::Config::BaseCallFilter` for all origin
-//! except root which will get no filter. And not the filter contained in origin
-//! use to call `fn schedule`.
+//! **NOTE:** The unique scheduler is designed for deferred transaction calls by block number.
+//! Any user can book a call of a certain transaction to a specific block number.
+//! Also possible to book a call with a certain frequency.
//!
-//! If a call is scheduled using proxy or whatever mecanism which adds filter,
-//! then those filter will not be used when dispatching the schedule call.
+//! Key differences from the original pallet:
+//! <https://crates.io/crates/pallet-scheduler>
+//! Schedule Id restricted by 16 bytes. Identificator for booked call.
+//! Priority limited by HARD DEADLINE (<= 63). Calls over maximum weight don't include to block.
+//! The maximum weight that may be scheduled per block for any dispatchables of less priority than `schedule::HARD_DEADLINE`.
+//! Maybe_periodic limit is 100 calls. Reserved for future sponsored transaction support.
+//! At 100 calls reserved amount is not so much and this is avoid potential problems with balance locks.
+//! Any account allowed to schedule any calls. Account withdraw implemented through default transaction logic.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
-//! * `schedule` - schedule a dispatch, which may be periodic, to occur at a specified block and
-//! with a specified priority.
-//! * `cancel` - cancel a scheduled dispatch, specified by block number and index.
//! * `schedule_named` - augments the `schedule` interface with an additional `Vec<u8>` parameter
//! that can be used for identification.
//! * `cancel_named` - the named complement to the cancel function.
@@ -258,7 +267,7 @@
/// A Scheduler-Runtime interface for finer payment handling.
pub trait DispatchCall<T: frame_system::Config + Config, SelfContainedSignedInfo> {
- /// Reserve the maximum spendings on a call.
+ /// Reserve (lock) the maximum spendings on a call, calculated from its weight and the repetition count.
fn reserve_balance(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
@@ -266,7 +275,7 @@
count: u32,
) -> Result<(), DispatchError>;
- /// Pay for call dispatch (un-reserve) from the reserved funds, returning the change.
+ /// Unreserve (unlock) a certain amount from the payer's reserved funds, returning the change.
fn pay_for_call(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
@@ -282,7 +291,7 @@
TransactionValidityError,
>;
- /// Release reserved funds.
+ /// Release unspent reserved funds in case of a schedule cancel.
fn cancel_reserve(
id: ScheduledId,
sponsor: <T as frame_system::Config>::AccountId,
@@ -441,6 +450,8 @@
// );
// }
+ // Execute transaction via chain default pipeline
+ // That means dispatch will be processed like any user's extrinsic e.g. transaction fees will be taken
let r = T::CallExecutor::dispatch_call(sender, call.clone());
let mut actual_call_weight: Weight = item_weight;
@@ -485,8 +496,8 @@
Agenda::<T>::append(wake, Some(s));
}
}
+ // Total weight should be 0, because the transaction is already paid for
0
- //total_weight
}
}
pallets/structure/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/structure/src/benchmarking.rs
+++ b/pallets/structure/src/benchmarking.rs
@@ -1,3 +1,19 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
use super::*;
use frame_benchmarking::{benchmarks, account};
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -1,3 +1,56 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+//! # Structure Pallet
+//!
+//! The Structure pallet provides functionality for handling tokens nesting an unnesting.
+//!
+//! - [`Config`]
+//! - [`Pallet`]
+//!
+//! ## Overview
+//!
+//! The Structure pallet provides functions for:
+//!
+//! - Searching for token parents, children and owners. Actual implementation of searching for
+//! parent/child is done by pallets corresponding to token's collection type.
+//! - Nesting and unnesting tokens. Actual implementation of nesting is done by pallets corresponding
+//! to token's collection type.
+//!
+//! ### Terminology
+//!
+//! - **Nesting:** Setting up parent-child relationship between tokens. Nested tokens are inhereting
+//! owner from their parent. There could be multiple levels of nesting. Token couldn't be nested in
+//! it's child token i.e. parent-child relationship graph shouldn't have
+//!
+//! - **Parent:** Token that current token is nested in.
+//!
+//! - **Owner:** Account that owns the token and all nested tokens.
+//!
+//! ## Interface
+//!
+//! ### Available Functions
+//!
+//! - `find_parent` - Find parent of the token. It could be an account or another token.
+//! - `parent_chain` - Find chain of parents of the token.
+//! - `find_topmost_owner` - Find account or token in the end of the chain of parents.
+//! - `check_nesting` - Check if the token could be nested in the other token
+//! - `nest_if_sent_to_token` - Nest the token in the other token
+//! - `unnest_if_nested` - Unnest the token from the other token
+
#![cfg_attr(not(feature = "std"), no_std)]
use pallet_common::CommonCollectionOperations;
@@ -82,6 +135,12 @@
}
impl<T: Config> Pallet<T> {
+ /// Find account owning the `token` or a token that the `token` is nested in.
+ ///
+ /// Returns the enum that have three variants:
+ /// - [`User`](crate::Parent<T>::User): Contains account.
+ /// - [`Token`](crate::Parent<T>::Token): Contains token id and collection id.
+ /// - [`TokenNotFound`](crate::Parent<T>::TokenNotFound): Indicates that parent was not found
pub fn find_parent(
collection: CollectionId,
token: TokenId,
@@ -103,6 +162,11 @@
})
}
+ /// Get the chain of parents of a token in the nesting hierarchy
+ ///
+ /// Returns an iterator of addresses of the owning tokens and the owning account,
+ /// starting from the immediate parent token, ending with the account.
+ /// Returns error if cycle is detected.
pub fn parent_chain(
mut collection: CollectionId,
mut token: TokenId,
@@ -133,6 +197,8 @@
/// Try to dereference address, until finding top level owner
///
/// May return token address if parent token not yet exists
+ ///
+ /// - `budget`: Limit for searching parents in depth.
pub fn find_topmost_owner(
collection: CollectionId,
token: TokenId,
@@ -149,6 +215,10 @@
})
}
+ /// Find the topmost parent and check that assigning `for_nest` token as a child for
+ /// `token` wouldn't create a cycle.
+ ///
+ /// - `budget`: Limit for searching parents in depth.
pub fn get_checked_topmost_owner(
collection: CollectionId,
token: TokenId,
@@ -177,6 +247,10 @@
Err(<Error<T>>::DepthLimit.into())
}
+ /// Burn token and all of it's nested tokens
+ ///
+ /// - `self_budget`: Limit for searching children in depth.
+ /// - `breadth_budget`: Limit of breadth of searching children.
pub fn burn_item_recursively(
from: T::CrossAccountId,
collection: CollectionId,
@@ -190,7 +264,13 @@
dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
}
- /// Check if token indirectly owned by specified user
+ /// Check if `token` indirectly owned by `user`
+ ///
+ /// Returns `true` if `user` is `token`'s owner. Or If token is provided as `user` then
+ /// check that `user` and `token` have same owner.
+ /// Checks that assigning `for_nest` token as a child for `token` wouldn't create a cycle.
+ ///
+ /// - `budget`: Limit for searching parents in depth.
pub fn check_indirectly_owned(
user: T::CrossAccountId,
collection: CollectionId,
@@ -207,6 +287,12 @@
.map(|indirect_owner| indirect_owner == target_parent)
}
+ /// Checks that `under` is valid token and that `token_id` could be nested under it
+ /// and that `from` is `under`'s owner
+ ///
+ /// Returns OK if `under` is not a token
+ ///
+ /// - `nesting_budget`: Limit for searching parents in depth.
pub fn check_nesting(
from: T::CrossAccountId,
under: &T::CrossAccountId,
@@ -219,6 +305,11 @@
})
}
+ /// Nests `token_id` under `under` token
+ ///
+ /// Returns OK if `under` is not a token. Checks that nesting is possible.
+ ///
+ /// - `nesting_budget`: Limit for searching parents in depth.
pub fn nest_if_sent_to_token(
from: T::CrossAccountId,
under: &T::CrossAccountId,
@@ -235,6 +326,9 @@
})
}
+ /// Nests `token_id` under `owner` token
+ ///
+ /// Caller should check that nesting wouldn't cause recursion in nesting
pub fn nest_if_sent_to_token_unchecked(
owner: &T::CrossAccountId,
collection_id: CollectionId,
@@ -245,6 +339,7 @@
});
}
+ /// Unnests `token_id` from `owner`.
pub fn unnest_if_nested(
owner: &T::CrossAccountId,
collection_id: CollectionId,
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
primitives/data-structs/Cargo.tomldiffbeforeafterboth--- a/primitives/data-structs/Cargo.toml
+++ b/primitives/data-structs/Cargo.toml
@@ -6,7 +6,7 @@
license = 'GPLv3'
homepage = "https://unique.network"
repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = '0.1.0'
+version = '0.1.1'
[dependencies]
scale-info = { version = "2.0.1", default-features = false, features = [
primitives/data-structs/Changelog.mddiffbeforeafterboth--- /dev/null
+++ b/primitives/data-structs/Changelog.md
@@ -0,0 +1,3 @@
+### 0.1.1
+---
+* Added fields with properties to `CreateReFungibleData` and `CreateRefungibleExData`.
\ No newline at end of file
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -563,8 +563,14 @@
#[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
#[derivative(Debug(format_with = "bounded::vec_debug"))]
pub const_data: BoundedVec<u8, CustomDataLimit>,
+
/// Number of pieces the RFT is split into
pub pieces: u128,
+
+ /// Key-value pairs used to describe the token as metadata
+ #[cfg_attr(feature = "serde1", serde(with = "bounded::vec_serde"))]
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
+ pub properties: CollectionPropertiesVec,
}
#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
@@ -601,6 +607,8 @@
pub const_data: BoundedVec<u8, CustomDataLimit>,
#[derivative(Debug(format_with = "bounded::map_debug"))]
pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
+ #[derivative(Debug(format_with = "bounded::vec_debug"))]
+ pub properties: CollectionPropertiesVec,
}
/// Explicit item creation data with meta parameters, namely the owner.
primitives/rpc/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/primitives/rpc/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
\ No newline at end of file
primitives/rpc/Cargo.tomldiffbeforeafterboth--- a/primitives/rpc/Cargo.toml
+++ b/primitives/rpc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "up-rpc"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -81,5 +81,6 @@
fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>>;
fn effective_collection_limits(collection_id: CollectionId) -> Result<Option<CollectionLimits>>;
fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Result<Option<u128>>;
+ fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec<CrossAccountId>>;
}
}
runtime/common/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/runtime/common/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.9.25] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
+
+
\ No newline at end of file
runtime/common/src/dispatch.rsdiffbeforeafterboth--- a/runtime/common/src/dispatch.rs
+++ b/runtime/common/src/dispatch.rs
@@ -25,7 +25,9 @@
pub use pallet_common::dispatch::CollectionDispatch;
use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};
use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};
-use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle, erc::RefungibleTokenHandle};
+use pallet_refungible::{
+ Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle,
+};
use up_data_structs::{
CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping,
};
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -40,6 +40,11 @@
fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
dispatch_unique_runtime!(collection.token_owner(token))
}
+
+ fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError> {
+ dispatch_unique_runtime!(collection.token_owners(token))
+ }
+
fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
let budget = up_data_structs::budget::Value::new(10);
runtime/tests/src/tests.rsdiffbeforeafterboth--- a/runtime/tests/src/tests.rs
+++ b/runtime/tests/src/tests.rs
@@ -64,6 +64,12 @@
CreateReFungibleData {
const_data: vec![1, 2, 3].try_into().unwrap(),
pieces: 1023,
+ properties: vec![Property {
+ key: b"test-prop".to_vec().try_into().unwrap(),
+ value: b"test-nft-prop".to_vec().try_into().unwrap(),
+ }]
+ .try_into()
+ .unwrap(),
}
}
tests/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/tests/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## 2022-07-14
+
+### Added
+ - Integrintegration tests of RPC method `token_owners`.
+ - Integrintegration tests of Fungible Pallet.
+
+
\ No newline at end of file
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -80,6 +80,8 @@
"testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts",
"testEthCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createCollection.test.ts",
"testRFT": "mocha --timeout 9999999 -r ts-node/register ./**/refungible.test.ts",
+ "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts",
+ "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts",
"polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
"polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",
"polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
tests/src/createMultipleItems.test.tsdiffbeforeafterboth--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -376,14 +376,26 @@
// ReFungible
const collectionIdReFungible =
await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
- const argsReFungible = [
- {ReFungible: ['1'.repeat(2049), 10]},
- {ReFungible: ['2'.repeat(2049), 10]},
- {ReFungible: ['3'.repeat(2049), 10]},
- ];
- const createMultipleItemsTxFungible = api.tx.unique
- .createMultipleItems(collectionIdReFungible, normalizeAccountId(alice.address), argsReFungible);
- await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTxFungible)).to.be.rejected;
+ {
+ const argsReFungible = [
+ {ReFungible: ['1'.repeat(2049), 10, []]},
+ {ReFungible: ['2'.repeat(2049), 10, []]},
+ {ReFungible: ['3'.repeat(2049), 10, []]},
+ ];
+ const createMultipleItemsTxFungible = api.tx.unique
+ .createMultipleItems(collectionIdReFungible, normalizeAccountId(alice.address), argsReFungible);
+ await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTxFungible)).to.be.rejected;
+ }
+ {
+ const argsReFungible = [
+ {ReFungible: {properties: [{key: 'key', value: 'A'.repeat(32769)}]}},
+ {ReFungible: {properties: [{key: 'key', value: 'B'.repeat(32769)}]}},
+ {ReFungible: {properties: [{key: 'key', value: 'C'.repeat(32769)}]}},
+ ];
+ const createMultipleItemsTxFungible = api.tx.unique
+ .createMultipleItems(collectionIdReFungible, normalizeAccountId(alice.address), argsReFungible);
+ await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTxFungible)).to.be.rejected;
+ }
});
});
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -28,6 +28,154 @@
function burnFrom(address from, uint256 amount) external returns (bool);
}
+// Selector: 7d9262e6
+interface Collection is Dummy, ERC165 {
+ // Set collection property.
+ //
+ // @param key Property key.
+ // @param value Propery value.
+ //
+ // Selector: setCollectionProperty(string,bytes) 2f073f66
+ function setCollectionProperty(string memory key, bytes memory value)
+ external;
+
+ // Delete collection property.
+ //
+ // @param key Property key.
+ //
+ // Selector: deleteCollectionProperty(string) 7b7debce
+ function deleteCollectionProperty(string memory key) external;
+
+ // Get collection property.
+ //
+ // @dev Throws error if key not found.
+ //
+ // @param key Property key.
+ // @return bytes The property corresponding to the key.
+ //
+ // Selector: collectionProperty(string) cf24fd6d
+ function collectionProperty(string memory key)
+ external
+ view
+ returns (bytes memory);
+
+ // Set the sponsor of the collection.
+ //
+ // @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ //
+ // @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.
+ //
+ // Selector: setCollectionSponsor(address) 7623402e
+ function setCollectionSponsor(address sponsor) external;
+
+ // Collection sponsorship confirmation.
+ //
+ // @dev After setting the sponsor for the collection, it must be confirmed with this function.
+ //
+ // Selector: confirmCollectionSponsorship() 3c50e97a
+ function confirmCollectionSponsorship() external;
+
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "accountTokenOwnershipLimit",
+ // "sponsoredDataSize",
+ // "sponsoredDataRateLimit",
+ // "tokenLimit",
+ // "sponsorTransferTimeout",
+ // "sponsorApproveTimeout"
+ // @param value Value of the limit.
+ //
+ // Selector: setCollectionLimit(string,uint32) 6a3841db
+ function setCollectionLimit(string memory limit, uint32 value) external;
+
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "ownerCanTransfer",
+ // "ownerCanDestroy",
+ // "transfersEnabled"
+ // @param value Value of the limit.
+ //
+ // Selector: setCollectionLimit(string,bool) 993b7fba
+ function setCollectionLimit(string memory limit, bool value) external;
+
+ // Get contract address.
+ //
+ // Selector: contractAddress() f6b4dfb4
+ function contractAddress() external view returns (address);
+
+ // Add collection admin by substrate address.
+ // @param new_admin Substrate administrator address.
+ //
+ // Selector: addCollectionAdminSubstrate(uint256) 5730062b
+ function addCollectionAdminSubstrate(uint256 newAdmin) external;
+
+ // Remove collection admin by substrate address.
+ // @param admin Substrate administrator address.
+ //
+ // Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
+ function removeCollectionAdminSubstrate(uint256 admin) external;
+
+ // Add collection admin.
+ // @param new_admin Address of the added administrator.
+ //
+ // Selector: addCollectionAdmin(address) 92e462c7
+ function addCollectionAdmin(address newAdmin) external;
+
+ // Remove collection admin.
+ //
+ // @param new_admin Address of the removed administrator.
+ //
+ // Selector: removeCollectionAdmin(address) fafd7b42
+ function removeCollectionAdmin(address admin) external;
+
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'
+ //
+ // Selector: setCollectionNesting(bool) 112d4586
+ function setCollectionNesting(bool enable) external;
+
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
+ // @param collections Addresses of collections that will be available for nesting.
+ //
+ // Selector: setCollectionNesting(bool,address[]) 64872396
+ function setCollectionNesting(bool enable, address[] memory collections)
+ external;
+
+ // Set the collection access method.
+ // @param mode Access mode
+ // 0 for Normal
+ // 1 for AllowList
+ //
+ // Selector: setCollectionAccess(uint8) 41835d4c
+ function setCollectionAccess(uint8 mode) external;
+
+ // Add the user to the allowed list.
+ //
+ // @param user Address of a trusted user.
+ //
+ // Selector: addToCollectionAllowList(address) 67844fe6
+ function addToCollectionAllowList(address user) external;
+
+ // Remove the user from the allowed list.
+ //
+ // @param user Address of a removed user.
+ //
+ // Selector: removeFromCollectionAllowList(address) 85c51acb
+ function removeFromCollectionAllowList(address user) external;
+
+ // Switch permission for minting.
+ //
+ // @param mode Enable if "true".
+ //
+ // Selector: setCollectionMintMode(bool) 00018e84
+ function setCollectionMintMode(bool mode) external;
+}
+
// Selector: 942e8b22
interface ERC20 is Dummy, ERC165, ERC20Events {
// Selector: name() 06fdde03
@@ -63,39 +211,6 @@
external
view
returns (uint256);
-}
-
-// Selector: c894dc35
-interface Collection is Dummy, ERC165 {
- // Selector: setCollectionProperty(string,bytes) 2f073f66
- function setCollectionProperty(string memory key, bytes memory value)
- external;
-
- // Selector: deleteCollectionProperty(string) 7b7debce
- function deleteCollectionProperty(string memory key) external;
-
- // Throws error if key not found
- //
- // Selector: collectionProperty(string) cf24fd6d
- function collectionProperty(string memory key)
- external
- view
- returns (bytes memory);
-
- // Selector: ethSetSponsor(address) 8f9af356
- function ethSetSponsor(address sponsor) external;
-
- // Selector: ethConfirmSponsorship() a8580d1a
- function ethConfirmSponsorship() external;
-
- // Selector: setLimit(string,uint32) 68db30ca
- function setLimit(string memory limit, uint32 value) external;
-
- // Selector: setLimit(string,bool) ea67e4c2
- function setLimit(string memory limit, bool value) external;
-
- // Selector: contractAddress() f6b4dfb4
- function contractAddress() external view returns (address);
}
interface UniqueFungible is
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -44,6 +44,13 @@
// Selector: 41369377
interface TokenProperties is Dummy, ERC165 {
+ // @notice Set permissions for token property.
+ // @dev Throws error if `msg.sender` is not admin or owner of the collection.
+ // @param key Property key.
+ // @param is_mutable Permission to mutate property.
+ // @param collection_admin Permission to mutate property by collection admin if property is mutable.
+ // @param token_owner Permission to mutate property by token owner if property is mutable.
+ //
// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
function setTokenPropertyPermission(
string memory key,
@@ -52,6 +59,12 @@
bool tokenOwner
) external;
+ // @notice Set token property value.
+ // @dev Throws error if `msg.sender` has no permission to edit the property.
+ // @param tokenId ID of the token.
+ // @param key Property key.
+ // @param value Property value.
+ //
// Selector: setProperty(uint256,string,bytes) 1752d67b
function setProperty(
uint256 tokenId,
@@ -59,10 +72,19 @@
bytes memory value
) external;
+ // @notice Delete token property value.
+ // @dev Throws error if `msg.sender` has no permission to edit the property.
+ // @param tokenId ID of the token.
+ // @param key Property key.
+ //
// Selector: deleteProperty(uint256,string) 066111d1
function deleteProperty(uint256 tokenId, string memory key) external;
- // Throws error if key not found
+ // @notice Get token property value.
+ // @dev Throws error if key not found
+ // @param tokenId ID of the token.
+ // @param key Property key.
+ // @return Property value bytes
//
// Selector: property(uint256,string) 7228c327
function property(uint256 tokenId, string memory key)
@@ -73,19 +95,36 @@
// Selector: 42966c68
interface ERC721Burnable is Dummy, ERC165 {
+ // @notice Burns a specific ERC721 token.
+ // @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ // operator of the current owner.
+ // @param tokenId The NFT to approve
+ //
// Selector: burn(uint256) 42966c68
function burn(uint256 tokenId) external;
}
// Selector: 58800161
interface ERC721 is Dummy, ERC165, ERC721Events {
+ // @notice Count all NFTs assigned to an owner
+ // @dev NFTs assigned to the zero address are considered invalid, and this
+ // function throws for queries about the zero address.
+ // @param owner An address for whom to query the balance
+ // @return The number of NFTs owned by `owner`, possibly zero
+ //
// Selector: balanceOf(address) 70a08231
function balanceOf(address owner) external view returns (uint256);
+ // @notice Find the owner of an NFT
+ // @dev NFTs assigned to zero address are considered invalid, and queries
+ // about them do throw.
+ // @param tokenId The identifier for an NFT
+ // @return The address of the owner of the NFT
+ //
// Selector: ownerOf(uint256) 6352211e
function ownerOf(uint256 tokenId) external view returns (address);
- // Not implemented
+ // @dev Not implemented
//
// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672
function safeTransferFromWithData(
@@ -95,7 +134,7 @@
bytes memory data
) external;
- // Not implemented
+ // @dev Not implemented
//
// Selector: safeTransferFrom(address,address,uint256) 42842e0e
function safeTransferFrom(
@@ -104,6 +143,17 @@
uint256 tokenId
) external;
+ // @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
+ // TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE
+ // THEY MAY BE PERMANENTLY LOST
+ // @dev Throws unless `msg.sender` is the current owner or an authorized
+ // operator for this NFT. Throws if `from` is not the current owner. Throws
+ // if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ // @param from The current owner of the NFT
+ // @param to The new owner
+ // @param tokenId The NFT to transfer
+ // @param _value Not used for an NFT
+ //
// Selector: transferFrom(address,address,uint256) 23b872dd
function transferFrom(
address from,
@@ -111,20 +161,27 @@
uint256 tokenId
) external;
+ // @notice Set or reaffirm the approved address for an NFT
+ // @dev The zero address indicates there is no approved address.
+ // @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
+ // operator of the current owner.
+ // @param approved The new approved NFT controller
+ // @param tokenId The NFT to approve
+ //
// Selector: approve(address,uint256) 095ea7b3
function approve(address approved, uint256 tokenId) external;
- // Not implemented
+ // @dev Not implemented
//
// Selector: setApprovalForAll(address,bool) a22cb465
function setApprovalForAll(address operator, bool approved) external;
- // Not implemented
+ // @dev Not implemented
//
// Selector: getApproved(uint256) 081812fc
function getApproved(uint256 tokenId) external view returns (address);
- // Not implemented
+ // @dev Not implemented
//
// Selector: isApprovedForAll(address,address) e985e9c5
function isApprovedForAll(address owner, address operator)
@@ -135,13 +192,21 @@
// Selector: 5b5e139f
interface ERC721Metadata is Dummy, ERC165 {
+ // @notice A descriptive name for a collection of NFTs in this contract
+ //
// Selector: name() 06fdde03
function name() external view returns (string memory);
+ // @notice An abbreviated name for NFTs in this contract
+ //
// Selector: symbol() 95d89b41
function symbol() external view returns (string memory);
- // Returns token's const_metadata
+ // @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ // @dev Throws if `tokenId` is not a valid NFT. URIs are defined in RFC
+ // 3986. The URI may point to a JSON file that conforms to the "ERC721
+ // Metadata JSON Schema".
+ // @return token's const_metadata
//
// Selector: tokenURI(uint256) c87b56dd
function tokenURI(uint256 tokenId) external view returns (string memory);
@@ -152,14 +217,21 @@
// Selector: mintingFinished() 05d2035b
function mintingFinished() external view returns (bool);
- // `token_id` should be obtained with `next_token_id` method,
- // unlike standard, you can't specify it manually
+ // @notice Function to mint token.
+ // @dev `tokenId` should be obtained with `nextTokenId` method,
+ // unlike standard, you can't specify it manually
+ // @param to The new owner
+ // @param tokenId ID of the minted NFT
//
// Selector: mint(address,uint256) 40c10f19
function mint(address to, uint256 tokenId) external returns (bool);
- // `token_id` should be obtained with `next_token_id` method,
- // unlike standard, you can't specify it manually
+ // @notice Function to mint token with the given tokenUri.
+ // @dev `tokenId` should be obtained with `nextTokenId` method,
+ // unlike standard, you can't specify it manually
+ // @param to The new owner
+ // @param tokenId ID of the minted NFT
+ // @param tokenUri Token URI that would be stored in the NFT properties
//
// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
function mintWithTokenURI(
@@ -168,7 +240,7 @@
string memory tokenUri
) external returns (bool);
- // Not implemented
+ // @dev Not implemented
//
// Selector: finishMinting() 7d64bcb4
function finishMinting() external returns (bool);
@@ -176,10 +248,15 @@
// Selector: 780e9d63
interface ERC721Enumerable is Dummy, ERC165 {
+ // @notice Enumerate valid NFTs
+ // @param index A counter less than `totalSupply()`
+ // @return The token identifier for the `index`th NFT,
+ // (sort order not specified)
+ //
// Selector: tokenByIndex(uint256) 4f6ccce7
function tokenByIndex(uint256 index) external view returns (uint256);
- // Not implemented
+ // @dev Not implemented
//
// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
function tokenOfOwnerByIndex(address owner, uint256 index)
@@ -187,20 +264,38 @@
view
returns (uint256);
+ // @notice Count NFTs tracked by this contract
+ // @return A count of valid NFTs tracked by this contract, where each one of
+ // them has an assigned and queryable owner not equal to the zero address
+ //
// Selector: totalSupply() 18160ddd
function totalSupply() external view returns (uint256);
}
// Selector: 7d9262e6
interface Collection is Dummy, ERC165 {
+ // Set collection property.
+ //
+ // @param key Property key.
+ // @param value Propery value.
+ //
// Selector: setCollectionProperty(string,bytes) 2f073f66
function setCollectionProperty(string memory key, bytes memory value)
external;
+ // Delete collection property.
+ //
+ // @param key Property key.
+ //
// Selector: deleteCollectionProperty(string) 7b7debce
function deleteCollectionProperty(string memory key) external;
- // Throws error if key not found
+ // Get collection property.
+ //
+ // @dev Throws error if key not found.
+ //
+ // @param key Property key.
+ // @return bytes The property corresponding to the key.
//
// Selector: collectionProperty(string) cf24fd6d
function collectionProperty(string memory key)
@@ -208,69 +303,168 @@
view
returns (bytes memory);
+ // Set the sponsor of the collection.
+ //
+ // @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor.
+ //
+ // @param sponsor Address of the sponsor from whose account funds will be debited for operations with the contract.
+ //
// Selector: setCollectionSponsor(address) 7623402e
function setCollectionSponsor(address sponsor) external;
+ // Collection sponsorship confirmation.
+ //
+ // @dev After setting the sponsor for the collection, it must be confirmed with this function.
+ //
// Selector: confirmCollectionSponsorship() 3c50e97a
function confirmCollectionSponsorship() external;
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "accountTokenOwnershipLimit",
+ // "sponsoredDataSize",
+ // "sponsoredDataRateLimit",
+ // "tokenLimit",
+ // "sponsorTransferTimeout",
+ // "sponsorApproveTimeout"
+ // @param value Value of the limit.
+ //
// Selector: setCollectionLimit(string,uint32) 6a3841db
function setCollectionLimit(string memory limit, uint32 value) external;
+ // Set limits for the collection.
+ // @dev Throws error if limit not found.
+ // @param limit Name of the limit. Valid names:
+ // "ownerCanTransfer",
+ // "ownerCanDestroy",
+ // "transfersEnabled"
+ // @param value Value of the limit.
+ //
// Selector: setCollectionLimit(string,bool) 993b7fba
function setCollectionLimit(string memory limit, bool value) external;
+ // Get contract address.
+ //
// Selector: contractAddress() f6b4dfb4
function contractAddress() external view returns (address);
+ // Add collection admin by substrate address.
+ // @param new_admin Substrate administrator address.
+ //
// Selector: addCollectionAdminSubstrate(uint256) 5730062b
- function addCollectionAdminSubstrate(uint256 newAdmin) external view;
+ function addCollectionAdminSubstrate(uint256 newAdmin) external;
+ // Remove collection admin by substrate address.
+ // @param admin Substrate administrator address.
+ //
// Selector: removeCollectionAdminSubstrate(uint256) 4048fcf9
- function removeCollectionAdminSubstrate(uint256 newAdmin) external view;
+ function removeCollectionAdminSubstrate(uint256 admin) external;
+ // Add collection admin.
+ // @param new_admin Address of the added administrator.
+ //
// Selector: addCollectionAdmin(address) 92e462c7
- function addCollectionAdmin(address newAdmin) external view;
+ function addCollectionAdmin(address newAdmin) external;
+ // Remove collection admin.
+ //
+ // @param new_admin Address of the removed administrator.
+ //
// Selector: removeCollectionAdmin(address) fafd7b42
- function removeCollectionAdmin(address admin) external view;
+ function removeCollectionAdmin(address admin) external;
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: 'Owner' else to nesting: 'Disabled'
+ //
// Selector: setCollectionNesting(bool) 112d4586
function setCollectionNesting(bool enable) external;
+ // Toggle accessibility of collection nesting.
+ //
+ // @param enable If "true" degenerates to nesting: {OwnerRestricted: [1, 2, 3]} else to nesting: 'Disabled'
+ // @param collections Addresses of collections that will be available for nesting.
+ //
// Selector: setCollectionNesting(bool,address[]) 64872396
function setCollectionNesting(bool enable, address[] memory collections)
external;
+ // Set the collection access method.
+ // @param mode Access mode
+ // 0 for Normal
+ // 1 for AllowList
+ //
// Selector: setCollectionAccess(uint8) 41835d4c
function setCollectionAccess(uint8 mode) external;
+ // Add the user to the allowed list.
+ //
+ // @param user Address of a trusted user.
+ //
// Selector: addToCollectionAllowList(address) 67844fe6
- function addToCollectionAllowList(address user) external view;
+ function addToCollectionAllowList(address user) external;
+ // Remove the user from the allowed list.
+ //
+ // @param user Address of a removed user.
+ //
// Selector: removeFromCollectionAllowList(address) 85c51acb
- function removeFromCollectionAllowList(address user) external view;
+ function removeFromCollectionAllowList(address user) external;
+ // Switch permission for minting.
+ //
+ // @param mode Enable if "true".
+ //
// Selector: setCollectionMintMode(bool) 00018e84
function setCollectionMintMode(bool mode) external;
}
// Selector: d74d154f
interface ERC721UniqueExtensions is Dummy, ERC165 {
+ // @notice Transfer ownership of an NFT
+ // @dev Throws unless `msg.sender` is the current owner. Throws if `to`
+ // is the zero address. Throws if `tokenId` is not a valid NFT.
+ // @param to The new owner
+ // @param tokenId The NFT to transfer
+ // @param _value Not used for an NFT
+ //
// Selector: transfer(address,uint256) a9059cbb
function transfer(address to, uint256 tokenId) external;
+ // @notice Burns a specific ERC721 token.
+ // @dev Throws unless `msg.sender` is the current owner or an authorized
+ // operator for this NFT. Throws if `from` is not the current owner. Throws
+ // if `to` is the zero address. Throws if `tokenId` is not a valid NFT.
+ // @param from The current owner of the NFT
+ // @param tokenId The NFT to transfer
+ // @param _value Not used for an NFT
+ //
// Selector: burnFrom(address,uint256) 79cc6790
function burnFrom(address from, uint256 tokenId) external;
+ // @notice Returns next free NFT ID.
+ //
// Selector: nextTokenId() 75794a3c
function nextTokenId() external view returns (uint256);
+ // @notice Function to mint multiple tokens.
+ // @dev `tokenIds` should be an array of consecutive numbers and first number
+ // should be obtained with `nextTokenId` method
+ // @param to The new owner
+ // @param tokenIds IDs of the minted NFTs
+ //
// Selector: mintBulk(address,uint256[]) 44a9945e
function mintBulk(address to, uint256[] memory tokenIds)
external
returns (bool);
+ // @notice Function to mint multiple tokens with the given tokenUris.
+ // @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
+ // numbers and first number should be obtained with `nextTokenId` method
+ // @param to The new owner
+ // @param tokens array of pairs of token ID and token URI for minted tokens
+ //
// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
external
tests/src/eth/api/UniqueRefungibleToken.soldiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/api/UniqueRefungibleToken.sol
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+// Common stubs holder
+interface Dummy {
+
+}
+
+interface ERC165 is Dummy {
+ function supportsInterface(bytes4 interfaceID) external view returns (bool);
+}
+
+// Inline
+interface ERC20Events {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(
+ address indexed owner,
+ address indexed spender,
+ uint256 value
+ );
+}
+
+// Selector: 942e8b22
+interface ERC20 is Dummy, ERC165, ERC20Events {
+ // Selector: name() 06fdde03
+ function name() external view returns (string memory);
+
+ // Selector: symbol() 95d89b41
+ function symbol() external view returns (string memory);
+
+ // Selector: totalSupply() 18160ddd
+ function totalSupply() external view returns (uint256);
+
+ // Selector: decimals() 313ce567
+ function decimals() external view returns (uint8);
+
+ // Selector: balanceOf(address) 70a08231
+ function balanceOf(address owner) external view returns (uint256);
+
+ // Selector: transfer(address,uint256) a9059cbb
+ function transfer(address to, uint256 amount) external returns (bool);
+
+ // Selector: transferFrom(address,address,uint256) 23b872dd
+ function transferFrom(
+ address from,
+ address to,
+ uint256 amount
+ ) external returns (bool);
+
+ // Selector: approve(address,uint256) 095ea7b3
+ function approve(address spender, uint256 amount) external returns (bool);
+
+ // Selector: allowance(address,address) dd62ed3e
+ function allowance(address owner, address spender)
+ external
+ view
+ returns (uint256);
+}
+
+// Selector: ab8deb37
+interface ERC20UniqueExtensions is Dummy, ERC165 {
+ // Selector: burnFrom(address,uint256) 79cc6790
+ function burnFrom(address from, uint256 amount) external returns (bool);
+
+ // Selector: repartition(uint256) d2418ca7
+ function repartition(uint256 amount) external returns (bool);
+}
+
+interface UniqueRefungibleToken is
+ Dummy,
+ ERC165,
+ ERC20,
+ ERC20UniqueExtensions
+{}
tests/src/eth/fungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -51,6 +51,33 @@
},
{
"inputs": [
+ { "internalType": "address", "name": "newAdmin", "type": "address" }
+ ],
+ "name": "addCollectionAdmin",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "newAdmin", "type": "uint256" }
+ ],
+ "name": "addCollectionAdminSubstrate",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "user", "type": "address" }
+ ],
+ "name": "addToCollectionAllowList",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "owner", "type": "address" },
{ "internalType": "address", "name": "spender", "type": "address" }
],
@@ -97,6 +124,13 @@
},
{
"inputs": [],
+ "name": "confirmCollectionSponsorship",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
"name": "contractAddress",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
@@ -118,43 +152,51 @@
},
{
"inputs": [],
- "name": "ethConfirmSponsorship",
- "outputs": [],
- "stateMutability": "nonpayable",
+ "name": "name",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
"type": "function"
},
{
"inputs": [
- { "internalType": "address", "name": "sponsor", "type": "address" }
+ { "internalType": "address", "name": "admin", "type": "address" }
],
- "name": "ethSetSponsor",
+ "name": "removeCollectionAdmin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
- "inputs": [],
- "name": "name",
- "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
- "stateMutability": "view",
+ "inputs": [
+ { "internalType": "uint256", "name": "admin", "type": "uint256" }
+ ],
+ "name": "removeCollectionAdminSubstrate",
+ "outputs": [],
+ "stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
- { "internalType": "string", "name": "key", "type": "string" },
- { "internalType": "bytes", "name": "value", "type": "bytes" }
+ { "internalType": "address", "name": "user", "type": "address" }
],
- "name": "setCollectionProperty",
+ "name": "removeFromCollectionAllowList",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
+ "inputs": [{ "internalType": "uint8", "name": "mode", "type": "uint8" }],
+ "name": "setCollectionAccess",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
"inputs": [
{ "internalType": "string", "name": "limit", "type": "string" },
{ "internalType": "uint32", "name": "value", "type": "uint32" }
],
- "name": "setLimit",
+ "name": "setCollectionLimit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
@@ -164,7 +206,54 @@
{ "internalType": "string", "name": "limit", "type": "string" },
{ "internalType": "bool", "name": "value", "type": "bool" }
],
- "name": "setLimit",
+ "name": "setCollectionLimit",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],
+ "name": "setCollectionMintMode",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [{ "internalType": "bool", "name": "enable", "type": "bool" }],
+ "name": "setCollectionNesting",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "bool", "name": "enable", "type": "bool" },
+ {
+ "internalType": "address[]",
+ "name": "collections",
+ "type": "address[]"
+ }
+ ],
+ "name": "setCollectionNesting",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "string", "name": "key", "type": "string" },
+ { "internalType": "bytes", "name": "value", "type": "bytes" }
+ ],
+ "name": "setCollectionProperty",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "sponsor", "type": "address" }
+ ],
+ "name": "setCollectionSponsor",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -86,7 +86,7 @@
],
"name": "addCollectionAdmin",
"outputs": [],
- "stateMutability": "view",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
@@ -95,7 +95,7 @@
],
"name": "addCollectionAdminSubstrate",
"outputs": [],
- "stateMutability": "view",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
@@ -104,7 +104,7 @@
],
"name": "addToCollectionAllowList",
"outputs": [],
- "stateMutability": "view",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
@@ -304,16 +304,16 @@
],
"name": "removeCollectionAdmin",
"outputs": [],
- "stateMutability": "view",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
- { "internalType": "uint256", "name": "newAdmin", "type": "uint256" }
+ { "internalType": "uint256", "name": "admin", "type": "uint256" }
],
"name": "removeCollectionAdminSubstrate",
"outputs": [],
- "stateMutability": "view",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
@@ -322,7 +322,7 @@
],
"name": "removeFromCollectionAllowList",
"outputs": [],
- "stateMutability": "view",
+ "stateMutability": "nonpayable",
"type": "function"
},
{
tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -0,0 +1,458 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE} from '../util/helpers';
+import {createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth} from './util/helpers';
+import reFungibleTokenAbi from './reFungibleTokenAbi.json';
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Refungible token: Information getting', () => {
+ itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+ const totalSupply = await contract.methods.totalSupply().call();
+
+ expect(totalSupply).to.equal('200');
+ });
+
+ itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+ const balance = await contract.methods.balanceOf(caller).call();
+
+ expect(balance).to.equal('200');
+ });
+
+ itWeb3('decimals', async ({api, web3, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS});
+ const decimals = await contract.methods.decimals().call();
+
+ expect(decimals).to.equal('0');
+ });
+});
+
+describe('Refungible: Plain calls', () => {
+ itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+
+ const spender = createEthAccount(web3);
+
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ {
+ const result = await contract.methods.approve(spender, 100).send({from: owner});
+ const events = normalizeEvents(result.events);
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner,
+ spender,
+ value: '100',
+ },
+ },
+ ]);
+ }
+
+ {
+ const allowance = await contract.methods.allowance(owner, spender).call();
+ expect(+allowance).to.equal(100);
+ }
+ });
+
+ itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
+
+ const spender = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, spender);
+
+ const receiver = createEthAccount(web3);
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ await contract.methods.approve(spender, 100).send();
+
+ {
+ const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});
+ const events = normalizeEvents(result.events);
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: receiver,
+ value: '49',
+ },
+ },
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner,
+ spender,
+ value: '51',
+ },
+ },
+ ]);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(receiver).call();
+ expect(+balance).to.equal(49);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(151);
+ }
+ });
+
+ itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
+
+ const receiver = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, receiver);
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ {
+ const result = await contract.methods.transfer(receiver, 50).send({from: owner});
+ const events = normalizeEvents(result.events);
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: receiver,
+ value: '50',
+ },
+ },
+ ]);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(150);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(receiver).call();
+ expect(+balance).to.equal(50);
+ }
+ });
+
+ itWeb3('Can perform repartition()', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner);
+
+ const receiver = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, receiver);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ await contract.methods.repartition(200).send({from: owner});
+ expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(200);
+ await contract.methods.transfer(receiver, 110).send({from: owner});
+ expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(90);
+ expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(110);
+
+ await expect(contract.methods.repartition(80).send({from: owner})).to.eventually.be.rejected;
+
+ await contract.methods.transfer(receiver, 90).send({from: owner});
+ expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(0);
+ expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(200);
+
+ await contract.methods.repartition(150).send({from: receiver});
+ await expect(contract.methods.transfer(owner, 160).send({from: receiver})).to.eventually.be.rejected;
+ expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(150);
+ });
+
+ itWeb3('Can repartition with increased amount', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ const result = await contract.methods.repartition(200).send();
+ const events = normalizeEvents(result.events);
+
+ expect(events).to.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: '0x0000000000000000000000000000000000000000',
+ to: owner,
+ value: '100',
+ },
+ },
+ ]);
+ });
+
+ itWeb3('Can repartition with decreased amount', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ const result = await contract.methods.repartition(50).send();
+ const events = normalizeEvents(result.events);
+ expect(events).to.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: '0x0000000000000000000000000000000000000000',
+ value: '50',
+ },
+ },
+ ]);
+ });
+});
+
+describe('Refungible: Fees', () => {
+ itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const spender = createEthAccount(web3);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, 100).send({from: owner}));
+ expect(cost < BigInt(0.2 * Number(UNIQUE)));
+ });
+
+ itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ await contract.methods.approve(spender, 100).send({from: owner});
+
+ const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));
+ expect(cost < BigInt(0.2 * Number(UNIQUE)));
+ });
+
+ itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+
+ const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);
+ const receiver = createEthAccount(web3);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS});
+
+ const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));
+ expect(cost < BigInt(0.2 * Number(UNIQUE)));
+ });
+});
+
+describe('Refungible: Substrate calls', () => {
+ itWeb3('Events emitted for approve()', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+
+ const receiver = createEthAccount(web3);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ expect(await approve(api, collectionId, tokenId, alice, {Ethereum: receiver}, 100n)).to.be.true;
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner: subToEth(alice.address),
+ spender: receiver,
+ value: '100',
+ },
+ },
+ ]);
+ });
+
+ itWeb3('Events emitted for transferFrom()', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+ const bob = privateKeyWrapper('//Bob');
+
+ const receiver = createEthAccount(web3);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId;
+ expect(await approve(api, collectionId, tokenId, alice, bob.address, 100n)).to.be.true;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ expect(await transferFrom(api, collectionId, tokenId, bob, alice, {Ethereum: receiver}, 51n)).to.be.true;
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: subToEth(alice.address),
+ to: receiver,
+ value: '51',
+ },
+ },
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner: subToEth(alice.address),
+ spender: subToEth(bob.address),
+ value: '49',
+ },
+ },
+ ]);
+ });
+
+ itWeb3('Events emitted for transfer()', async ({web3, api, privateKeyWrapper}) => {
+ const alice = privateKeyWrapper('//Alice');
+
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+
+ const receiver = createEthAccount(web3);
+
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId;
+
+ const address = tokenIdToAddress(collectionId, tokenId);
+ const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ expect(await transfer(api, collectionId, tokenId, alice, {Ethereum: receiver}, 51n)).to.be.true;
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: subToEth(alice.address),
+ to: receiver,
+ value: '51',
+ },
+ },
+ ]);
+ });
+});
tests/src/eth/reFungibleTokenAbi.jsondiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/reFungibleTokenAbi.json
@@ -0,0 +1,158 @@
+[
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "uint256",
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" },
+ { "internalType": "address", "name": "spender", "type": "address" }
+ ],
+ "name": "allowance",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "spender", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "approve",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "owner", "type": "address" }
+ ],
+ "name": "balanceOf",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "from", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "burnFrom",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "repartition",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" }
+ ],
+ "name": "supportsInterface",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transfer",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ { "internalType": "address", "name": "from", "type": "address" },
+ { "internalType": "address", "name": "to", "type": "address" },
+ { "internalType": "uint256", "name": "amount", "type": "uint256" }
+ ],
+ "name": "transferFrom",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+]
tests/src/fungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/fungible.test.ts
@@ -0,0 +1,184 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {default as usingApi} from './substrate/substrate-api';
+import {IKeyringPair} from '@polkadot/types/types';
+import {
+ getBalance,
+ createMultipleItemsExpectSuccess,
+ isTokenExists,
+ getLastTokenId,
+ getAllowance,
+ approve,
+ transferFrom,
+ createCollection,
+ transfer,
+ burnItem,
+ normalizeAccountId,
+ CrossAccountId,
+ createFungibleItemExpectSuccess,
+ U128_MAX,
+ burnFromExpectSuccess,
+} from './util/helpers';
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+
+describe('integration test: Fungible functionality:', () => {
+ before(async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ alice = privateKeyWrapper('//Alice');
+ bob = privateKeyWrapper('//Bob');
+ });
+ });
+
+ it('Create fungible collection and token', async () => {
+ await usingApi(async api => {
+ const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
+ expect(createCollectionResult.success).to.be.true;
+ const collectionId = createCollectionResult.collectionId;
+ const defaultTokenId = await getLastTokenId(api, collectionId);
+ const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
+ const aliceBalance = await getBalance(api, collectionId, alice, aliceTokenId);
+ const itemCountAfter = await getLastTokenId(api, collectionId);
+
+ // What to expect
+ // tslint:disable-next-line:no-unused-expression
+ expect(itemCountAfter).to.be.equal(defaultTokenId);
+ expect(aliceBalance).to.be.equal(U128_MAX);
+ });
+ });
+
+ it('RPC method tokenOnewrs for fungible collection and token', async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+ const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+
+ const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
+ const collectionId = createCollectionResult.collectionId;
+ const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
+
+ await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
+ await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
+
+ for (let i = 0; i < 7; i++) {
+ await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1);
+ }
+
+ const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
+ const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
+ const aliceID = normalizeAccountId(alice);
+ const bobId = normalizeAccountId(bob);
+
+ // What to expect
+ // tslint:disable-next-line:no-unused-expression
+ expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+ expect(owners.length == 10).to.be.true;
+
+ const eleven = privateKeyWrapper('11');
+ expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+ expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ });
+ });
+
+ it('Transfer token', async () => {
+ await usingApi(async api => {
+ const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const tokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address);
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
+ expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+ expect(await transfer(api, collectionId, tokenId, alice, ethAcc, 140n)).to.be.true;
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(300n);
+ expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
+ expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(140n);
+ await expect(transfer(api, collectionId, tokenId, alice, bob, 350n)).to.eventually.be.rejected;
+ });
+ });
+
+ it('Tokens multiple creation', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+
+ const args = [
+ {Fungible: {Value: 500n}},
+ {Fungible: {Value: 400n}},
+ {Fungible: {Value: 300n}},
+ ];
+
+ await createMultipleItemsExpectSuccess(alice, collectionId, args);
+ expect(await getBalance(api, collectionId, alice, 0)).to.be.equal(1200n);
+ });
+ });
+
+ it('Burn some tokens ', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
+ expect(await burnItem(api, alice, collectionId, tokenId, 499n)).to.be.true;
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n);
+ });
+ });
+
+ it('Burn all tokens ', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+ expect(await burnItem(api, alice, collectionId, tokenId, 500n)).to.be.true;
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
+ expect((await api.rpc.unique.totalPieces(collectionId, tokenId)).value.toBigInt()).to.be.equal(0n);
+ });
+ });
+
+ it('Set allowance for token', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+
+ const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 100n}, alice.address));
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+
+ expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+ expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n);
+ expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(0n);
+
+ expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob, 20n)).to.be.true;
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n);
+ expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n);
+ expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n);
+
+ await burnFromExpectSuccess(bob, alice, collectionId, tokenId, 10n);
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(70n);
+ expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(30n);
+ expect(await transferFrom(api, collectionId, tokenId, bob, alice, ethAcc, 10n)).to.be.true;
+ expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(10n);
+ });
+ });
+});
tests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -29,7 +29,13 @@
[key: string]: Codec;
};
common: {
+ /**
+ * Maximum admins per collection.
+ **/
collectionAdminsLimit: u32 & AugmentedConst<ApiType>;
+ /**
+ * Set price to create a collection.
+ **/
collectionCreationPrice: u128 & AugmentedConst<ApiType>;
/**
* Generic const
tests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -285,7 +285,7 @@
**/
FungibleItemsDontHaveData: AugmentedError<ApiType>;
/**
- * Not default id passed as TokenId argument.
+ * Fungible tokens hold no ID, and the default value of TokenId for Fungible collection is 0.
**/
FungibleItemsHaveNoId: AugmentedError<ApiType>;
/**
@@ -596,7 +596,7 @@
};
unique: {
/**
- * Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.
+ * Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].
**/
CollectionDecimalPointLimitExceeded: AugmentedError<ApiType>;
/**
@@ -608,7 +608,7 @@
**/
EmptyArgument: AugmentedError<ApiType>;
/**
- * Repertition is only supported by refungible collection
+ * Repertition is only supported by refungible collection.
**/
RepartitionCalledOnNonRefungibleCollection: AugmentedError<ApiType>;
/**
tests/src/interfaces/augment-api-events.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-events.ts
+++ b/tests/src/interfaces/augment-api-events.ts
@@ -59,114 +59,47 @@
};
common: {
/**
- * Sponsoring allowance was approved.
- *
- * # Arguments
- *
- * * collection_id - todo:doc flesh out
- * * item_id
- * * sender
- * * spender
- * * amount
+ * Amount pieces of token owned by `sender` was approved for `spender`.
**/
Approved: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
/**
* New collection was created
- *
- * # Arguments
- *
- * * collection_id - Globally unique identifier of newly created collection.
- * * mode - [CollectionMode] converted into u8.
- * * account_id - Collection owner.
**/
CollectionCreated: AugmentedEvent<ApiType, [u32, u8, AccountId32]>;
/**
* New collection was destroyed
- *
- * # Arguments
- *
- * * collection_id - Globally unique identifier of collection that has been destroyed.
**/
CollectionDestroyed: AugmentedEvent<ApiType, [u32]>;
/**
- * Collection property was deleted.
- *
- * # Arguments
- *
- * * collection_id - ID of the collection, whose property was just deleted.
- * * property_key - Key of the property that was just deleted.
+ * The property has been deleted.
**/
CollectionPropertyDeleted: AugmentedEvent<ApiType, [u32, Bytes]>;
/**
- * Collection property was added or edited.
- *
- * # Arguments
- *
- * * collection_id - ID of the collection, whose property was just set.
- * * property_key - Key of the property that was just set.
+ * The colletion property has been added or edited.
**/
CollectionPropertySet: AugmentedEvent<ApiType, [u32, Bytes]>;
/**
* New item was created.
- *
- * # Arguments
- *
- * * collection_id - ID of the collection where the item was created.
- * * item_id - ID of the item. Unique within the collection.
- * * recipient - Owner of the newly created item.
- * * amount - The amount of tokens that were created (always 1 for NFT).
**/
ItemCreated: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
/**
* Collection item was burned.
- *
- * # Arguments
- *
- * * collection_id - Identifier of the collection to which the burned NFT belonged.
- * * item_id - Identifier of burned NFT.
- * * owner - Which user has destroyed their tokens.
- * * amount - Amount of tokens that were destroyed (always 1 for NFT).
**/
ItemDestroyed: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
/**
- * Token property permission was added or updated for a collection.
- *
- * # Arguments
- *
- * * collection_id - ID of the collection, whose permissions were just set/updated.
- * * property_key - Key of the property of the set/updated permission.
+ * The token property permission of a collection has been set.
**/
PropertyPermissionSet: AugmentedEvent<ApiType, [u32, Bytes]>;
/**
- * Item property was deleted.
- *
- * # Arguments
- *
- * * collection_id - ID of the collection, whose token's property was just deleted.
- * * item_id - ID of the item, whose property was just deleted.
- * * property_key - Key of the property that was just deleted.
+ * The token property has been deleted.
**/
TokenPropertyDeleted: AugmentedEvent<ApiType, [u32, u32, Bytes]>;
/**
- * Item property was added or edited.
- *
- * # Arguments
- *
- * * collection_id - ID of the collection, whose token's property was just set.
- * * item_id - ID of the item, whose property was just set.
- * * property_key - Key of the property that was just set.
+ * The token property has been added or edited.
**/
TokenPropertySet: AugmentedEvent<ApiType, [u32, u32, Bytes]>;
/**
- * Item was transferred.
- *
- * # Arguments
- *
- * * collection_id - ID of the collection to which the item belongs.
- * * item_id - ID of the item transferred.
- * * sender - Original owner of the item.
- * * recipient - New owner of the item.
- * * amount - Amount of tokens that were transferred (always 1 for NFT).
+ * Item was transferred
**/
Transfer: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;
/**
@@ -569,94 +502,77 @@
* Address was added to the allow list
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
- *
- * * user: Address.
+ * * collection_id: ID of the affected collection.
+ * * user: Address of the added account.
**/
AllowListAddressAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
* Address was removed from the allow list
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
- *
- * * user: Address.
+ * * collection_id: ID of the affected collection.
+ * * user: Address of the removed account.
**/
AllowListAddressRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
* Collection admin was added
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
- *
- * * admin: Admin address.
+ * * collection_id: ID of the affected collection.
+ * * admin: Admin address.
**/
CollectionAdminAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
* Collection admin was removed
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
- *
- * * admin: Admin address.
+ * * collection_id: ID of the affected collection.
+ * * admin: Removed admin address.
**/
CollectionAdminRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
* Collection limits were set
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
+ * * collection_id: ID of the affected collection.
**/
CollectionLimitSet: AugmentedEvent<ApiType, [u32]>;
/**
* Collection owned was changed
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
- *
- * * owner: New owner address.
+ * * collection_id: ID of the affected collection.
+ * * owner: New owner address.
**/
CollectionOwnedChanged: AugmentedEvent<ApiType, [u32, AccountId32]>;
/**
* Collection permissions were set
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
+ * * collection_id: ID of the affected collection.
**/
CollectionPermissionSet: AugmentedEvent<ApiType, [u32]>;
/**
* Collection sponsor was removed
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
+ * * collection_id: ID of the affected collection.
**/
CollectionSponsorRemoved: AugmentedEvent<ApiType, [u32]>;
/**
* Collection sponsor was set
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
- *
- * * owner: New sponsor address.
+ * * collection_id: ID of the affected collection.
+ * * owner: New sponsor address.
**/
CollectionSponsorSet: AugmentedEvent<ApiType, [u32, AccountId32]>;
/**
* New sponsor was confirm
*
* # Arguments
- *
- * * collection_id: Globally unique collection identifier.
- *
- * * sponsor: New sponsor address.
+ * * collection_id: ID of the affected collection.
+ * * sponsor: New sponsor address.
**/
SponsorshipConfirmed: AugmentedEvent<ApiType, [u32, AccountId32]>;
/**
tests/src/interfaces/augment-api-query.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -70,39 +70,39 @@
};
common: {
/**
- * Amount of collection admins
+ * Storage of the amount of collection admins.
**/
adminAmount: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
- * Allowlisted collection users
+ * Allowlisted collection users.
**/
allowlist: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * Collection info
+ * Storage of collection info.
**/
collectionById: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Option<UpDataStructsCollection>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
- * Collection properties
+ * Storage of collection properties.
**/
collectionProperties: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
- * Token permissions of a collection
+ * Storage of token property permissions of a collection.
**/
collectionPropertyPermissions: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<BTreeMap<Bytes, UpDataStructsPropertyPermission>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
- * The number of created collections. Essentially contains the last collection ID.
+ * Storage of the count of created collections. Essentially contains the last collection ID.
**/
createdCollectionCount: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
/**
- * The number of destroyed collections
+ * Storage of the count of deleted collections.
**/
destroyedCollectionCount: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
/**
- * Not used by code, exists only to provide some types to metadata
+ * Not used by code, exists only to provide some types to metadata.
**/
dummyStorageValue: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[UpDataStructsCollectionStats, u32, u32, UpDataStructsTokenChild, PhantomTypeUpDataStructs]>>>, []> & QueryableStorageEntry<ApiType, []>;
/**
- * List of collection admins
+ * List of collection admins.
**/
isAdmin: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
@@ -200,7 +200,7 @@
};
fungible: {
/**
- * todo:doc
+ * Storage for assets delegated to a limited extent to other users.
**/
allowance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<u128>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
@@ -244,11 +244,11 @@
};
nonfungible: {
/**
- * Amount of tokens owned in a collection.
+ * Amount of tokens owned by an account in a collection.
**/
accountBalance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<u32>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * todo:doc
+ * Allowance set by a token owner for another user to perform one of certain transactions on a token.
**/
allowance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<Option<PalletEvmAccountBasicCrossAccountIdRepr>>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
/**
@@ -256,7 +256,15 @@
**/
owned: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: u32 | AnyNumber | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]>;
/**
- * Scoped, auxiliary properties of a token, primarily used for on-chain operations.
+ * Custom data of a token that is serialized to bytes,
+ * primarily reserved for on-chain operations,
+ * normally obscured from the external users.
+ *
+ * Auxiliary properties are slightly different from
+ * usual [`TokenProperties`] due to an unlimited number
+ * and separately stored and written-to key-value pairs.
+ *
+ * Currently used to store RMRK data.
**/
tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
/**
@@ -268,7 +276,7 @@
**/
tokenData: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<Option<PalletNonfungibleItemData>>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
/**
- * Key-value pairs, describing the metadata of a token.
+ * Map of key-value pairs, describing the metadata of a token.
**/
tokenProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
/**
@@ -447,11 +455,11 @@
**/
accountBalance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<u32>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * todo:doc
+ * Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.
**/
allowance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg4: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<u128>, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * Amount of pieces of a token owned by an account.
+ * Amount of token pieces owned by account.
**/
balance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<u128>, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
@@ -463,6 +471,10 @@
**/
tokenData: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<PalletRefungibleItemData>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
/**
+ * Amount of pieces a refungible token is split into.
+ **/
+ tokenProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+ /**
* Amount of tokens burnt in a collection.
**/
tokensBurnt: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
@@ -471,7 +483,7 @@
**/
tokensMinted: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
- * Amount of pieces a refungible token is split into.
+ * Total amount of pieces for token
**/
totalSupply: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<u128>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
/**
tests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -673,6 +673,10 @@
**/
collectionTokens: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<u32>>>;
/**
+ * Get token constant metadata
+ **/
+ constMetadata: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Bytes>>;
+ /**
* Get effective collection limits
**/
effectiveCollectionLimits: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<UpDataStructsCollectionLimits>>>;
@@ -705,6 +709,10 @@
**/
tokenOwner: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<PalletEvmAccountBasicCrossAccountIdRepr>>>;
/**
+ * Returns 10 tokens owners in no particular order
+ **/
+ tokenOwners: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<PalletEvmAccountBasicCrossAccountIdRepr>>>;
+ /**
* Get token properties, optionally limited to the provided keys
**/
tokenProperties: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, propertyKeys?: Vec<Text> | (Text | string)[], at?: Hash | string | Uint8Array) => Observable<Vec<UpDataStructsProperty>>>;
@@ -720,6 +728,10 @@
* Get the amount of distinctive tokens present in a collection
**/
totalSupply: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<u32>>;
+ /**
+ * Get token variable metadata
+ **/
+ variableMetadata: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Bytes>>;
};
web3: {
/**
tests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -730,18 +730,22 @@
};
unique: {
/**
- * Adds an admin of the collection.
- * 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.
+ * Add an admin to a collection.
+ *
+ * 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.
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id - ID of the Collection to add admin for.
- * * new_admin - Address of new admin to add.
+ * * `collection_id`: ID of the Collection to add an admin for.
+ * * `new_admin`: Address of new admin to add.
**/
addCollectionAdmin: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newAdmin: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
@@ -749,61 +753,76 @@
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id.
- * * address.
+ * * `collection_id`: ID of the modified collection.
+ * * `address`: ID of the address to be added to the allowlist.
**/
addToAllowList: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, address: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * Set, change, or remove approved address to transfer the ownership of the NFT.
+ * Allow a non-permissioned address to transfer or burn an item.
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
- * * Current NFT owner
+ * * Collection owner
+ * * Collection admin
+ * * Current item owner
*
* # Arguments
*
- * * approved - Address that is approved to transfer this NFT or zero (if needed to remove approval).
- * * collection_id.
- * * item_id - ID of the item.
+ * * `spender`: Account to be approved to make specific transactions on non-owned tokens.
+ * * `collection_id`: ID of the collection the item belongs to.
+ * * `item_id`: ID of the item transactions on which are now approved.
+ * * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).
+ * Set to 0 to revoke the approval.
**/
approve: AugmentedSubmittable<(spender: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletEvmAccountBasicCrossAccountIdRepr, u32, u32, u128]>;
/**
- * Destroy a concrete instance of NFT on behalf of the owner.
- * See also: [`approve`]
+ * Destroy a token on behalf of the owner as a non-owner account.
+ *
+ * See also: [`approve`][`Pallet::approve`].
+ *
+ * After this method executes, one approval is removed from the total so that
+ * the approved address will not be able to transfer this item again from this owner.
*
* # Permissions
*
- * * Collection Owner.
- * * Collection Admin.
- * * Current NFT Owner.
+ * * Collection owner
+ * * Collection admin
+ * * Current token owner
+ * * Address approved by current item owner
*
* # Arguments
*
- * * collection_id - ID of the collection.
- * * item_id - ID of NFT to burn.
- * * from - The owner of the item from whom it is taken away.
+ * * `from`: The owner of the burning item.
+ * * `collection_id`: ID of the collection to which the item belongs.
+ * * `item_id`: ID of item to burn.
+ * * `value`: Number of pieces to burn.
+ * * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.
+ * * Fungible Mode: The desired number of pieces to burn.
+ * * Re-Fungible Mode: The desired number of pieces to burn.
**/
burnFrom: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, from: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32, u128]>;
/**
- * Destroy a concrete instance of NFT.
+ * Destroy an item.
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
- * * Current NFT Owner
+ * * Collection owner
+ * * Collection admin
+ * * Current item owner
*
* # Arguments
*
- * * collection_id - ID of the collection.
- * * item_id - ID of NFT to burn.
+ * * `collection_id`: ID of the collection to which the item belongs.
+ * * `item_id`: ID of item to burn.
+ * * `value`: Number of pieces of the item to destroy.
+ * * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.
+ * * Fungible Mode: The desired number of pieces to burn.
+ * * Re-Fungible Mode: The desired number of pieces to burn.
**/
burnItem: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, u32, u128]>;
/**
@@ -811,108 +830,126 @@
*
* # Permissions
*
- * * Collection Owner
+ * * Collection owner
*
* # Arguments
*
- * * collection_id.
- * * new_owner.
+ * * `collection_id`: ID of the modified collection.
+ * * `new_owner`: ID of the account that will become the owner.
**/
changeCollectionOwner: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newOwner: AccountId32 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, AccountId32]>;
/**
- * Confirm own sponsorship of a collection.
+ * Confirm own sponsorship of a collection, becoming the sponsor.
*
+ * An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].
+ * Sponsor can pay the fees of a transaction instead of the sender,
+ * but only within specified limits.
+ *
* # Permissions
*
* * Sponsor-to-be
*
* # Arguments
*
- * * collection_id.
+ * * `collection_id`: ID of the collection with the pending sponsor.
**/
confirmSponsorship: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
/**
- * This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.
+ * Create a collection of tokens.
+ *
+ * Each Token may have multiple properties encoded as an array of bytes
+ * of certain length. The initial owner of the collection is set
+ * to the address that signed the transaction and can be changed later.
*
+ * Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.
+ *
* # Permissions
*
- * * Anyone.
+ * * Anyone - becomes the owner of the new collection.
*
* # Arguments
*
- * * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.
- * * collection_description - UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.
- * * token_prefix - UTF-8 string with token prefix.
- * * mode - [CollectionMode] collection type and type dependent data.
+ * * `collection_name`: Wide-character string with collection name
+ * (limit [`MAX_COLLECTION_NAME_LENGTH`]).
+ * * `collection_description`: Wide-character string with collection description
+ * (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).
+ * * `token_prefix`: Byte string containing the token prefix to mark a collection
+ * to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).
+ * * `mode`: Type of items stored in the collection and type dependent data.
**/
createCollection: AugmentedSubmittable<(collectionName: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], collectionDescription: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], tokenPrefix: Bytes | string | Uint8Array, mode: UpDataStructsCollectionMode | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Vec<u16>, Vec<u16>, Bytes, UpDataStructsCollectionMode]>;
/**
* Create a collection with explicit parameters.
- * Prefer it to the deprecated [`created_collection`] method.
*
+ * Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.
+ *
* # Permissions
*
- * * Anyone.
+ * * Anyone - becomes the owner of the new collection.
*
* # Arguments
*
- * * data: explicit create-collection data.
+ * * `data`: Explicit data of a collection used for its creation.
**/
createCollectionEx: AugmentedSubmittable<(data: UpDataStructsCreateCollectionData | { mode?: any; access?: any; name?: any; description?: any; tokenPrefix?: any; pendingSponsor?: any; limits?: any; permissions?: any; tokenPropertyPermissions?: any; properties?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [UpDataStructsCreateCollectionData]>;
/**
- * Create a concrete instance of NFT Collection created with CreateCollection method.
+ * Mint an item within a collection.
+ *
+ * A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
* * Anyone if
* * Allow List is enabled, and
* * Address is added to allow list, and
- * * MintPermission is enabled (see SetMintPermission method)
+ * * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])
*
* # Arguments
*
- * * collection_id - ID of the collection.
- * * owner - Address, initial owner of the NFT.
- * * data - Token data to store on chain.
+ * * `collection_id`: ID of the collection to which an item would belong.
+ * * `owner`: Address of the initial owner of the item.
+ * * `data`: Token data describing the item to store on chain.
**/
createItem: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, owner: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, data: UpDataStructsCreateItemData | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, UpDataStructsCreateItemData]>;
/**
- * Create multiple items in a collection created with CreateCollection method.
+ * Create multiple items within a collection.
+ *
+ * A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
* * Anyone if
* * Allow List is enabled, and
- * * Address is added to allow list, and
- * * MintPermission is enabled (see SetMintPermission method)
+ * * Address is added to the allow list, and
+ * * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])
*
* # Arguments
*
- * * collection_id - ID of the collection.
- * * owner - Address, initial owner of the NFT.
- * * items_data - Array items properties. Each property is an array of bytes itself, see [`create_item`].
+ * * `collection_id`: ID of the collection to which the tokens would belong.
+ * * `owner`: Address of the initial owner of the tokens.
+ * * `items_data`: Vector of data describing each item to be created.
**/
createMultipleItems: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, owner: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, itemsData: Vec<UpDataStructsCreateItemData> | (UpDataStructsCreateItemData | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, Vec<UpDataStructsCreateItemData>]>;
/**
- * Create multiple items inside a collection with explicitly specified initial parameters.
+ * Create multiple items within a collection with explicitly specified initial parameters.
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
* * Anyone if
* * Allow List is enabled, and
* * Address is added to allow list, and
- * * MintPermission is enabled (see SetMintPermission method)
+ * * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])
*
* # Arguments
*
- * * collection_id - ID of the collection.
- * * data - Explicit item creation data.
+ * * `collection_id`: ID of the collection to which the tokens would belong.
+ * * `data`: Explicit item creation data.
**/
createMultipleItemsEx: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, data: UpDataStructsCreateItemExData | { NFT: any } | { Fungible: any } | { RefungibleMultipleItems: any } | { RefungibleMultipleOwners: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsCreateItemExData]>;
/**
@@ -925,63 +962,68 @@
*
* # Arguments
*
- * * collection_id.
- * * property_keys - Vector of keys of the properties to be deleted.
+ * * `collection_id`: ID of the modified collection.
+ * * `property_keys`: Vector of keys of the properties to be deleted.
+ * Keys support Latin letters, `-`, `_`, and `.` as symbols.
**/
deleteCollectionProperties: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, propertyKeys: Vec<Bytes> | (Bytes | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, Vec<Bytes>]>;
/**
- * Delete specified token properties.
+ * Delete specified token properties. Currently properties only work with NFTs.
*
* # Permissions
*
* * Depends on collection's token property permissions and specified property mutability:
- * * Collection Owner
- * * Collection Admin
- * * Token Owner
+ * * Collection owner
+ * * Collection admin
+ * * Token owner
*
* # Arguments
*
- * * collection_id.
- * * token_id.
- * * property_keys - Vector of keys of the properties to be deleted.
+ * * `collection_id`: ID of the collection to which the token belongs.
+ * * `token_id`: ID of the modified token.
+ * * `property_keys`: Vector of keys of the properties to be deleted.
+ * Keys support Latin letters, `-`, `_`, and `.` as symbols.
**/
deleteTokenProperties: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, propertyKeys: Vec<Bytes> | (Bytes | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, u32, Vec<Bytes>]>;
/**
- * Destroy the collection if no tokens exist within.
+ * Destroy a collection if no tokens exist within.
*
* # Permissions
*
- * * Collection Owner
+ * * Collection owner
*
* # Arguments
*
- * * collection_id - collection to destroy.
+ * * `collection_id`: Collection to destroy.
**/
destroyCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
/**
- * 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.
+ * Remove admin of a 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.
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id - ID of the Collection to remove admin for.
- * * account_id - Address of admin to remove.
+ * * `collection_id`: ID of the collection to remove the admin for.
+ * * `account_id`: Address of the admin to remove.
**/
removeCollectionAdmin: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, accountId: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * Switch back to pay-per-own-transaction model.
+ * Remove a collection's a sponsor, making everyone pay for their own transactions.
*
* # Permissions
*
- * * Collection Owner
+ * * Collection owner
*
* # Arguments
*
- * * collection_id.
+ * * `collection_id`: ID of the collection with the sponsor to remove.
**/
removeCollectionSponsor: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
/**
@@ -989,27 +1031,27 @@
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id.
- * * address.
+ * * `collection_id`: ID of the modified collection.
+ * * `address`: ID of the address to be removed from the allowlist.
**/
removeFromAllowList: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, address: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * Re-partition a refungible token, while owning all of its parts.
+ * Re-partition a refungible token, while owning all of its parts/pieces.
*
* # Permissions
*
- * * Token Owner (must own every part)
+ * * Token owner (must own every part)
*
* # Arguments
*
- * * collection_id.
- * * token_id - ID of the RFT.
- * * amount - New number of parts into which the token shall be partitioned.
+ * * `collection_id`: ID of the collection the RFT belongs to.
+ * * `token_id`: ID of the RFT.
+ * * `amount`: New number of parts/pieces into which the token shall be partitioned.
**/
repartition: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, u32, u128]>;
/**
@@ -1017,13 +1059,14 @@
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id.
- * * new_limit - New limits of the collection. They will overwrite the current ones.
+ * * `collection_id`: ID of the modified collection.
+ * * `new_limit`: New limits of the collection. Fields that are not set (None)
+ * will not overwrite the old ones.
**/
setCollectionLimits: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newLimit: UpDataStructsCollectionLimits | { accountTokenOwnershipLimit?: any; sponsoredDataSize?: any; sponsoredDataRateLimit?: any; tokenLimit?: any; sponsorTransferTimeout?: any; sponsorApproveTimeout?: any; ownerCanTransfer?: any; ownerCanDestroy?: any; transfersEnabled?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsCollectionLimits]>;
/**
@@ -1031,13 +1074,14 @@
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id.
- * * new_permission - New permissions of the collection. They will overwrite the current ones.
+ * * `collection_id`: ID of the modified collection.
+ * * `new_permission`: New permissions of the collection. Fields that are not set (None)
+ * will not overwrite the old ones.
**/
setCollectionPermissions: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newPermission: UpDataStructsCollectionPermissions | { access?: any; mintMode?: any; nesting?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, UpDataStructsCollectionPermissions]>;
/**
@@ -1045,71 +1089,82 @@
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id.
- * * properties - Vector of key-value pairs stored as the collection's metadata. Keys support Latin letters, `-`, `_`, and `.` as symbols.
+ * * `collection_id`: ID of the modified collection.
+ * * `properties`: Vector of key-value pairs stored as the collection's metadata.
+ * Keys support Latin letters, `-`, `_`, and `.` as symbols.
**/
setCollectionProperties: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, properties: Vec<UpDataStructsProperty> | (UpDataStructsProperty | { key?: any; value?: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, Vec<UpDataStructsProperty>]>;
/**
- * Set (invite) a new collection sponsor. If successful, confirmation from the sponsor-to-be will be pending.
+ * Set (invite) a new collection sponsor.
+ *
+ * If successful, confirmation from the sponsor-to-be will be pending.
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id.
- * * new_sponsor.
+ * * `collection_id`: ID of the modified collection.
+ * * `new_sponsor`: ID of the account of the sponsor-to-be.
**/
setCollectionSponsor: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, newSponsor: AccountId32 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, AccountId32]>;
/**
* Add or change token properties according to collection's permissions.
+ * Currently properties only work with NFTs.
*
* # Permissions
*
* * Depends on collection's token property permissions and specified property mutability:
- * * Collection Owner
- * * Collection Admin
- * * Token Owner
+ * * Collection owner
+ * * Collection admin
+ * * Token owner
+ *
+ * See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].
*
* # Arguments
*
- * * collection_id.
- * * token_id.
- * * properties - Vector of key-value pairs stored as the token's metadata. Keys support Latin letters, `-`, `_`, and `.` as symbols.
+ * * `collection_id: ID of the collection to which the token belongs.
+ * * `token_id`: ID of the modified token.
+ * * `properties`: Vector of key-value pairs stored as the token's metadata.
+ * Keys support Latin letters, `-`, `_`, and `.` as symbols.
**/
setTokenProperties: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, properties: Vec<UpDataStructsProperty> | (UpDataStructsProperty | { key?: any; value?: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, u32, Vec<UpDataStructsProperty>]>;
/**
* Add or change token property permissions of a collection.
*
+ * Without a permission for a particular key, a property with that key
+ * cannot be created in a token.
+ *
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
+ * * Collection owner
+ * * Collection admin
*
* # Arguments
*
- * * collection_id.
- * * property_permissions - Vector of permissions for property keys. Keys support Latin letters, `-`, `_`, and `.` as symbols.
+ * * `collection_id`: ID of the modified collection.
+ * * `property_permissions`: Vector of permissions for property keys.
+ * Keys support Latin letters, `-`, `_`, and `.` as symbols.
**/
setTokenPropertyPermissions: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, propertyPermissions: Vec<UpDataStructsPropertyKeyPermission> | (UpDataStructsPropertyKeyPermission | { key?: any; permission?: any } | string | Uint8Array)[]) => SubmittableExtrinsic<ApiType>, [u32, Vec<UpDataStructsPropertyKeyPermission>]>;
/**
- * Set transfers_enabled value for particular collection.
+ * Completely allow or disallow transfers for a particular collection.
*
* # Permissions
*
- * * Collection Owner
+ * * Collection owner
*
* # Arguments
*
- * * collection_id - ID of the collection.
- * * value - New flag value.
+ * * `collection_id`: ID of the collection.
+ * * `value`: New value of the flag, are transfers allowed?
**/
setTransfersEnabledFlag: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array, value: bool | boolean | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32, bool]>;
/**
@@ -1117,44 +1172,50 @@
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
- * * Current NFT owner
+ * * Collection owner
+ * * Collection admin
+ * * Current token owner
*
* # Arguments
*
- * * recipient - Address of token recipient.
- *
- * * collection_id.
- *
- * * item_id - ID of the item
+ * * `recipient`: Address of token recipient.
+ * * `collection_id`: ID of the collection the item belongs to.
+ * * `item_id`: ID of the item.
* * Non-Fungible Mode: Required.
* * Fungible Mode: Ignored.
* * Re-Fungible Mode: Required.
*
- * * value - Amount to transfer.
- * * Non-Fungible Mode: Ignored
- * * Fungible Mode: Must specify transferred amount
- * * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)
+ * * `value`: Amount to transfer.
+ * * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.
+ * * Fungible Mode: The desired number of pieces to transfer.
+ * * Re-Fungible Mode: The desired number of pieces to transfer.
**/
transfer: AugmentedSubmittable<(recipient: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletEvmAccountBasicCrossAccountIdRepr, u32, u32, u128]>;
/**
- * 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.
+ * Change ownership of an item on behalf of the owner as a non-owner account.
+ *
+ * See the [`approve`][`Pallet::approve`] method for additional information.
+ *
+ * After this method executes, one approval is removed from the total so that
+ * the approved address will not be able to transfer this item again from this owner.
*
* # Permissions
*
- * * Collection Owner
- * * Collection Admin
- * * Current NFT owner
- * * Address approved by current NFT owner
+ * * Collection owner
+ * * Collection admin
+ * * Current item owner
+ * * Address approved by current item owner
*
* # Arguments
*
- * * from - Address that currently owns the token.
- * * recipient - Address of the new token-owner-to-be.
- * * collection_id.
- * * item_id - ID of the item to be transferred.
- * * value - Amount to transfer.
+ * * `from`: Address that currently owns the token.
+ * * `recipient`: Address of the new token-owner-to-be.
+ * * `collection_id`: ID of the collection the item.
+ * * `item_id`: ID of the item to be transferred.
+ * * `value`: Amount to transfer.
+ * * Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.
+ * * Fungible Mode: The desired number of pieces to transfer.
+ * * Re-Fungible Mode: The desired number of pieces to transfer.
**/
transferFrom: AugmentedSubmittable<(from: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, recipient: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, collectionId: u32 | AnyNumber | Uint8Array, itemId: u32 | AnyNumber | Uint8Array, value: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u32, u32, u128]>;
/**
tests/src/interfaces/default/types.tsdiffbeforeafterboth--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -2461,12 +2461,14 @@
export interface UpDataStructsCreateReFungibleData extends Struct {
readonly constData: Bytes;
readonly pieces: u128;
+ readonly properties: Vec<UpDataStructsProperty>;
}
/** @name UpDataStructsCreateRefungibleExData */
export interface UpDataStructsCreateRefungibleExData extends Struct {
readonly constData: Bytes;
readonly users: BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>;
+ readonly properties: Vec<UpDataStructsProperty>;
}
/** @name UpDataStructsNestingPermissions */
tests/src/interfaces/lookup.tsdiffbeforeafterboth1// Auto-generated via `yarn polkadot-types-from-defs`, do not edit2/* eslint-disable */34/* eslint-disable sort-keys */56export default {7 /**8 * Lookup2: polkadot_primitives::v2::PersistedValidationData<primitive_types::H256, N>9 **/10 PolkadotPrimitivesV2PersistedValidationData: {11 parentHead: 'Bytes',12 relayParentNumber: 'u32',13 relayParentStorageRoot: 'H256',14 maxPovSize: 'u32'15 },16 /**17 * Lookup9: polkadot_primitives::v2::UpgradeRestriction18 **/19 PolkadotPrimitivesV2UpgradeRestriction: {20 _enum: ['Present']21 },22 /**23 * Lookup10: sp_trie::storage_proof::StorageProof24 **/25 SpTrieStorageProof: {26 trieNodes: 'BTreeSet<Bytes>'27 },28 /**29 * Lookup13: cumulus_pallet_parachain_system::relay_state_snapshot::MessagingStateSnapshot30 **/31 CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot: {32 dmqMqcHead: 'H256',33 relayDispatchQueueSize: '(u32,u32)',34 ingressChannels: 'Vec<(u32,PolkadotPrimitivesV2AbridgedHrmpChannel)>',35 egressChannels: 'Vec<(u32,PolkadotPrimitivesV2AbridgedHrmpChannel)>'36 },37 /**38 * Lookup18: polkadot_primitives::v2::AbridgedHrmpChannel39 **/40 PolkadotPrimitivesV2AbridgedHrmpChannel: {41 maxCapacity: 'u32',42 maxTotalSize: 'u32',43 maxMessageSize: 'u32',44 msgCount: 'u32',45 totalSize: 'u32',46 mqcHead: 'Option<H256>'47 },48 /**49 * Lookup20: polkadot_primitives::v2::AbridgedHostConfiguration50 **/51 PolkadotPrimitivesV2AbridgedHostConfiguration: {52 maxCodeSize: 'u32',53 maxHeadDataSize: 'u32',54 maxUpwardQueueCount: 'u32',55 maxUpwardQueueSize: 'u32',56 maxUpwardMessageSize: 'u32',57 maxUpwardMessageNumPerCandidate: 'u32',58 hrmpMaxMessageNumPerCandidate: 'u32',59 validationUpgradeCooldown: 'u32',60 validationUpgradeDelay: 'u32'61 },62 /**63 * Lookup26: polkadot_core_primitives::OutboundHrmpMessage<polkadot_parachain::primitives::Id>64 **/65 PolkadotCorePrimitivesOutboundHrmpMessage: {66 recipient: 'u32',67 data: 'Bytes'68 },69 /**70 * Lookup28: cumulus_pallet_parachain_system::pallet::Call<T>71 **/72 CumulusPalletParachainSystemCall: {73 _enum: {74 set_validation_data: {75 data: 'CumulusPrimitivesParachainInherentParachainInherentData',76 },77 sudo_send_upward_message: {78 message: 'Bytes',79 },80 authorize_upgrade: {81 codeHash: 'H256',82 },83 enact_authorized_upgrade: {84 code: 'Bytes'85 }86 }87 },88 /**89 * Lookup29: cumulus_primitives_parachain_inherent::ParachainInherentData90 **/91 CumulusPrimitivesParachainInherentParachainInherentData: {92 validationData: 'PolkadotPrimitivesV2PersistedValidationData',93 relayChainState: 'SpTrieStorageProof',94 downwardMessages: 'Vec<PolkadotCorePrimitivesInboundDownwardMessage>',95 horizontalMessages: 'BTreeMap<u32, Vec<PolkadotCorePrimitivesInboundHrmpMessage>>'96 },97 /**98 * Lookup31: polkadot_core_primitives::InboundDownwardMessage<BlockNumber>99 **/100 PolkadotCorePrimitivesInboundDownwardMessage: {101 sentAt: 'u32',102 msg: 'Bytes'103 },104 /**105 * Lookup34: polkadot_core_primitives::InboundHrmpMessage<BlockNumber>106 **/107 PolkadotCorePrimitivesInboundHrmpMessage: {108 sentAt: 'u32',109 data: 'Bytes'110 },111 /**112 * Lookup37: cumulus_pallet_parachain_system::pallet::Event<T>113 **/114 CumulusPalletParachainSystemEvent: {115 _enum: {116 ValidationFunctionStored: 'Null',117 ValidationFunctionApplied: {118 relayChainBlockNum: 'u32',119 },120 ValidationFunctionDiscarded: 'Null',121 UpgradeAuthorized: {122 codeHash: 'H256',123 },124 DownwardMessagesReceived: {125 count: 'u32',126 },127 DownwardMessagesProcessed: {128 weightUsed: 'u64',129 dmqHead: 'H256'130 }131 }132 },133 /**134 * Lookup38: cumulus_pallet_parachain_system::pallet::Error<T>135 **/136 CumulusPalletParachainSystemError: {137 _enum: ['OverlappingUpgrades', 'ProhibitedByPolkadot', 'TooBig', 'ValidationDataNotAvailable', 'HostConfigurationNotAvailable', 'NotScheduled', 'NothingAuthorized', 'Unauthorized']138 },139 /**140 * Lookup41: pallet_balances::AccountData<Balance>141 **/142 PalletBalancesAccountData: {143 free: 'u128',144 reserved: 'u128',145 miscFrozen: 'u128',146 feeFrozen: 'u128'147 },148 /**149 * Lookup43: pallet_balances::BalanceLock<Balance>150 **/151 PalletBalancesBalanceLock: {152 id: '[u8;8]',153 amount: 'u128',154 reasons: 'PalletBalancesReasons'155 },156 /**157 * Lookup45: pallet_balances::Reasons158 **/159 PalletBalancesReasons: {160 _enum: ['Fee', 'Misc', 'All']161 },162 /**163 * Lookup48: pallet_balances::ReserveData<ReserveIdentifier, Balance>164 **/165 PalletBalancesReserveData: {166 id: '[u8;16]',167 amount: 'u128'168 },169 /**170 * Lookup51: pallet_balances::Releases171 **/172 PalletBalancesReleases: {173 _enum: ['V1_0_0', 'V2_0_0']174 },175 /**176 * Lookup52: pallet_balances::pallet::Call<T, I>177 **/178 PalletBalancesCall: {179 _enum: {180 transfer: {181 dest: 'MultiAddress',182 value: 'Compact<u128>',183 },184 set_balance: {185 who: 'MultiAddress',186 newFree: 'Compact<u128>',187 newReserved: 'Compact<u128>',188 },189 force_transfer: {190 source: 'MultiAddress',191 dest: 'MultiAddress',192 value: 'Compact<u128>',193 },194 transfer_keep_alive: {195 dest: 'MultiAddress',196 value: 'Compact<u128>',197 },198 transfer_all: {199 dest: 'MultiAddress',200 keepAlive: 'bool',201 },202 force_unreserve: {203 who: 'MultiAddress',204 amount: 'u128'205 }206 }207 },208 /**209 * Lookup58: pallet_balances::pallet::Event<T, I>210 **/211 PalletBalancesEvent: {212 _enum: {213 Endowed: {214 account: 'AccountId32',215 freeBalance: 'u128',216 },217 DustLost: {218 account: 'AccountId32',219 amount: 'u128',220 },221 Transfer: {222 from: 'AccountId32',223 to: 'AccountId32',224 amount: 'u128',225 },226 BalanceSet: {227 who: 'AccountId32',228 free: 'u128',229 reserved: 'u128',230 },231 Reserved: {232 who: 'AccountId32',233 amount: 'u128',234 },235 Unreserved: {236 who: 'AccountId32',237 amount: 'u128',238 },239 ReserveRepatriated: {240 from: 'AccountId32',241 to: 'AccountId32',242 amount: 'u128',243 destinationStatus: 'FrameSupportTokensMiscBalanceStatus',244 },245 Deposit: {246 who: 'AccountId32',247 amount: 'u128',248 },249 Withdraw: {250 who: 'AccountId32',251 amount: 'u128',252 },253 Slashed: {254 who: 'AccountId32',255 amount: 'u128'256 }257 }258 },259 /**260 * Lookup59: frame_support::traits::tokens::misc::BalanceStatus261 **/262 FrameSupportTokensMiscBalanceStatus: {263 _enum: ['Free', 'Reserved']264 },265 /**266 * Lookup60: pallet_balances::pallet::Error<T, I>267 **/268 PalletBalancesError: {269 _enum: ['VestingBalance', 'LiquidityRestrictions', 'InsufficientBalance', 'ExistentialDeposit', 'KeepAlive', 'ExistingVestingSchedule', 'DeadAccount', 'TooManyReserves']270 },271 /**272 * Lookup63: pallet_timestamp::pallet::Call<T>273 **/274 PalletTimestampCall: {275 _enum: {276 set: {277 now: 'Compact<u64>'278 }279 }280 },281 /**282 * Lookup66: pallet_transaction_payment::Releases283 **/284 PalletTransactionPaymentReleases: {285 _enum: ['V1Ancient', 'V2']286 },287 /**288 * Lookup67: pallet_treasury::Proposal<sp_core::crypto::AccountId32, Balance>289 **/290 PalletTreasuryProposal: {291 proposer: 'AccountId32',292 value: 'u128',293 beneficiary: 'AccountId32',294 bond: 'u128'295 },296 /**297 * Lookup70: pallet_treasury::pallet::Call<T, I>298 **/299 PalletTreasuryCall: {300 _enum: {301 propose_spend: {302 value: 'Compact<u128>',303 beneficiary: 'MultiAddress',304 },305 reject_proposal: {306 proposalId: 'Compact<u32>',307 },308 approve_proposal: {309 proposalId: 'Compact<u32>',310 },311 remove_approval: {312 proposalId: 'Compact<u32>'313 }314 }315 },316 /**317 * Lookup72: pallet_treasury::pallet::Event<T, I>318 **/319 PalletTreasuryEvent: {320 _enum: {321 Proposed: {322 proposalIndex: 'u32',323 },324 Spending: {325 budgetRemaining: 'u128',326 },327 Awarded: {328 proposalIndex: 'u32',329 award: 'u128',330 account: 'AccountId32',331 },332 Rejected: {333 proposalIndex: 'u32',334 slashed: 'u128',335 },336 Burnt: {337 burntFunds: 'u128',338 },339 Rollover: {340 rolloverBalance: 'u128',341 },342 Deposit: {343 value: 'u128'344 }345 }346 },347 /**348 * Lookup75: frame_support::PalletId349 **/350 FrameSupportPalletId: '[u8;8]',351 /**352 * Lookup76: pallet_treasury::pallet::Error<T, I>353 **/354 PalletTreasuryError: {355 _enum: ['InsufficientProposersBalance', 'InvalidIndex', 'TooManyApprovals', 'ProposalNotApproved']356 },357 /**358 * Lookup77: pallet_sudo::pallet::Call<T>359 **/360 PalletSudoCall: {361 _enum: {362 sudo: {363 call: 'Call',364 },365 sudo_unchecked_weight: {366 call: 'Call',367 weight: 'u64',368 },369 set_key: {370 _alias: {371 new_: 'new',372 },373 new_: 'MultiAddress',374 },375 sudo_as: {376 who: 'MultiAddress',377 call: 'Call'378 }379 }380 },381 /**382 * Lookup79: frame_system::pallet::Call<T>383 **/384 FrameSystemCall: {385 _enum: {386 fill_block: {387 ratio: 'Perbill',388 },389 remark: {390 remark: 'Bytes',391 },392 set_heap_pages: {393 pages: 'u64',394 },395 set_code: {396 code: 'Bytes',397 },398 set_code_without_checks: {399 code: 'Bytes',400 },401 set_storage: {402 items: 'Vec<(Bytes,Bytes)>',403 },404 kill_storage: {405 _alias: {406 keys_: 'keys',407 },408 keys_: 'Vec<Bytes>',409 },410 kill_prefix: {411 prefix: 'Bytes',412 subkeys: 'u32',413 },414 remark_with_event: {415 remark: 'Bytes'416 }417 }418 },419 /**420 * Lookup83: orml_vesting::module::Call<T>421 **/422 OrmlVestingModuleCall: {423 _enum: {424 claim: 'Null',425 vested_transfer: {426 dest: 'MultiAddress',427 schedule: 'OrmlVestingVestingSchedule',428 },429 update_vesting_schedules: {430 who: 'MultiAddress',431 vestingSchedules: 'Vec<OrmlVestingVestingSchedule>',432 },433 claim_for: {434 dest: 'MultiAddress'435 }436 }437 },438 /**439 * Lookup84: orml_vesting::VestingSchedule<BlockNumber, Balance>440 **/441 OrmlVestingVestingSchedule: {442 start: 'u32',443 period: 'u32',444 periodCount: 'u32',445 perPeriod: 'Compact<u128>'446 },447 /**448 * Lookup86: cumulus_pallet_xcmp_queue::pallet::Call<T>449 **/450 CumulusPalletXcmpQueueCall: {451 _enum: {452 service_overweight: {453 index: 'u64',454 weightLimit: 'u64',455 },456 suspend_xcm_execution: 'Null',457 resume_xcm_execution: 'Null',458 update_suspend_threshold: {459 _alias: {460 new_: 'new',461 },462 new_: 'u32',463 },464 update_drop_threshold: {465 _alias: {466 new_: 'new',467 },468 new_: 'u32',469 },470 update_resume_threshold: {471 _alias: {472 new_: 'new',473 },474 new_: 'u32',475 },476 update_threshold_weight: {477 _alias: {478 new_: 'new',479 },480 new_: 'u64',481 },482 update_weight_restrict_decay: {483 _alias: {484 new_: 'new',485 },486 new_: 'u64',487 },488 update_xcmp_max_individual_weight: {489 _alias: {490 new_: 'new',491 },492 new_: 'u64'493 }494 }495 },496 /**497 * Lookup87: pallet_xcm::pallet::Call<T>498 **/499 PalletXcmCall: {500 _enum: {501 send: {502 dest: 'XcmVersionedMultiLocation',503 message: 'XcmVersionedXcm',504 },505 teleport_assets: {506 dest: 'XcmVersionedMultiLocation',507 beneficiary: 'XcmVersionedMultiLocation',508 assets: 'XcmVersionedMultiAssets',509 feeAssetItem: 'u32',510 },511 reserve_transfer_assets: {512 dest: 'XcmVersionedMultiLocation',513 beneficiary: 'XcmVersionedMultiLocation',514 assets: 'XcmVersionedMultiAssets',515 feeAssetItem: 'u32',516 },517 execute: {518 message: 'XcmVersionedXcm',519 maxWeight: 'u64',520 },521 force_xcm_version: {522 location: 'XcmV1MultiLocation',523 xcmVersion: 'u32',524 },525 force_default_xcm_version: {526 maybeXcmVersion: 'Option<u32>',527 },528 force_subscribe_version_notify: {529 location: 'XcmVersionedMultiLocation',530 },531 force_unsubscribe_version_notify: {532 location: 'XcmVersionedMultiLocation',533 },534 limited_reserve_transfer_assets: {535 dest: 'XcmVersionedMultiLocation',536 beneficiary: 'XcmVersionedMultiLocation',537 assets: 'XcmVersionedMultiAssets',538 feeAssetItem: 'u32',539 weightLimit: 'XcmV2WeightLimit',540 },541 limited_teleport_assets: {542 dest: 'XcmVersionedMultiLocation',543 beneficiary: 'XcmVersionedMultiLocation',544 assets: 'XcmVersionedMultiAssets',545 feeAssetItem: 'u32',546 weightLimit: 'XcmV2WeightLimit'547 }548 }549 },550 /**551 * Lookup88: xcm::VersionedMultiLocation552 **/553 XcmVersionedMultiLocation: {554 _enum: {555 V0: 'XcmV0MultiLocation',556 V1: 'XcmV1MultiLocation'557 }558 },559 /**560 * Lookup89: xcm::v0::multi_location::MultiLocation561 **/562 XcmV0MultiLocation: {563 _enum: {564 Null: 'Null',565 X1: 'XcmV0Junction',566 X2: '(XcmV0Junction,XcmV0Junction)',567 X3: '(XcmV0Junction,XcmV0Junction,XcmV0Junction)',568 X4: '(XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction)',569 X5: '(XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction)',570 X6: '(XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction)',571 X7: '(XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction)',572 X8: '(XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction,XcmV0Junction)'573 }574 },575 /**576 * Lookup90: xcm::v0::junction::Junction577 **/578 XcmV0Junction: {579 _enum: {580 Parent: 'Null',581 Parachain: 'Compact<u32>',582 AccountId32: {583 network: 'XcmV0JunctionNetworkId',584 id: '[u8;32]',585 },586 AccountIndex64: {587 network: 'XcmV0JunctionNetworkId',588 index: 'Compact<u64>',589 },590 AccountKey20: {591 network: 'XcmV0JunctionNetworkId',592 key: '[u8;20]',593 },594 PalletInstance: 'u8',595 GeneralIndex: 'Compact<u128>',596 GeneralKey: 'Bytes',597 OnlyChild: 'Null',598 Plurality: {599 id: 'XcmV0JunctionBodyId',600 part: 'XcmV0JunctionBodyPart'601 }602 }603 },604 /**605 * Lookup91: xcm::v0::junction::NetworkId606 **/607 XcmV0JunctionNetworkId: {608 _enum: {609 Any: 'Null',610 Named: 'Bytes',611 Polkadot: 'Null',612 Kusama: 'Null'613 }614 },615 /**616 * Lookup92: xcm::v0::junction::BodyId617 **/618 XcmV0JunctionBodyId: {619 _enum: {620 Unit: 'Null',621 Named: 'Bytes',622 Index: 'Compact<u32>',623 Executive: 'Null',624 Technical: 'Null',625 Legislative: 'Null',626 Judicial: 'Null'627 }628 },629 /**630 * Lookup93: xcm::v0::junction::BodyPart631 **/632 XcmV0JunctionBodyPart: {633 _enum: {634 Voice: 'Null',635 Members: {636 count: 'Compact<u32>',637 },638 Fraction: {639 nom: 'Compact<u32>',640 denom: 'Compact<u32>',641 },642 AtLeastProportion: {643 nom: 'Compact<u32>',644 denom: 'Compact<u32>',645 },646 MoreThanProportion: {647 nom: 'Compact<u32>',648 denom: 'Compact<u32>'649 }650 }651 },652 /**653 * Lookup94: xcm::v1::multilocation::MultiLocation654 **/655 XcmV1MultiLocation: {656 parents: 'u8',657 interior: 'XcmV1MultilocationJunctions'658 },659 /**660 * Lookup95: xcm::v1::multilocation::Junctions661 **/662 XcmV1MultilocationJunctions: {663 _enum: {664 Here: 'Null',665 X1: 'XcmV1Junction',666 X2: '(XcmV1Junction,XcmV1Junction)',667 X3: '(XcmV1Junction,XcmV1Junction,XcmV1Junction)',668 X4: '(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)',669 X5: '(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)',670 X6: '(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)',671 X7: '(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)',672 X8: '(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)'673 }674 },675 /**676 * Lookup96: xcm::v1::junction::Junction677 **/678 XcmV1Junction: {679 _enum: {680 Parachain: 'Compact<u32>',681 AccountId32: {682 network: 'XcmV0JunctionNetworkId',683 id: '[u8;32]',684 },685 AccountIndex64: {686 network: 'XcmV0JunctionNetworkId',687 index: 'Compact<u64>',688 },689 AccountKey20: {690 network: 'XcmV0JunctionNetworkId',691 key: '[u8;20]',692 },693 PalletInstance: 'u8',694 GeneralIndex: 'Compact<u128>',695 GeneralKey: 'Bytes',696 OnlyChild: 'Null',697 Plurality: {698 id: 'XcmV0JunctionBodyId',699 part: 'XcmV0JunctionBodyPart'700 }701 }702 },703 /**704 * Lookup97: xcm::VersionedXcm<Call>705 **/706 XcmVersionedXcm: {707 _enum: {708 V0: 'XcmV0Xcm',709 V1: 'XcmV1Xcm',710 V2: 'XcmV2Xcm'711 }712 },713 /**714 * Lookup98: xcm::v0::Xcm<Call>715 **/716 XcmV0Xcm: {717 _enum: {718 WithdrawAsset: {719 assets: 'Vec<XcmV0MultiAsset>',720 effects: 'Vec<XcmV0Order>',721 },722 ReserveAssetDeposit: {723 assets: 'Vec<XcmV0MultiAsset>',724 effects: 'Vec<XcmV0Order>',725 },726 TeleportAsset: {727 assets: 'Vec<XcmV0MultiAsset>',728 effects: 'Vec<XcmV0Order>',729 },730 QueryResponse: {731 queryId: 'Compact<u64>',732 response: 'XcmV0Response',733 },734 TransferAsset: {735 assets: 'Vec<XcmV0MultiAsset>',736 dest: 'XcmV0MultiLocation',737 },738 TransferReserveAsset: {739 assets: 'Vec<XcmV0MultiAsset>',740 dest: 'XcmV0MultiLocation',741 effects: 'Vec<XcmV0Order>',742 },743 Transact: {744 originType: 'XcmV0OriginKind',745 requireWeightAtMost: 'u64',746 call: 'XcmDoubleEncoded',747 },748 HrmpNewChannelOpenRequest: {749 sender: 'Compact<u32>',750 maxMessageSize: 'Compact<u32>',751 maxCapacity: 'Compact<u32>',752 },753 HrmpChannelAccepted: {754 recipient: 'Compact<u32>',755 },756 HrmpChannelClosing: {757 initiator: 'Compact<u32>',758 sender: 'Compact<u32>',759 recipient: 'Compact<u32>',760 },761 RelayedFrom: {762 who: 'XcmV0MultiLocation',763 message: 'XcmV0Xcm'764 }765 }766 },767 /**768 * Lookup100: xcm::v0::multi_asset::MultiAsset769 **/770 XcmV0MultiAsset: {771 _enum: {772 None: 'Null',773 All: 'Null',774 AllFungible: 'Null',775 AllNonFungible: 'Null',776 AllAbstractFungible: {777 id: 'Bytes',778 },779 AllAbstractNonFungible: {780 class: 'Bytes',781 },782 AllConcreteFungible: {783 id: 'XcmV0MultiLocation',784 },785 AllConcreteNonFungible: {786 class: 'XcmV0MultiLocation',787 },788 AbstractFungible: {789 id: 'Bytes',790 amount: 'Compact<u128>',791 },792 AbstractNonFungible: {793 class: 'Bytes',794 instance: 'XcmV1MultiassetAssetInstance',795 },796 ConcreteFungible: {797 id: 'XcmV0MultiLocation',798 amount: 'Compact<u128>',799 },800 ConcreteNonFungible: {801 class: 'XcmV0MultiLocation',802 instance: 'XcmV1MultiassetAssetInstance'803 }804 }805 },806 /**807 * Lookup101: xcm::v1::multiasset::AssetInstance808 **/809 XcmV1MultiassetAssetInstance: {810 _enum: {811 Undefined: 'Null',812 Index: 'Compact<u128>',813 Array4: '[u8;4]',814 Array8: '[u8;8]',815 Array16: '[u8;16]',816 Array32: '[u8;32]',817 Blob: 'Bytes'818 }819 },820 /**821 * Lookup104: xcm::v0::order::Order<Call>822 **/823 XcmV0Order: {824 _enum: {825 Null: 'Null',826 DepositAsset: {827 assets: 'Vec<XcmV0MultiAsset>',828 dest: 'XcmV0MultiLocation',829 },830 DepositReserveAsset: {831 assets: 'Vec<XcmV0MultiAsset>',832 dest: 'XcmV0MultiLocation',833 effects: 'Vec<XcmV0Order>',834 },835 ExchangeAsset: {836 give: 'Vec<XcmV0MultiAsset>',837 receive: 'Vec<XcmV0MultiAsset>',838 },839 InitiateReserveWithdraw: {840 assets: 'Vec<XcmV0MultiAsset>',841 reserve: 'XcmV0MultiLocation',842 effects: 'Vec<XcmV0Order>',843 },844 InitiateTeleport: {845 assets: 'Vec<XcmV0MultiAsset>',846 dest: 'XcmV0MultiLocation',847 effects: 'Vec<XcmV0Order>',848 },849 QueryHolding: {850 queryId: 'Compact<u64>',851 dest: 'XcmV0MultiLocation',852 assets: 'Vec<XcmV0MultiAsset>',853 },854 BuyExecution: {855 fees: 'XcmV0MultiAsset',856 weight: 'u64',857 debt: 'u64',858 haltOnError: 'bool',859 xcm: 'Vec<XcmV0Xcm>'860 }861 }862 },863 /**864 * Lookup106: xcm::v0::Response865 **/866 XcmV0Response: {867 _enum: {868 Assets: 'Vec<XcmV0MultiAsset>'869 }870 },871 /**872 * Lookup107: xcm::v0::OriginKind873 **/874 XcmV0OriginKind: {875 _enum: ['Native', 'SovereignAccount', 'Superuser', 'Xcm']876 },877 /**878 * Lookup108: xcm::double_encoded::DoubleEncoded<T>879 **/880 XcmDoubleEncoded: {881 encoded: 'Bytes'882 },883 /**884 * Lookup109: xcm::v1::Xcm<Call>885 **/886 XcmV1Xcm: {887 _enum: {888 WithdrawAsset: {889 assets: 'XcmV1MultiassetMultiAssets',890 effects: 'Vec<XcmV1Order>',891 },892 ReserveAssetDeposited: {893 assets: 'XcmV1MultiassetMultiAssets',894 effects: 'Vec<XcmV1Order>',895 },896 ReceiveTeleportedAsset: {897 assets: 'XcmV1MultiassetMultiAssets',898 effects: 'Vec<XcmV1Order>',899 },900 QueryResponse: {901 queryId: 'Compact<u64>',902 response: 'XcmV1Response',903 },904 TransferAsset: {905 assets: 'XcmV1MultiassetMultiAssets',906 beneficiary: 'XcmV1MultiLocation',907 },908 TransferReserveAsset: {909 assets: 'XcmV1MultiassetMultiAssets',910 dest: 'XcmV1MultiLocation',911 effects: 'Vec<XcmV1Order>',912 },913 Transact: {914 originType: 'XcmV0OriginKind',915 requireWeightAtMost: 'u64',916 call: 'XcmDoubleEncoded',917 },918 HrmpNewChannelOpenRequest: {919 sender: 'Compact<u32>',920 maxMessageSize: 'Compact<u32>',921 maxCapacity: 'Compact<u32>',922 },923 HrmpChannelAccepted: {924 recipient: 'Compact<u32>',925 },926 HrmpChannelClosing: {927 initiator: 'Compact<u32>',928 sender: 'Compact<u32>',929 recipient: 'Compact<u32>',930 },931 RelayedFrom: {932 who: 'XcmV1MultilocationJunctions',933 message: 'XcmV1Xcm',934 },935 SubscribeVersion: {936 queryId: 'Compact<u64>',937 maxResponseWeight: 'Compact<u64>',938 },939 UnsubscribeVersion: 'Null'940 }941 },942 /**943 * Lookup110: xcm::v1::multiasset::MultiAssets944 **/945 XcmV1MultiassetMultiAssets: 'Vec<XcmV1MultiAsset>',946 /**947 * Lookup112: xcm::v1::multiasset::MultiAsset948 **/949 XcmV1MultiAsset: {950 id: 'XcmV1MultiassetAssetId',951 fun: 'XcmV1MultiassetFungibility'952 },953 /**954 * Lookup113: xcm::v1::multiasset::AssetId955 **/956 XcmV1MultiassetAssetId: {957 _enum: {958 Concrete: 'XcmV1MultiLocation',959 Abstract: 'Bytes'960 }961 },962 /**963 * Lookup114: xcm::v1::multiasset::Fungibility964 **/965 XcmV1MultiassetFungibility: {966 _enum: {967 Fungible: 'Compact<u128>',968 NonFungible: 'XcmV1MultiassetAssetInstance'969 }970 },971 /**972 * Lookup116: xcm::v1::order::Order<Call>973 **/974 XcmV1Order: {975 _enum: {976 Noop: 'Null',977 DepositAsset: {978 assets: 'XcmV1MultiassetMultiAssetFilter',979 maxAssets: 'u32',980 beneficiary: 'XcmV1MultiLocation',981 },982 DepositReserveAsset: {983 assets: 'XcmV1MultiassetMultiAssetFilter',984 maxAssets: 'u32',985 dest: 'XcmV1MultiLocation',986 effects: 'Vec<XcmV1Order>',987 },988 ExchangeAsset: {989 give: 'XcmV1MultiassetMultiAssetFilter',990 receive: 'XcmV1MultiassetMultiAssets',991 },992 InitiateReserveWithdraw: {993 assets: 'XcmV1MultiassetMultiAssetFilter',994 reserve: 'XcmV1MultiLocation',995 effects: 'Vec<XcmV1Order>',996 },997 InitiateTeleport: {998 assets: 'XcmV1MultiassetMultiAssetFilter',999 dest: 'XcmV1MultiLocation',1000 effects: 'Vec<XcmV1Order>',1001 },1002 QueryHolding: {1003 queryId: 'Compact<u64>',1004 dest: 'XcmV1MultiLocation',1005 assets: 'XcmV1MultiassetMultiAssetFilter',1006 },1007 BuyExecution: {1008 fees: 'XcmV1MultiAsset',1009 weight: 'u64',1010 debt: 'u64',1011 haltOnError: 'bool',1012 instructions: 'Vec<XcmV1Xcm>'1013 }1014 }1015 },1016 /**1017 * Lookup117: xcm::v1::multiasset::MultiAssetFilter1018 **/1019 XcmV1MultiassetMultiAssetFilter: {1020 _enum: {1021 Definite: 'XcmV1MultiassetMultiAssets',1022 Wild: 'XcmV1MultiassetWildMultiAsset'1023 }1024 },1025 /**1026 * Lookup118: xcm::v1::multiasset::WildMultiAsset1027 **/1028 XcmV1MultiassetWildMultiAsset: {1029 _enum: {1030 All: 'Null',1031 AllOf: {1032 id: 'XcmV1MultiassetAssetId',1033 fun: 'XcmV1MultiassetWildFungibility'1034 }1035 }1036 },1037 /**1038 * Lookup119: xcm::v1::multiasset::WildFungibility1039 **/1040 XcmV1MultiassetWildFungibility: {1041 _enum: ['Fungible', 'NonFungible']1042 },1043 /**1044 * Lookup121: xcm::v1::Response1045 **/1046 XcmV1Response: {1047 _enum: {1048 Assets: 'XcmV1MultiassetMultiAssets',1049 Version: 'u32'1050 }1051 },1052 /**1053 * Lookup122: xcm::v2::Xcm<Call>1054 **/1055 XcmV2Xcm: 'Vec<XcmV2Instruction>',1056 /**1057 * Lookup124: xcm::v2::Instruction<Call>1058 **/1059 XcmV2Instruction: {1060 _enum: {1061 WithdrawAsset: 'XcmV1MultiassetMultiAssets',1062 ReserveAssetDeposited: 'XcmV1MultiassetMultiAssets',1063 ReceiveTeleportedAsset: 'XcmV1MultiassetMultiAssets',1064 QueryResponse: {1065 queryId: 'Compact<u64>',1066 response: 'XcmV2Response',1067 maxWeight: 'Compact<u64>',1068 },1069 TransferAsset: {1070 assets: 'XcmV1MultiassetMultiAssets',1071 beneficiary: 'XcmV1MultiLocation',1072 },1073 TransferReserveAsset: {1074 assets: 'XcmV1MultiassetMultiAssets',1075 dest: 'XcmV1MultiLocation',1076 xcm: 'XcmV2Xcm',1077 },1078 Transact: {1079 originType: 'XcmV0OriginKind',1080 requireWeightAtMost: 'Compact<u64>',1081 call: 'XcmDoubleEncoded',1082 },1083 HrmpNewChannelOpenRequest: {1084 sender: 'Compact<u32>',1085 maxMessageSize: 'Compact<u32>',1086 maxCapacity: 'Compact<u32>',1087 },1088 HrmpChannelAccepted: {1089 recipient: 'Compact<u32>',1090 },1091 HrmpChannelClosing: {1092 initiator: 'Compact<u32>',1093 sender: 'Compact<u32>',1094 recipient: 'Compact<u32>',1095 },1096 ClearOrigin: 'Null',1097 DescendOrigin: 'XcmV1MultilocationJunctions',1098 ReportError: {1099 queryId: 'Compact<u64>',1100 dest: 'XcmV1MultiLocation',1101 maxResponseWeight: 'Compact<u64>',1102 },1103 DepositAsset: {1104 assets: 'XcmV1MultiassetMultiAssetFilter',1105 maxAssets: 'Compact<u32>',1106 beneficiary: 'XcmV1MultiLocation',1107 },1108 DepositReserveAsset: {1109 assets: 'XcmV1MultiassetMultiAssetFilter',1110 maxAssets: 'Compact<u32>',1111 dest: 'XcmV1MultiLocation',1112 xcm: 'XcmV2Xcm',1113 },1114 ExchangeAsset: {1115 give: 'XcmV1MultiassetMultiAssetFilter',1116 receive: 'XcmV1MultiassetMultiAssets',1117 },1118 InitiateReserveWithdraw: {1119 assets: 'XcmV1MultiassetMultiAssetFilter',1120 reserve: 'XcmV1MultiLocation',1121 xcm: 'XcmV2Xcm',1122 },1123 InitiateTeleport: {1124 assets: 'XcmV1MultiassetMultiAssetFilter',1125 dest: 'XcmV1MultiLocation',1126 xcm: 'XcmV2Xcm',1127 },1128 QueryHolding: {1129 queryId: 'Compact<u64>',1130 dest: 'XcmV1MultiLocation',1131 assets: 'XcmV1MultiassetMultiAssetFilter',1132 maxResponseWeight: 'Compact<u64>',1133 },1134 BuyExecution: {1135 fees: 'XcmV1MultiAsset',1136 weightLimit: 'XcmV2WeightLimit',1137 },1138 RefundSurplus: 'Null',1139 SetErrorHandler: 'XcmV2Xcm',1140 SetAppendix: 'XcmV2Xcm',1141 ClearError: 'Null',1142 ClaimAsset: {1143 assets: 'XcmV1MultiassetMultiAssets',1144 ticket: 'XcmV1MultiLocation',1145 },1146 Trap: 'Compact<u64>',1147 SubscribeVersion: {1148 queryId: 'Compact<u64>',1149 maxResponseWeight: 'Compact<u64>',1150 },1151 UnsubscribeVersion: 'Null'1152 }1153 },1154 /**1155 * Lookup125: xcm::v2::Response1156 **/1157 XcmV2Response: {1158 _enum: {1159 Null: 'Null',1160 Assets: 'XcmV1MultiassetMultiAssets',1161 ExecutionResult: 'Option<(u32,XcmV2TraitsError)>',1162 Version: 'u32'1163 }1164 },1165 /**1166 * Lookup128: xcm::v2::traits::Error1167 **/1168 XcmV2TraitsError: {1169 _enum: {1170 Overflow: 'Null',1171 Unimplemented: 'Null',1172 UntrustedReserveLocation: 'Null',1173 UntrustedTeleportLocation: 'Null',1174 MultiLocationFull: 'Null',1175 MultiLocationNotInvertible: 'Null',1176 BadOrigin: 'Null',1177 InvalidLocation: 'Null',1178 AssetNotFound: 'Null',1179 FailedToTransactAsset: 'Null',1180 NotWithdrawable: 'Null',1181 LocationCannotHold: 'Null',1182 ExceedsMaxMessageSize: 'Null',1183 DestinationUnsupported: 'Null',1184 Transport: 'Null',1185 Unroutable: 'Null',1186 UnknownClaim: 'Null',1187 FailedToDecode: 'Null',1188 MaxWeightInvalid: 'Null',1189 NotHoldingFees: 'Null',1190 TooExpensive: 'Null',1191 Trap: 'u64',1192 UnhandledXcmVersion: 'Null',1193 WeightLimitReached: 'u64',1194 Barrier: 'Null',1195 WeightNotComputable: 'Null'1196 }1197 },1198 /**1199 * Lookup129: xcm::v2::WeightLimit1200 **/1201 XcmV2WeightLimit: {1202 _enum: {1203 Unlimited: 'Null',1204 Limited: 'Compact<u64>'1205 }1206 },1207 /**1208 * Lookup130: xcm::VersionedMultiAssets1209 **/1210 XcmVersionedMultiAssets: {1211 _enum: {1212 V0: 'Vec<XcmV0MultiAsset>',1213 V1: 'XcmV1MultiassetMultiAssets'1214 }1215 },1216 /**1217 * Lookup145: cumulus_pallet_xcm::pallet::Call<T>1218 **/1219 CumulusPalletXcmCall: 'Null',1220 /**1221 * Lookup146: cumulus_pallet_dmp_queue::pallet::Call<T>1222 **/1223 CumulusPalletDmpQueueCall: {1224 _enum: {1225 service_overweight: {1226 index: 'u64',1227 weightLimit: 'u64'1228 }1229 }1230 },1231 /**1232 * Lookup147: pallet_inflation::pallet::Call<T>1233 **/1234 PalletInflationCall: {1235 _enum: {1236 start_inflation: {1237 inflationStartRelayBlock: 'u32'1238 }1239 }1240 },1241 /**1242 * Lookup148: pallet_unique::Call<T>1243 **/1244 PalletUniqueCall: {1245 _enum: {1246 create_collection: {1247 collectionName: 'Vec<u16>',1248 collectionDescription: 'Vec<u16>',1249 tokenPrefix: 'Bytes',1250 mode: 'UpDataStructsCollectionMode',1251 },1252 create_collection_ex: {1253 data: 'UpDataStructsCreateCollectionData',1254 },1255 destroy_collection: {1256 collectionId: 'u32',1257 },1258 add_to_allow_list: {1259 collectionId: 'u32',1260 address: 'PalletEvmAccountBasicCrossAccountIdRepr',1261 },1262 remove_from_allow_list: {1263 collectionId: 'u32',1264 address: 'PalletEvmAccountBasicCrossAccountIdRepr',1265 },1266 change_collection_owner: {1267 collectionId: 'u32',1268 newOwner: 'AccountId32',1269 },1270 add_collection_admin: {1271 collectionId: 'u32',1272 newAdmin: 'PalletEvmAccountBasicCrossAccountIdRepr',1273 },1274 remove_collection_admin: {1275 collectionId: 'u32',1276 accountId: 'PalletEvmAccountBasicCrossAccountIdRepr',1277 },1278 set_collection_sponsor: {1279 collectionId: 'u32',1280 newSponsor: 'AccountId32',1281 },1282 confirm_sponsorship: {1283 collectionId: 'u32',1284 },1285 remove_collection_sponsor: {1286 collectionId: 'u32',1287 },1288 create_item: {1289 collectionId: 'u32',1290 owner: 'PalletEvmAccountBasicCrossAccountIdRepr',1291 data: 'UpDataStructsCreateItemData',1292 },1293 create_multiple_items: {1294 collectionId: 'u32',1295 owner: 'PalletEvmAccountBasicCrossAccountIdRepr',1296 itemsData: 'Vec<UpDataStructsCreateItemData>',1297 },1298 set_collection_properties: {1299 collectionId: 'u32',1300 properties: 'Vec<UpDataStructsProperty>',1301 },1302 delete_collection_properties: {1303 collectionId: 'u32',1304 propertyKeys: 'Vec<Bytes>',1305 },1306 set_token_properties: {1307 collectionId: 'u32',1308 tokenId: 'u32',1309 properties: 'Vec<UpDataStructsProperty>',1310 },1311 delete_token_properties: {1312 collectionId: 'u32',1313 tokenId: 'u32',1314 propertyKeys: 'Vec<Bytes>',1315 },1316 set_token_property_permissions: {1317 collectionId: 'u32',1318 propertyPermissions: 'Vec<UpDataStructsPropertyKeyPermission>',1319 },1320 create_multiple_items_ex: {1321 collectionId: 'u32',1322 data: 'UpDataStructsCreateItemExData',1323 },1324 set_transfers_enabled_flag: {1325 collectionId: 'u32',1326 value: 'bool',1327 },1328 burn_item: {1329 collectionId: 'u32',1330 itemId: 'u32',1331 value: 'u128',1332 },1333 burn_from: {1334 collectionId: 'u32',1335 from: 'PalletEvmAccountBasicCrossAccountIdRepr',1336 itemId: 'u32',1337 value: 'u128',1338 },1339 transfer: {1340 recipient: 'PalletEvmAccountBasicCrossAccountIdRepr',1341 collectionId: 'u32',1342 itemId: 'u32',1343 value: 'u128',1344 },1345 approve: {1346 spender: 'PalletEvmAccountBasicCrossAccountIdRepr',1347 collectionId: 'u32',1348 itemId: 'u32',1349 amount: 'u128',1350 },1351 transfer_from: {1352 from: 'PalletEvmAccountBasicCrossAccountIdRepr',1353 recipient: 'PalletEvmAccountBasicCrossAccountIdRepr',1354 collectionId: 'u32',1355 itemId: 'u32',1356 value: 'u128',1357 },1358 set_collection_limits: {1359 collectionId: 'u32',1360 newLimit: 'UpDataStructsCollectionLimits',1361 },1362 set_collection_permissions: {1363 collectionId: 'u32',1364 newPermission: 'UpDataStructsCollectionPermissions',1365 },1366 repartition: {1367 collectionId: 'u32',1368 tokenId: 'u32',1369 amount: 'u128'1370 }1371 }1372 },1373 /**1374 * Lookup154: up_data_structs::CollectionMode1375 **/1376 UpDataStructsCollectionMode: {1377 _enum: {1378 NFT: 'Null',1379 Fungible: 'u8',1380 ReFungible: 'Null'1381 }1382 },1383 /**1384 * Lookup155: up_data_structs::CreateCollectionData<sp_core::crypto::AccountId32>1385 **/1386 UpDataStructsCreateCollectionData: {1387 mode: 'UpDataStructsCollectionMode',1388 access: 'Option<UpDataStructsAccessMode>',1389 name: 'Vec<u16>',1390 description: 'Vec<u16>',1391 tokenPrefix: 'Bytes',1392 pendingSponsor: 'Option<AccountId32>',1393 limits: 'Option<UpDataStructsCollectionLimits>',1394 permissions: 'Option<UpDataStructsCollectionPermissions>',1395 tokenPropertyPermissions: 'Vec<UpDataStructsPropertyKeyPermission>',1396 properties: 'Vec<UpDataStructsProperty>'1397 },1398 /**1399 * Lookup157: up_data_structs::AccessMode1400 **/1401 UpDataStructsAccessMode: {1402 _enum: ['Normal', 'AllowList']1403 },1404 /**1405 * Lookup160: up_data_structs::CollectionLimits1406 **/1407 UpDataStructsCollectionLimits: {1408 accountTokenOwnershipLimit: 'Option<u32>',1409 sponsoredDataSize: 'Option<u32>',1410 sponsoredDataRateLimit: 'Option<UpDataStructsSponsoringRateLimit>',1411 tokenLimit: 'Option<u32>',1412 sponsorTransferTimeout: 'Option<u32>',1413 sponsorApproveTimeout: 'Option<u32>',1414 ownerCanTransfer: 'Option<bool>',1415 ownerCanDestroy: 'Option<bool>',1416 transfersEnabled: 'Option<bool>'1417 },1418 /**1419 * Lookup162: up_data_structs::SponsoringRateLimit1420 **/1421 UpDataStructsSponsoringRateLimit: {1422 _enum: {1423 SponsoringDisabled: 'Null',1424 Blocks: 'u32'1425 }1426 },1427 /**1428 * Lookup165: up_data_structs::CollectionPermissions1429 **/1430 UpDataStructsCollectionPermissions: {1431 access: 'Option<UpDataStructsAccessMode>',1432 mintMode: 'Option<bool>',1433 nesting: 'Option<UpDataStructsNestingPermissions>'1434 },1435 /**1436 * Lookup167: up_data_structs::NestingPermissions1437 **/1438 UpDataStructsNestingPermissions: {1439 tokenOwner: 'bool',1440 collectionAdmin: 'bool',1441 restricted: 'Option<UpDataStructsOwnerRestrictedSet>'1442 },1443 /**1444 * Lookup169: up_data_structs::OwnerRestrictedSet1445 **/1446 UpDataStructsOwnerRestrictedSet: 'BTreeSet<u32>',1447 /**1448 * Lookup175: up_data_structs::PropertyKeyPermission1449 **/1450 UpDataStructsPropertyKeyPermission: {1451 key: 'Bytes',1452 permission: 'UpDataStructsPropertyPermission'1453 },1454 /**1455 * Lookup177: up_data_structs::PropertyPermission1456 **/1457 UpDataStructsPropertyPermission: {1458 mutable: 'bool',1459 collectionAdmin: 'bool',1460 tokenOwner: 'bool'1461 },1462 /**1463 * Lookup180: up_data_structs::Property1464 **/1465 UpDataStructsProperty: {1466 key: 'Bytes',1467 value: 'Bytes'1468 },1469 /**1470 * Lookup183: pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>1471 **/1472 PalletEvmAccountBasicCrossAccountIdRepr: {1473 _enum: {1474 Substrate: 'AccountId32',1475 Ethereum: 'H160'1476 }1477 },1478 /**1479 * Lookup185: up_data_structs::CreateItemData1480 **/1481 UpDataStructsCreateItemData: {1482 _enum: {1483 NFT: 'UpDataStructsCreateNftData',1484 Fungible: 'UpDataStructsCreateFungibleData',1485 ReFungible: 'UpDataStructsCreateReFungibleData'1486 }1487 },1488 /**1489 * Lookup186: up_data_structs::CreateNftData1490 **/1491 UpDataStructsCreateNftData: {1492 properties: 'Vec<UpDataStructsProperty>'1493 },1494 /**1495 * Lookup187: up_data_structs::CreateFungibleData1496 **/1497 UpDataStructsCreateFungibleData: {1498 value: 'u128'1499 },1500 /**1501 * Lookup188: up_data_structs::CreateReFungibleData1502 **/1503 UpDataStructsCreateReFungibleData: {1504 constData: 'Bytes',1505 pieces: 'u128'1506 },1507 /**1508 * Lookup193: up_data_structs::CreateItemExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>1509 **/1510 UpDataStructsCreateItemExData: {1511 _enum: {1512 NFT: 'Vec<UpDataStructsCreateNftExData>',1513 Fungible: 'BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>',1514 RefungibleMultipleItems: 'Vec<UpDataStructsCreateRefungibleExData>',1515 RefungibleMultipleOwners: 'UpDataStructsCreateRefungibleExData'1516 }1517 },1518 /**1519 * Lookup195: up_data_structs::CreateNftExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>1520 **/1521 UpDataStructsCreateNftExData: {1522 properties: 'Vec<UpDataStructsProperty>',1523 owner: 'PalletEvmAccountBasicCrossAccountIdRepr'1524 },1525 /**1526 * Lookup202: up_data_structs::CreateRefungibleExData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>1527 **/1528 UpDataStructsCreateRefungibleExData: {1529 constData: 'Bytes',1530 users: 'BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>'1531 },1532 /**1533 * Lookup204: pallet_unique_scheduler::pallet::Call<T>1534 **/1535 PalletUniqueSchedulerCall: {1536 _enum: {1537 schedule_named: {1538 id: '[u8;16]',1539 when: 'u32',1540 maybePeriodic: 'Option<(u32,u32)>',1541 priority: 'u8',1542 call: 'FrameSupportScheduleMaybeHashed',1543 },1544 cancel_named: {1545 id: '[u8;16]',1546 },1547 schedule_named_after: {1548 id: '[u8;16]',1549 after: 'u32',1550 maybePeriodic: 'Option<(u32,u32)>',1551 priority: 'u8',1552 call: 'FrameSupportScheduleMaybeHashed'1553 }1554 }1555 },1556 /**1557 * Lookup206: frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>1558 **/1559 FrameSupportScheduleMaybeHashed: {1560 _enum: {1561 Value: 'Call',1562 Hash: 'H256'1563 }1564 },1565 /**1566 * Lookup207: pallet_template_transaction_payment::Call<T>1567 **/1568 PalletTemplateTransactionPaymentCall: 'Null',1569 /**1570 * Lookup208: pallet_structure::pallet::Call<T>1571 **/1572 PalletStructureCall: 'Null',1573 /**1574 * Lookup209: pallet_rmrk_core::pallet::Call<T>1575 **/1576 PalletRmrkCoreCall: {1577 _enum: {1578 create_collection: {1579 metadata: 'Bytes',1580 max: 'Option<u32>',1581 symbol: 'Bytes',1582 },1583 destroy_collection: {1584 collectionId: 'u32',1585 },1586 change_collection_issuer: {1587 collectionId: 'u32',1588 newIssuer: 'MultiAddress',1589 },1590 lock_collection: {1591 collectionId: 'u32',1592 },1593 mint_nft: {1594 owner: 'Option<AccountId32>',1595 collectionId: 'u32',1596 recipient: 'Option<AccountId32>',1597 royaltyAmount: 'Option<Permill>',1598 metadata: 'Bytes',1599 transferable: 'bool',1600 resources: 'Option<Vec<RmrkTraitsResourceResourceTypes>>',1601 },1602 burn_nft: {1603 collectionId: 'u32',1604 nftId: 'u32',1605 maxBurns: 'u32',1606 },1607 send: {1608 rmrkCollectionId: 'u32',1609 rmrkNftId: 'u32',1610 newOwner: 'RmrkTraitsNftAccountIdOrCollectionNftTuple',1611 },1612 accept_nft: {1613 rmrkCollectionId: 'u32',1614 rmrkNftId: 'u32',1615 newOwner: 'RmrkTraitsNftAccountIdOrCollectionNftTuple',1616 },1617 reject_nft: {1618 rmrkCollectionId: 'u32',1619 rmrkNftId: 'u32',1620 },1621 accept_resource: {1622 rmrkCollectionId: 'u32',1623 rmrkNftId: 'u32',1624 resourceId: 'u32',1625 },1626 accept_resource_removal: {1627 rmrkCollectionId: 'u32',1628 rmrkNftId: 'u32',1629 resourceId: 'u32',1630 },1631 set_property: {1632 rmrkCollectionId: 'Compact<u32>',1633 maybeNftId: 'Option<u32>',1634 key: 'Bytes',1635 value: 'Bytes',1636 },1637 set_priority: {1638 rmrkCollectionId: 'u32',1639 rmrkNftId: 'u32',1640 priorities: 'Vec<u32>',1641 },1642 add_basic_resource: {1643 rmrkCollectionId: 'u32',1644 nftId: 'u32',1645 resource: 'RmrkTraitsResourceBasicResource',1646 },1647 add_composable_resource: {1648 rmrkCollectionId: 'u32',1649 nftId: 'u32',1650 resource: 'RmrkTraitsResourceComposableResource',1651 },1652 add_slot_resource: {1653 rmrkCollectionId: 'u32',1654 nftId: 'u32',1655 resource: 'RmrkTraitsResourceSlotResource',1656 },1657 remove_resource: {1658 rmrkCollectionId: 'u32',1659 nftId: 'u32',1660 resourceId: 'u32'1661 }1662 }1663 },1664 /**1665 * Lookup215: rmrk_traits::resource::ResourceTypes<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<T, S>>1666 **/1667 RmrkTraitsResourceResourceTypes: {1668 _enum: {1669 Basic: 'RmrkTraitsResourceBasicResource',1670 Composable: 'RmrkTraitsResourceComposableResource',1671 Slot: 'RmrkTraitsResourceSlotResource'1672 }1673 },1674 /**1675 * Lookup217: rmrk_traits::resource::BasicResource<frame_support::storage::bounded_vec::BoundedVec<T, S>>1676 **/1677 RmrkTraitsResourceBasicResource: {1678 src: 'Option<Bytes>',1679 metadata: 'Option<Bytes>',1680 license: 'Option<Bytes>',1681 thumb: 'Option<Bytes>'1682 },1683 /**1684 * Lookup219: rmrk_traits::resource::ComposableResource<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<T, S>>1685 **/1686 RmrkTraitsResourceComposableResource: {1687 parts: 'Vec<u32>',1688 base: 'u32',1689 src: 'Option<Bytes>',1690 metadata: 'Option<Bytes>',1691 license: 'Option<Bytes>',1692 thumb: 'Option<Bytes>'1693 },1694 /**1695 * Lookup220: rmrk_traits::resource::SlotResource<frame_support::storage::bounded_vec::BoundedVec<T, S>>1696 **/1697 RmrkTraitsResourceSlotResource: {1698 base: 'u32',1699 src: 'Option<Bytes>',1700 metadata: 'Option<Bytes>',1701 slot: 'u32',1702 license: 'Option<Bytes>',1703 thumb: 'Option<Bytes>'1704 },1705 /**1706 * Lookup222: rmrk_traits::nft::AccountIdOrCollectionNftTuple<sp_core::crypto::AccountId32>1707 **/1708 RmrkTraitsNftAccountIdOrCollectionNftTuple: {1709 _enum: {1710 AccountId: 'AccountId32',1711 CollectionAndNftTuple: '(u32,u32)'1712 }1713 },1714 /**1715 * Lookup226: pallet_rmrk_equip::pallet::Call<T>1716 **/1717 PalletRmrkEquipCall: {1718 _enum: {1719 create_base: {1720 baseType: 'Bytes',1721 symbol: 'Bytes',1722 parts: 'Vec<RmrkTraitsPartPartType>',1723 },1724 theme_add: {1725 baseId: 'u32',1726 theme: 'RmrkTraitsTheme',1727 },1728 equippable: {1729 baseId: 'u32',1730 slotId: 'u32',1731 equippables: 'RmrkTraitsPartEquippableList'1732 }1733 }1734 },1735 /**1736 * Lookup229: rmrk_traits::part::PartType<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<T, S>>1737 **/1738 RmrkTraitsPartPartType: {1739 _enum: {1740 FixedPart: 'RmrkTraitsPartFixedPart',1741 SlotPart: 'RmrkTraitsPartSlotPart'1742 }1743 },1744 /**1745 * Lookup231: rmrk_traits::part::FixedPart<frame_support::storage::bounded_vec::BoundedVec<T, S>>1746 **/1747 RmrkTraitsPartFixedPart: {1748 id: 'u32',1749 z: 'u32',1750 src: 'Bytes'1751 },1752 /**1753 * Lookup232: rmrk_traits::part::SlotPart<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<T, S>>1754 **/1755 RmrkTraitsPartSlotPart: {1756 id: 'u32',1757 equippable: 'RmrkTraitsPartEquippableList',1758 src: 'Bytes',1759 z: 'u32'1760 },1761 /**1762 * Lookup233: rmrk_traits::part::EquippableList<frame_support::storage::bounded_vec::BoundedVec<T, S>>1763 **/1764 RmrkTraitsPartEquippableList: {1765 _enum: {1766 All: 'Null',1767 Empty: 'Null',1768 Custom: 'Vec<u32>'1769 }1770 },1771 /**1772 * Lookup235: rmrk_traits::theme::Theme<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<rmrk_traits::theme::ThemeProperty<frame_support::storage::bounded_vec::BoundedVec<T, S>>, S>>1773 **/1774 RmrkTraitsTheme: {1775 name: 'Bytes',1776 properties: 'Vec<RmrkTraitsThemeThemeProperty>',1777 inherit: 'bool'1778 },1779 /**1780 * Lookup237: rmrk_traits::theme::ThemeProperty<frame_support::storage::bounded_vec::BoundedVec<T, S>>1781 **/1782 RmrkTraitsThemeThemeProperty: {1783 key: 'Bytes',1784 value: 'Bytes'1785 },1786 /**1787 * Lookup239: pallet_evm::pallet::Call<T>1788 **/1789 PalletEvmCall: {1790 _enum: {1791 withdraw: {1792 address: 'H160',1793 value: 'u128',1794 },1795 call: {1796 source: 'H160',1797 target: 'H160',1798 input: 'Bytes',1799 value: 'U256',1800 gasLimit: 'u64',1801 maxFeePerGas: 'U256',1802 maxPriorityFeePerGas: 'Option<U256>',1803 nonce: 'Option<U256>',1804 accessList: 'Vec<(H160,Vec<H256>)>',1805 },1806 create: {1807 source: 'H160',1808 init: 'Bytes',1809 value: 'U256',1810 gasLimit: 'u64',1811 maxFeePerGas: 'U256',1812 maxPriorityFeePerGas: 'Option<U256>',1813 nonce: 'Option<U256>',1814 accessList: 'Vec<(H160,Vec<H256>)>',1815 },1816 create2: {1817 source: 'H160',1818 init: 'Bytes',1819 salt: 'H256',1820 value: 'U256',1821 gasLimit: 'u64',1822 maxFeePerGas: 'U256',1823 maxPriorityFeePerGas: 'Option<U256>',1824 nonce: 'Option<U256>',1825 accessList: 'Vec<(H160,Vec<H256>)>'1826 }1827 }1828 },1829 /**1830 * Lookup245: pallet_ethereum::pallet::Call<T>1831 **/1832 PalletEthereumCall: {1833 _enum: {1834 transact: {1835 transaction: 'EthereumTransactionTransactionV2'1836 }1837 }1838 },1839 /**1840 * Lookup246: ethereum::transaction::TransactionV21841 **/1842 EthereumTransactionTransactionV2: {1843 _enum: {1844 Legacy: 'EthereumTransactionLegacyTransaction',1845 EIP2930: 'EthereumTransactionEip2930Transaction',1846 EIP1559: 'EthereumTransactionEip1559Transaction'1847 }1848 },1849 /**1850 * Lookup247: ethereum::transaction::LegacyTransaction1851 **/1852 EthereumTransactionLegacyTransaction: {1853 nonce: 'U256',1854 gasPrice: 'U256',1855 gasLimit: 'U256',1856 action: 'EthereumTransactionTransactionAction',1857 value: 'U256',1858 input: 'Bytes',1859 signature: 'EthereumTransactionTransactionSignature'1860 },1861 /**1862 * Lookup248: ethereum::transaction::TransactionAction1863 **/1864 EthereumTransactionTransactionAction: {1865 _enum: {1866 Call: 'H160',1867 Create: 'Null'1868 }1869 },1870 /**1871 * Lookup249: ethereum::transaction::TransactionSignature1872 **/1873 EthereumTransactionTransactionSignature: {1874 v: 'u64',1875 r: 'H256',1876 s: 'H256'1877 },1878 /**1879 * Lookup251: ethereum::transaction::EIP2930Transaction1880 **/1881 EthereumTransactionEip2930Transaction: {1882 chainId: 'u64',1883 nonce: 'U256',1884 gasPrice: 'U256',1885 gasLimit: 'U256',1886 action: 'EthereumTransactionTransactionAction',1887 value: 'U256',1888 input: 'Bytes',1889 accessList: 'Vec<EthereumTransactionAccessListItem>',1890 oddYParity: 'bool',1891 r: 'H256',1892 s: 'H256'1893 },1894 /**1895 * Lookup253: ethereum::transaction::AccessListItem1896 **/1897 EthereumTransactionAccessListItem: {1898 address: 'H160',1899 storageKeys: 'Vec<H256>'1900 },1901 /**1902 * Lookup254: ethereum::transaction::EIP1559Transaction1903 **/1904 EthereumTransactionEip1559Transaction: {1905 chainId: 'u64',1906 nonce: 'U256',1907 maxPriorityFeePerGas: 'U256',1908 maxFeePerGas: 'U256',1909 gasLimit: 'U256',1910 action: 'EthereumTransactionTransactionAction',1911 value: 'U256',1912 input: 'Bytes',1913 accessList: 'Vec<EthereumTransactionAccessListItem>',1914 oddYParity: 'bool',1915 r: 'H256',1916 s: 'H256'1917 },1918 /**1919 * Lookup255: pallet_evm_migration::pallet::Call<T>1920 **/1921 PalletEvmMigrationCall: {1922 _enum: {1923 begin: {1924 address: 'H160',1925 },1926 set_data: {1927 address: 'H160',1928 data: 'Vec<(H256,H256)>',1929 },1930 finish: {1931 address: 'H160',1932 code: 'Bytes'1933 }1934 }1935 },1936 /**1937 * Lookup258: pallet_sudo::pallet::Event<T>1938 **/1939 PalletSudoEvent: {1940 _enum: {1941 Sudid: {1942 sudoResult: 'Result<Null, SpRuntimeDispatchError>',1943 },1944 KeyChanged: {1945 oldSudoer: 'Option<AccountId32>',1946 },1947 SudoAsDone: {1948 sudoResult: 'Result<Null, SpRuntimeDispatchError>'1949 }1950 }1951 },1952 /**1953 * Lookup260: sp_runtime::DispatchError1954 **/1955 SpRuntimeDispatchError: {1956 _enum: {1957 Other: 'Null',1958 CannotLookup: 'Null',1959 BadOrigin: 'Null',1960 Module: 'SpRuntimeModuleError',1961 ConsumerRemaining: 'Null',1962 NoProviders: 'Null',1963 TooManyConsumers: 'Null',1964 Token: 'SpRuntimeTokenError',1965 Arithmetic: 'SpRuntimeArithmeticError',1966 Transactional: 'SpRuntimeTransactionalError'1967 }1968 },1969 /**1970 * Lookup261: sp_runtime::ModuleError1971 **/1972 SpRuntimeModuleError: {1973 index: 'u8',1974 error: '[u8;4]'1975 },1976 /**1977 * Lookup262: sp_runtime::TokenError1978 **/1979 SpRuntimeTokenError: {1980 _enum: ['NoFunds', 'WouldDie', 'BelowMinimum', 'CannotCreate', 'UnknownAsset', 'Frozen', 'Unsupported']1981 },1982 /**1983 * Lookup263: sp_runtime::ArithmeticError1984 **/1985 SpRuntimeArithmeticError: {1986 _enum: ['Underflow', 'Overflow', 'DivisionByZero']1987 },1988 /**1989 * Lookup264: sp_runtime::TransactionalError1990 **/1991 SpRuntimeTransactionalError: {1992 _enum: ['LimitReached', 'NoLayer']1993 },1994 /**1995 * Lookup265: pallet_sudo::pallet::Error<T>1996 **/1997 PalletSudoError: {1998 _enum: ['RequireSudo']1999 },2000 /**2001 * Lookup266: frame_system::AccountInfo<Index, pallet_balances::AccountData<Balance>>2002 **/2003 FrameSystemAccountInfo: {2004 nonce: 'u32',2005 consumers: 'u32',2006 providers: 'u32',2007 sufficients: 'u32',2008 data: 'PalletBalancesAccountData'2009 },2010 /**2011 * Lookup267: frame_support::weights::PerDispatchClass<T>2012 **/2013 FrameSupportWeightsPerDispatchClassU64: {2014 normal: 'u64',2015 operational: 'u64',2016 mandatory: 'u64'2017 },2018 /**2019 * Lookup268: sp_runtime::generic::digest::Digest2020 **/2021 SpRuntimeDigest: {2022 logs: 'Vec<SpRuntimeDigestDigestItem>'2023 },2024 /**2025 * Lookup270: sp_runtime::generic::digest::DigestItem2026 **/2027 SpRuntimeDigestDigestItem: {2028 _enum: {2029 Other: 'Bytes',2030 __Unused1: 'Null',2031 __Unused2: 'Null',2032 __Unused3: 'Null',2033 Consensus: '([u8;4],Bytes)',2034 Seal: '([u8;4],Bytes)',2035 PreRuntime: '([u8;4],Bytes)',2036 __Unused7: 'Null',2037 RuntimeEnvironmentUpdated: 'Null'2038 }2039 },2040 /**2041 * Lookup272: frame_system::EventRecord<opal_runtime::Event, primitive_types::H256>2042 **/2043 FrameSystemEventRecord: {2044 phase: 'FrameSystemPhase',2045 event: 'Event',2046 topics: 'Vec<H256>'2047 },2048 /**2049 * Lookup274: frame_system::pallet::Event<T>2050 **/2051 FrameSystemEvent: {2052 _enum: {2053 ExtrinsicSuccess: {2054 dispatchInfo: 'FrameSupportWeightsDispatchInfo',2055 },2056 ExtrinsicFailed: {2057 dispatchError: 'SpRuntimeDispatchError',2058 dispatchInfo: 'FrameSupportWeightsDispatchInfo',2059 },2060 CodeUpdated: 'Null',2061 NewAccount: {2062 account: 'AccountId32',2063 },2064 KilledAccount: {2065 account: 'AccountId32',2066 },2067 Remarked: {2068 _alias: {2069 hash_: 'hash',2070 },2071 sender: 'AccountId32',2072 hash_: 'H256'2073 }2074 }2075 },2076 /**2077 * Lookup275: frame_support::weights::DispatchInfo2078 **/2079 FrameSupportWeightsDispatchInfo: {2080 weight: 'u64',2081 class: 'FrameSupportWeightsDispatchClass',2082 paysFee: 'FrameSupportWeightsPays'2083 },2084 /**2085 * Lookup276: frame_support::weights::DispatchClass2086 **/2087 FrameSupportWeightsDispatchClass: {2088 _enum: ['Normal', 'Operational', 'Mandatory']2089 },2090 /**2091 * Lookup277: frame_support::weights::Pays2092 **/2093 FrameSupportWeightsPays: {2094 _enum: ['Yes', 'No']2095 },2096 /**2097 * Lookup278: orml_vesting::module::Event<T>2098 **/2099 OrmlVestingModuleEvent: {2100 _enum: {2101 VestingScheduleAdded: {2102 from: 'AccountId32',2103 to: 'AccountId32',2104 vestingSchedule: 'OrmlVestingVestingSchedule',2105 },2106 Claimed: {2107 who: 'AccountId32',2108 amount: 'u128',2109 },2110 VestingSchedulesUpdated: {2111 who: 'AccountId32'2112 }2113 }2114 },2115 /**2116 * Lookup279: cumulus_pallet_xcmp_queue::pallet::Event<T>2117 **/2118 CumulusPalletXcmpQueueEvent: {2119 _enum: {2120 Success: 'Option<H256>',2121 Fail: '(Option<H256>,XcmV2TraitsError)',2122 BadVersion: 'Option<H256>',2123 BadFormat: 'Option<H256>',2124 UpwardMessageSent: 'Option<H256>',2125 XcmpMessageSent: 'Option<H256>',2126 OverweightEnqueued: '(u32,u32,u64,u64)',2127 OverweightServiced: '(u64,u64)'2128 }2129 },2130 /**2131 * Lookup280: pallet_xcm::pallet::Event<T>2132 **/2133 PalletXcmEvent: {2134 _enum: {2135 Attempted: 'XcmV2TraitsOutcome',2136 Sent: '(XcmV1MultiLocation,XcmV1MultiLocation,XcmV2Xcm)',2137 UnexpectedResponse: '(XcmV1MultiLocation,u64)',2138 ResponseReady: '(u64,XcmV2Response)',2139 Notified: '(u64,u8,u8)',2140 NotifyOverweight: '(u64,u8,u8,u64,u64)',2141 NotifyDispatchError: '(u64,u8,u8)',2142 NotifyDecodeFailed: '(u64,u8,u8)',2143 InvalidResponder: '(XcmV1MultiLocation,u64,Option<XcmV1MultiLocation>)',2144 InvalidResponderVersion: '(XcmV1MultiLocation,u64)',2145 ResponseTaken: 'u64',2146 AssetsTrapped: '(H256,XcmV1MultiLocation,XcmVersionedMultiAssets)',2147 VersionChangeNotified: '(XcmV1MultiLocation,u32)',2148 SupportedVersionChanged: '(XcmV1MultiLocation,u32)',2149 NotifyTargetSendFail: '(XcmV1MultiLocation,u64,XcmV2TraitsError)',2150 NotifyTargetMigrationFail: '(XcmVersionedMultiLocation,u64)'2151 }2152 },2153 /**2154 * Lookup281: xcm::v2::traits::Outcome2155 **/2156 XcmV2TraitsOutcome: {2157 _enum: {2158 Complete: 'u64',2159 Incomplete: '(u64,XcmV2TraitsError)',2160 Error: 'XcmV2TraitsError'2161 }2162 },2163 /**2164 * Lookup283: cumulus_pallet_xcm::pallet::Event<T>2165 **/2166 CumulusPalletXcmEvent: {2167 _enum: {2168 InvalidFormat: '[u8;8]',2169 UnsupportedVersion: '[u8;8]',2170 ExecutedDownward: '([u8;8],XcmV2TraitsOutcome)'2171 }2172 },2173 /**2174 * Lookup284: cumulus_pallet_dmp_queue::pallet::Event<T>2175 **/2176 CumulusPalletDmpQueueEvent: {2177 _enum: {2178 InvalidFormat: {2179 messageId: '[u8;32]',2180 },2181 UnsupportedVersion: {2182 messageId: '[u8;32]',2183 },2184 ExecutedDownward: {2185 messageId: '[u8;32]',2186 outcome: 'XcmV2TraitsOutcome',2187 },2188 WeightExhausted: {2189 messageId: '[u8;32]',2190 remainingWeight: 'u64',2191 requiredWeight: 'u64',2192 },2193 OverweightEnqueued: {2194 messageId: '[u8;32]',2195 overweightIndex: 'u64',2196 requiredWeight: 'u64',2197 },2198 OverweightServiced: {2199 overweightIndex: 'u64',2200 weightUsed: 'u64'2201 }2202 }2203 },2204 /**2205 * Lookup285: pallet_unique::RawEvent<sp_core::crypto::AccountId32, pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>2206 **/2207 PalletUniqueRawEvent: {2208 _enum: {2209 CollectionSponsorRemoved: 'u32',2210 CollectionAdminAdded: '(u32,PalletEvmAccountBasicCrossAccountIdRepr)',2211 CollectionOwnedChanged: '(u32,AccountId32)',2212 CollectionSponsorSet: '(u32,AccountId32)',2213 SponsorshipConfirmed: '(u32,AccountId32)',2214 CollectionAdminRemoved: '(u32,PalletEvmAccountBasicCrossAccountIdRepr)',2215 AllowListAddressRemoved: '(u32,PalletEvmAccountBasicCrossAccountIdRepr)',2216 AllowListAddressAdded: '(u32,PalletEvmAccountBasicCrossAccountIdRepr)',2217 CollectionLimitSet: 'u32',2218 CollectionPermissionSet: 'u32'2219 }2220 },2221 /**2222 * Lookup286: pallet_unique_scheduler::pallet::Event<T>2223 **/2224 PalletUniqueSchedulerEvent: {2225 _enum: {2226 Scheduled: {2227 when: 'u32',2228 index: 'u32',2229 },2230 Canceled: {2231 when: 'u32',2232 index: 'u32',2233 },2234 Dispatched: {2235 task: '(u32,u32)',2236 id: 'Option<[u8;16]>',2237 result: 'Result<Null, SpRuntimeDispatchError>',2238 },2239 CallLookupFailed: {2240 task: '(u32,u32)',2241 id: 'Option<[u8;16]>',2242 error: 'FrameSupportScheduleLookupError'2243 }2244 }2245 },2246 /**2247 * Lookup288: frame_support::traits::schedule::LookupError2248 **/2249 FrameSupportScheduleLookupError: {2250 _enum: ['Unknown', 'BadFormat']2251 },2252 /**2253 * Lookup289: pallet_common::pallet::Event<T>2254 **/2255 PalletCommonEvent: {2256 _enum: {2257 CollectionCreated: '(u32,u8,AccountId32)',2258 CollectionDestroyed: 'u32',2259 ItemCreated: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,u128)',2260 ItemDestroyed: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,u128)',2261 Transfer: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,u128)',2262 Approved: '(u32,u32,PalletEvmAccountBasicCrossAccountIdRepr,PalletEvmAccountBasicCrossAccountIdRepr,u128)',2263 CollectionPropertySet: '(u32,Bytes)',2264 CollectionPropertyDeleted: '(u32,Bytes)',2265 TokenPropertySet: '(u32,u32,Bytes)',2266 TokenPropertyDeleted: '(u32,u32,Bytes)',2267 PropertyPermissionSet: '(u32,Bytes)'2268 }2269 },2270 /**2271 * Lookup290: pallet_structure::pallet::Event<T>2272 **/2273 PalletStructureEvent: {2274 _enum: {2275 Executed: 'Result<Null, SpRuntimeDispatchError>'2276 }2277 },2278 /**2279 * Lookup291: pallet_rmrk_core::pallet::Event<T>2280 **/2281 PalletRmrkCoreEvent: {2282 _enum: {2283 CollectionCreated: {2284 issuer: 'AccountId32',2285 collectionId: 'u32',2286 },2287 CollectionDestroyed: {2288 issuer: 'AccountId32',2289 collectionId: 'u32',2290 },2291 IssuerChanged: {2292 oldIssuer: 'AccountId32',2293 newIssuer: 'AccountId32',2294 collectionId: 'u32',2295 },2296 CollectionLocked: {2297 issuer: 'AccountId32',2298 collectionId: 'u32',2299 },2300 NftMinted: {2301 owner: 'AccountId32',2302 collectionId: 'u32',2303 nftId: 'u32',2304 },2305 NFTBurned: {2306 owner: 'AccountId32',2307 nftId: 'u32',2308 },2309 NFTSent: {2310 sender: 'AccountId32',2311 recipient: 'RmrkTraitsNftAccountIdOrCollectionNftTuple',2312 collectionId: 'u32',2313 nftId: 'u32',2314 approvalRequired: 'bool',2315 },2316 NFTAccepted: {2317 sender: 'AccountId32',2318 recipient: 'RmrkTraitsNftAccountIdOrCollectionNftTuple',2319 collectionId: 'u32',2320 nftId: 'u32',2321 },2322 NFTRejected: {2323 sender: 'AccountId32',2324 collectionId: 'u32',2325 nftId: 'u32',2326 },2327 PropertySet: {2328 collectionId: 'u32',2329 maybeNftId: 'Option<u32>',2330 key: 'Bytes',2331 value: 'Bytes',2332 },2333 ResourceAdded: {2334 nftId: 'u32',2335 resourceId: 'u32',2336 },2337 ResourceRemoval: {2338 nftId: 'u32',2339 resourceId: 'u32',2340 },2341 ResourceAccepted: {2342 nftId: 'u32',2343 resourceId: 'u32',2344 },2345 ResourceRemovalAccepted: {2346 nftId: 'u32',2347 resourceId: 'u32',2348 },2349 PrioritySet: {2350 collectionId: 'u32',2351 nftId: 'u32'2352 }2353 }2354 },2355 /**2356 * Lookup292: pallet_rmrk_equip::pallet::Event<T>2357 **/2358 PalletRmrkEquipEvent: {2359 _enum: {2360 BaseCreated: {2361 issuer: 'AccountId32',2362 baseId: 'u32',2363 },2364 EquippablesUpdated: {2365 baseId: 'u32',2366 slotId: 'u32'2367 }2368 }2369 },2370 /**2371 * Lookup293: pallet_evm::pallet::Event<T>2372 **/2373 PalletEvmEvent: {2374 _enum: {2375 Log: 'EthereumLog',2376 Created: 'H160',2377 CreatedFailed: 'H160',2378 Executed: 'H160',2379 ExecutedFailed: 'H160',2380 BalanceDeposit: '(AccountId32,H160,U256)',2381 BalanceWithdraw: '(AccountId32,H160,U256)'2382 }2383 },2384 /**2385 * Lookup294: ethereum::log::Log2386 **/2387 EthereumLog: {2388 address: 'H160',2389 topics: 'Vec<H256>',2390 data: 'Bytes'2391 },2392 /**2393 * Lookup295: pallet_ethereum::pallet::Event2394 **/2395 PalletEthereumEvent: {2396 _enum: {2397 Executed: '(H160,H160,H256,EvmCoreErrorExitReason)'2398 }2399 },2400 /**2401 * Lookup296: evm_core::error::ExitReason2402 **/2403 EvmCoreErrorExitReason: {2404 _enum: {2405 Succeed: 'EvmCoreErrorExitSucceed',2406 Error: 'EvmCoreErrorExitError',2407 Revert: 'EvmCoreErrorExitRevert',2408 Fatal: 'EvmCoreErrorExitFatal'2409 }2410 },2411 /**2412 * Lookup297: evm_core::error::ExitSucceed2413 **/2414 EvmCoreErrorExitSucceed: {2415 _enum: ['Stopped', 'Returned', 'Suicided']2416 },2417 /**2418 * Lookup298: evm_core::error::ExitError2419 **/2420 EvmCoreErrorExitError: {2421 _enum: {2422 StackUnderflow: 'Null',2423 StackOverflow: 'Null',2424 InvalidJump: 'Null',2425 InvalidRange: 'Null',2426 DesignatedInvalid: 'Null',2427 CallTooDeep: 'Null',2428 CreateCollision: 'Null',2429 CreateContractLimit: 'Null',2430 OutOfOffset: 'Null',2431 OutOfGas: 'Null',2432 OutOfFund: 'Null',2433 PCUnderflow: 'Null',2434 CreateEmpty: 'Null',2435 Other: 'Text',2436 InvalidCode: 'Null'2437 }2438 },2439 /**2440 * Lookup301: evm_core::error::ExitRevert2441 **/2442 EvmCoreErrorExitRevert: {2443 _enum: ['Reverted']2444 },2445 /**2446 * Lookup302: evm_core::error::ExitFatal2447 **/2448 EvmCoreErrorExitFatal: {2449 _enum: {2450 NotSupported: 'Null',2451 UnhandledInterrupt: 'Null',2452 CallErrorAsFatal: 'EvmCoreErrorExitError',2453 Other: 'Text'2454 }2455 },2456 /**2457 * Lookup303: frame_system::Phase2458 **/2459 FrameSystemPhase: {2460 _enum: {2461 ApplyExtrinsic: 'u32',2462 Finalization: 'Null',2463 Initialization: 'Null'2464 }2465 },2466 /**2467 * Lookup305: frame_system::LastRuntimeUpgradeInfo2468 **/2469 FrameSystemLastRuntimeUpgradeInfo: {2470 specVersion: 'Compact<u32>',2471 specName: 'Text'2472 },2473 /**2474 * Lookup306: frame_system::limits::BlockWeights2475 **/2476 FrameSystemLimitsBlockWeights: {2477 baseBlock: 'u64',2478 maxBlock: 'u64',2479 perClass: 'FrameSupportWeightsPerDispatchClassWeightsPerClass'2480 },2481 /**2482 * Lookup307: frame_support::weights::PerDispatchClass<frame_system::limits::WeightsPerClass>2483 **/2484 FrameSupportWeightsPerDispatchClassWeightsPerClass: {2485 normal: 'FrameSystemLimitsWeightsPerClass',2486 operational: 'FrameSystemLimitsWeightsPerClass',2487 mandatory: 'FrameSystemLimitsWeightsPerClass'2488 },2489 /**2490 * Lookup308: frame_system::limits::WeightsPerClass2491 **/2492 FrameSystemLimitsWeightsPerClass: {2493 baseExtrinsic: 'u64',2494 maxExtrinsic: 'Option<u64>',2495 maxTotal: 'Option<u64>',2496 reserved: 'Option<u64>'2497 },2498 /**2499 * Lookup310: frame_system::limits::BlockLength2500 **/2501 FrameSystemLimitsBlockLength: {2502 max: 'FrameSupportWeightsPerDispatchClassU32'2503 },2504 /**2505 * Lookup311: frame_support::weights::PerDispatchClass<T>2506 **/2507 FrameSupportWeightsPerDispatchClassU32: {2508 normal: 'u32',2509 operational: 'u32',2510 mandatory: 'u32'2511 },2512 /**2513 * Lookup312: frame_support::weights::RuntimeDbWeight2514 **/2515 FrameSupportWeightsRuntimeDbWeight: {2516 read: 'u64',2517 write: 'u64'2518 },2519 /**2520 * Lookup313: sp_version::RuntimeVersion2521 **/2522 SpVersionRuntimeVersion: {2523 specName: 'Text',2524 implName: 'Text',2525 authoringVersion: 'u32',2526 specVersion: 'u32',2527 implVersion: 'u32',2528 apis: 'Vec<([u8;8],u32)>',2529 transactionVersion: 'u32',2530 stateVersion: 'u8'2531 },2532 /**2533 * Lookup317: frame_system::pallet::Error<T>2534 **/2535 FrameSystemError: {2536 _enum: ['InvalidSpecName', 'SpecVersionNeedsToIncrease', 'FailedToExtractRuntimeVersion', 'NonDefaultComposite', 'NonZeroRefCount', 'CallFiltered']2537 },2538 /**2539 * Lookup319: orml_vesting::module::Error<T>2540 **/2541 OrmlVestingModuleError: {2542 _enum: ['ZeroVestingPeriod', 'ZeroVestingPeriodCount', 'InsufficientBalanceToLock', 'TooManyVestingSchedules', 'AmountLow', 'MaxVestingSchedulesExceeded']2543 },2544 /**2545 * Lookup321: cumulus_pallet_xcmp_queue::InboundChannelDetails2546 **/2547 CumulusPalletXcmpQueueInboundChannelDetails: {2548 sender: 'u32',2549 state: 'CumulusPalletXcmpQueueInboundState',2550 messageMetadata: 'Vec<(u32,PolkadotParachainPrimitivesXcmpMessageFormat)>'2551 },2552 /**2553 * Lookup322: cumulus_pallet_xcmp_queue::InboundState2554 **/2555 CumulusPalletXcmpQueueInboundState: {2556 _enum: ['Ok', 'Suspended']2557 },2558 /**2559 * Lookup325: polkadot_parachain::primitives::XcmpMessageFormat2560 **/2561 PolkadotParachainPrimitivesXcmpMessageFormat: {2562 _enum: ['ConcatenatedVersionedXcm', 'ConcatenatedEncodedBlob', 'Signals']2563 },2564 /**2565 * Lookup328: cumulus_pallet_xcmp_queue::OutboundChannelDetails2566 **/2567 CumulusPalletXcmpQueueOutboundChannelDetails: {2568 recipient: 'u32',2569 state: 'CumulusPalletXcmpQueueOutboundState',2570 signalsExist: 'bool',2571 firstIndex: 'u16',2572 lastIndex: 'u16'2573 },2574 /**2575 * Lookup329: cumulus_pallet_xcmp_queue::OutboundState2576 **/2577 CumulusPalletXcmpQueueOutboundState: {2578 _enum: ['Ok', 'Suspended']2579 },2580 /**2581 * Lookup331: cumulus_pallet_xcmp_queue::QueueConfigData2582 **/2583 CumulusPalletXcmpQueueQueueConfigData: {2584 suspendThreshold: 'u32',2585 dropThreshold: 'u32',2586 resumeThreshold: 'u32',2587 thresholdWeight: 'u64',2588 weightRestrictDecay: 'u64',2589 xcmpMaxIndividualWeight: 'u64'2590 },2591 /**2592 * Lookup333: cumulus_pallet_xcmp_queue::pallet::Error<T>2593 **/2594 CumulusPalletXcmpQueueError: {2595 _enum: ['FailedToSend', 'BadXcmOrigin', 'BadXcm', 'BadOverweightIndex', 'WeightOverLimit']2596 },2597 /**2598 * Lookup334: pallet_xcm::pallet::Error<T>2599 **/2600 PalletXcmError: {2601 _enum: ['Unreachable', 'SendFailure', 'Filtered', 'UnweighableMessage', 'DestinationNotInvertible', 'Empty', 'CannotReanchor', 'TooManyAssets', 'InvalidOrigin', 'BadVersion', 'BadLocation', 'NoSubscription', 'AlreadySubscribed']2602 },2603 /**2604 * Lookup335: cumulus_pallet_xcm::pallet::Error<T>2605 **/2606 CumulusPalletXcmError: 'Null',2607 /**2608 * Lookup336: cumulus_pallet_dmp_queue::ConfigData2609 **/2610 CumulusPalletDmpQueueConfigData: {2611 maxIndividual: 'u64'2612 },2613 /**2614 * Lookup337: cumulus_pallet_dmp_queue::PageIndexData2615 **/2616 CumulusPalletDmpQueuePageIndexData: {2617 beginUsed: 'u32',2618 endUsed: 'u32',2619 overweightCount: 'u64'2620 },2621 /**2622 * Lookup340: cumulus_pallet_dmp_queue::pallet::Error<T>2623 **/2624 CumulusPalletDmpQueueError: {2625 _enum: ['Unknown', 'OverLimit']2626 },2627 /**2628 * Lookup344: pallet_unique::Error<T>2629 **/2630 PalletUniqueError: {2631 _enum: ['CollectionDecimalPointLimitExceeded', 'ConfirmUnsetSponsorFail', 'EmptyArgument', 'RepartitionCalledOnNonRefungibleCollection']2632 },2633 /**2634 * Lookup347: pallet_unique_scheduler::ScheduledV3<frame_support::traits::schedule::MaybeHashed<opal_runtime::Call, primitive_types::H256>, BlockNumber, opal_runtime::OriginCaller, sp_core::crypto::AccountId32>2635 **/2636 PalletUniqueSchedulerScheduledV3: {2637 maybeId: 'Option<[u8;16]>',2638 priority: 'u8',2639 call: 'FrameSupportScheduleMaybeHashed',2640 maybePeriodic: 'Option<(u32,u32)>',2641 origin: 'OpalRuntimeOriginCaller'2642 },2643 /**2644 * Lookup348: opal_runtime::OriginCaller2645 **/2646 OpalRuntimeOriginCaller: {2647 _enum: {2648 __Unused0: 'Null',2649 __Unused1: 'Null',2650 __Unused2: 'Null',2651 __Unused3: 'Null',2652 Void: 'SpCoreVoid',2653 __Unused5: 'Null',2654 __Unused6: 'Null',2655 __Unused7: 'Null',2656 __Unused8: 'Null',2657 __Unused9: 'Null',2658 __Unused10: 'Null',2659 __Unused11: 'Null',2660 __Unused12: 'Null',2661 __Unused13: 'Null',2662 __Unused14: 'Null',2663 __Unused15: 'Null',2664 __Unused16: 'Null',2665 __Unused17: 'Null',2666 __Unused18: 'Null',2667 __Unused19: 'Null',2668 __Unused20: 'Null',2669 __Unused21: 'Null',2670 __Unused22: 'Null',2671 __Unused23: 'Null',2672 __Unused24: 'Null',2673 __Unused25: 'Null',2674 __Unused26: 'Null',2675 __Unused27: 'Null',2676 __Unused28: 'Null',2677 __Unused29: 'Null',2678 __Unused30: 'Null',2679 __Unused31: 'Null',2680 __Unused32: 'Null',2681 __Unused33: 'Null',2682 __Unused34: 'Null',2683 __Unused35: 'Null',2684 system: 'FrameSupportDispatchRawOrigin',2685 __Unused37: 'Null',2686 __Unused38: 'Null',2687 __Unused39: 'Null',2688 __Unused40: 'Null',2689 __Unused41: 'Null',2690 __Unused42: 'Null',2691 __Unused43: 'Null',2692 __Unused44: 'Null',2693 __Unused45: 'Null',2694 __Unused46: 'Null',2695 __Unused47: 'Null',2696 __Unused48: 'Null',2697 __Unused49: 'Null',2698 __Unused50: 'Null',2699 PolkadotXcm: 'PalletXcmOrigin',2700 CumulusXcm: 'CumulusPalletXcmOrigin',2701 __Unused53: 'Null',2702 __Unused54: 'Null',2703 __Unused55: 'Null',2704 __Unused56: 'Null',2705 __Unused57: 'Null',2706 __Unused58: 'Null',2707 __Unused59: 'Null',2708 __Unused60: 'Null',2709 __Unused61: 'Null',2710 __Unused62: 'Null',2711 __Unused63: 'Null',2712 __Unused64: 'Null',2713 __Unused65: 'Null',2714 __Unused66: 'Null',2715 __Unused67: 'Null',2716 __Unused68: 'Null',2717 __Unused69: 'Null',2718 __Unused70: 'Null',2719 __Unused71: 'Null',2720 __Unused72: 'Null',2721 __Unused73: 'Null',2722 __Unused74: 'Null',2723 __Unused75: 'Null',2724 __Unused76: 'Null',2725 __Unused77: 'Null',2726 __Unused78: 'Null',2727 __Unused79: 'Null',2728 __Unused80: 'Null',2729 __Unused81: 'Null',2730 __Unused82: 'Null',2731 __Unused83: 'Null',2732 __Unused84: 'Null',2733 __Unused85: 'Null',2734 __Unused86: 'Null',2735 __Unused87: 'Null',2736 __Unused88: 'Null',2737 __Unused89: 'Null',2738 __Unused90: 'Null',2739 __Unused91: 'Null',2740 __Unused92: 'Null',2741 __Unused93: 'Null',2742 __Unused94: 'Null',2743 __Unused95: 'Null',2744 __Unused96: 'Null',2745 __Unused97: 'Null',2746 __Unused98: 'Null',2747 __Unused99: 'Null',2748 __Unused100: 'Null',2749 Ethereum: 'PalletEthereumRawOrigin'2750 }2751 },2752 /**2753 * Lookup349: frame_support::dispatch::RawOrigin<sp_core::crypto::AccountId32>2754 **/2755 FrameSupportDispatchRawOrigin: {2756 _enum: {2757 Root: 'Null',2758 Signed: 'AccountId32',2759 None: 'Null'2760 }2761 },2762 /**2763 * Lookup350: pallet_xcm::pallet::Origin2764 **/2765 PalletXcmOrigin: {2766 _enum: {2767 Xcm: 'XcmV1MultiLocation',2768 Response: 'XcmV1MultiLocation'2769 }2770 },2771 /**2772 * Lookup351: cumulus_pallet_xcm::pallet::Origin2773 **/2774 CumulusPalletXcmOrigin: {2775 _enum: {2776 Relay: 'Null',2777 SiblingParachain: 'u32'2778 }2779 },2780 /**2781 * Lookup352: pallet_ethereum::RawOrigin2782 **/2783 PalletEthereumRawOrigin: {2784 _enum: {2785 EthereumTransaction: 'H160'2786 }2787 },2788 /**2789 * Lookup353: sp_core::Void2790 **/2791 SpCoreVoid: 'Null',2792 /**2793 * Lookup354: pallet_unique_scheduler::pallet::Error<T>2794 **/2795 PalletUniqueSchedulerError: {2796 _enum: ['FailedToSchedule', 'NotFound', 'TargetBlockNumberInPast', 'RescheduleNoChange']2797 },2798 /**2799 * Lookup355: up_data_structs::Collection<sp_core::crypto::AccountId32>2800 **/2801 UpDataStructsCollection: {2802 owner: 'AccountId32',2803 mode: 'UpDataStructsCollectionMode',2804 name: 'Vec<u16>',2805 description: 'Vec<u16>',2806 tokenPrefix: 'Bytes',2807 sponsorship: 'UpDataStructsSponsorshipState',2808 limits: 'UpDataStructsCollectionLimits',2809 permissions: 'UpDataStructsCollectionPermissions',2810 externalCollection: 'bool'2811 },2812 /**2813 * Lookup356: up_data_structs::SponsorshipState<sp_core::crypto::AccountId32>2814 **/2815 UpDataStructsSponsorshipState: {2816 _enum: {2817 Disabled: 'Null',2818 Unconfirmed: 'AccountId32',2819 Confirmed: 'AccountId32'2820 }2821 },2822 /**2823 * Lookup357: up_data_structs::Properties2824 **/2825 UpDataStructsProperties: {2826 map: 'UpDataStructsPropertiesMapBoundedVec',2827 consumedSpace: 'u32',2828 spaceLimit: 'u32'2829 },2830 /**2831 * Lookup358: up_data_structs::PropertiesMap<frame_support::storage::bounded_vec::BoundedVec<T, S>>2832 **/2833 UpDataStructsPropertiesMapBoundedVec: 'BTreeMap<Bytes, Bytes>',2834 /**2835 * Lookup363: up_data_structs::PropertiesMap<up_data_structs::PropertyPermission>2836 **/2837 UpDataStructsPropertiesMapPropertyPermission: 'BTreeMap<Bytes, UpDataStructsPropertyPermission>',2838 /**2839 * Lookup370: up_data_structs::CollectionStats2840 **/2841 UpDataStructsCollectionStats: {2842 created: 'u32',2843 destroyed: 'u32',2844 alive: 'u32'2845 },2846 /**2847 * Lookup371: up_data_structs::TokenChild2848 **/2849 UpDataStructsTokenChild: {2850 token: 'u32',2851 collection: 'u32'2852 },2853 /**2854 * Lookup372: PhantomType::up_data_structs<T>2855 **/2856 PhantomTypeUpDataStructs: '[(UpDataStructsTokenData,UpDataStructsRpcCollection,RmrkTraitsCollectionCollectionInfo,RmrkTraitsNftNftInfo,RmrkTraitsResourceResourceInfo,RmrkTraitsPropertyPropertyInfo,RmrkTraitsBaseBaseInfo,RmrkTraitsPartPartType,RmrkTraitsTheme,RmrkTraitsNftNftChild);0]',2857 /**2858 * Lookup374: up_data_structs::TokenData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>2859 **/2860 UpDataStructsTokenData: {2861 properties: 'Vec<UpDataStructsProperty>',2862 owner: 'Option<PalletEvmAccountBasicCrossAccountIdRepr>',2863 pieces: 'u128'2864 },2865 /**2866 * Lookup376: up_data_structs::RpcCollection<sp_core::crypto::AccountId32>2867 **/2868 UpDataStructsRpcCollection: {2869 owner: 'AccountId32',2870 mode: 'UpDataStructsCollectionMode',2871 name: 'Vec<u16>',2872 description: 'Vec<u16>',2873 tokenPrefix: 'Bytes',2874 sponsorship: 'UpDataStructsSponsorshipState',2875 limits: 'UpDataStructsCollectionLimits',2876 permissions: 'UpDataStructsCollectionPermissions',2877 tokenPropertyPermissions: 'Vec<UpDataStructsPropertyKeyPermission>',2878 properties: 'Vec<UpDataStructsProperty>',2879 readOnly: 'bool'2880 },2881 /**2882 * Lookup377: rmrk_traits::collection::CollectionInfo<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<T, S>, sp_core::crypto::AccountId32>2883 **/2884 RmrkTraitsCollectionCollectionInfo: {2885 issuer: 'AccountId32',2886 metadata: 'Bytes',2887 max: 'Option<u32>',2888 symbol: 'Bytes',2889 nftsCount: 'u32'2890 },2891 /**2892 * Lookup378: rmrk_traits::nft::NftInfo<sp_core::crypto::AccountId32, sp_arithmetic::per_things::Permill, frame_support::storage::bounded_vec::BoundedVec<T, S>>2893 **/2894 RmrkTraitsNftNftInfo: {2895 owner: 'RmrkTraitsNftAccountIdOrCollectionNftTuple',2896 royalty: 'Option<RmrkTraitsNftRoyaltyInfo>',2897 metadata: 'Bytes',2898 equipped: 'bool',2899 pending: 'bool'2900 },2901 /**2902 * Lookup380: rmrk_traits::nft::RoyaltyInfo<sp_core::crypto::AccountId32, sp_arithmetic::per_things::Permill>2903 **/2904 RmrkTraitsNftRoyaltyInfo: {2905 recipient: 'AccountId32',2906 amount: 'Permill'2907 },2908 /**2909 * Lookup381: rmrk_traits::resource::ResourceInfo<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<T, S>>2910 **/2911 RmrkTraitsResourceResourceInfo: {2912 id: 'u32',2913 resource: 'RmrkTraitsResourceResourceTypes',2914 pending: 'bool',2915 pendingRemoval: 'bool'2916 },2917 /**2918 * Lookup382: rmrk_traits::property::PropertyInfo<frame_support::storage::bounded_vec::BoundedVec<T, S>, frame_support::storage::bounded_vec::BoundedVec<T, S>>2919 **/2920 RmrkTraitsPropertyPropertyInfo: {2921 key: 'Bytes',2922 value: 'Bytes'2923 },2924 /**2925 * Lookup383: rmrk_traits::base::BaseInfo<sp_core::crypto::AccountId32, frame_support::storage::bounded_vec::BoundedVec<T, S>>2926 **/2927 RmrkTraitsBaseBaseInfo: {2928 issuer: 'AccountId32',2929 baseType: 'Bytes',2930 symbol: 'Bytes'2931 },2932 /**2933 * Lookup384: rmrk_traits::nft::NftChild2934 **/2935 RmrkTraitsNftNftChild: {2936 collectionId: 'u32',2937 nftId: 'u32'2938 },2939 /**2940 * Lookup386: pallet_common::pallet::Error<T>2941 **/2942 PalletCommonError: {2943 _enum: ['CollectionNotFound', 'MustBeTokenOwner', 'NoPermission', 'CantDestroyNotEmptyCollection', 'PublicMintingNotAllowed', 'AddressNotInAllowlist', 'CollectionNameLimitExceeded', 'CollectionDescriptionLimitExceeded', 'CollectionTokenPrefixLimitExceeded', 'TotalCollectionsLimitExceeded', 'CollectionAdminCountExceeded', 'CollectionLimitBoundsExceeded', 'OwnerPermissionsCantBeReverted', 'TransferNotAllowed', 'AccountTokenLimitExceeded', 'CollectionTokenLimitExceeded', 'MetadataFlagFrozen', 'TokenNotFound', 'TokenValueTooLow', 'ApprovedValueTooLow', 'CantApproveMoreThanOwned', 'AddressIsZero', 'UnsupportedOperation', 'NotSufficientFounds', 'UserIsNotAllowedToNest', 'SourceCollectionIsNotAllowedToNest', 'CollectionFieldSizeExceeded', 'NoSpaceForProperty', 'PropertyLimitReached', 'PropertyKeyIsTooLong', 'InvalidCharacterInPropertyKey', 'EmptyPropertyKey', 'CollectionIsExternal', 'CollectionIsInternal']2944 },2945 /**2946 * Lookup388: pallet_fungible::pallet::Error<T>2947 **/2948 PalletFungibleError: {2949 _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed']2950 },2951 /**2952 * Lookup389: pallet_refungible::ItemData2953 **/2954 PalletRefungibleItemData: {2955 constData: 'Bytes'2956 },2957 /**2958 * Lookup393: pallet_refungible::pallet::Error<T>2959 **/2960 PalletRefungibleError: {2961 _enum: ['NotRefungibleDataUsedToMintFungibleCollectionToken', 'WrongRefungiblePieces', 'RepartitionWhileNotOwningAllPieces', 'RefungibleDisallowsNesting', 'SettingPropertiesNotAllowed']2962 },2963 /**2964 * Lookup394: pallet_nonfungible::ItemData<pallet_evm::account::BasicCrossAccountIdRepr<sp_core::crypto::AccountId32>>2965 **/2966 PalletNonfungibleItemData: {2967 owner: 'PalletEvmAccountBasicCrossAccountIdRepr'2968 },2969 /**2970 * Lookup396: up_data_structs::PropertyScope2971 **/2972 UpDataStructsPropertyScope: {2973 _enum: ['None', 'Rmrk']2974 },2975 /**2976 * Lookup398: pallet_nonfungible::pallet::Error<T>2977 **/2978 PalletNonfungibleError: {2979 _enum: ['NotNonfungibleDataUsedToMintFungibleCollectionToken', 'NonfungibleItemsHaveNoAmount', 'CantBurnNftWithChildren']2980 },2981 /**2982 * Lookup399: pallet_structure::pallet::Error<T>2983 **/2984 PalletStructureError: {2985 _enum: ['OuroborosDetected', 'DepthLimit', 'BreadthLimit', 'TokenNotFound']2986 },2987 /**2988 * Lookup400: pallet_rmrk_core::pallet::Error<T>2989 **/2990 PalletRmrkCoreError: {2991 _enum: ['CorruptedCollectionType', 'NftTypeEncodeError', 'RmrkPropertyKeyIsTooLong', 'RmrkPropertyValueIsTooLong', 'RmrkPropertyIsNotFound', 'UnableToDecodeRmrkData', 'CollectionNotEmpty', 'NoAvailableCollectionId', 'NoAvailableNftId', 'CollectionUnknown', 'NoPermission', 'NonTransferable', 'CollectionFullOrLocked', 'ResourceDoesntExist', 'CannotSendToDescendentOrSelf', 'CannotAcceptNonOwnedNft', 'CannotRejectNonOwnedNft', 'CannotRejectNonPendingNft', 'ResourceNotPending', 'NoAvailableResourceId']2992 },2993 /**2994 * Lookup402: pallet_rmrk_equip::pallet::Error<T>2995 **/2996 PalletRmrkEquipError: {2997 _enum: ['PermissionError', 'NoAvailableBaseId', 'NoAvailablePartId', 'BaseDoesntExist', 'NeedsDefaultThemeFirst', 'PartDoesntExist', 'NoEquippableOnFixedPart']2998 },2999 /**3000 * Lookup405: pallet_evm::pallet::Error<T>3001 **/3002 PalletEvmError: {3003 _enum: ['BalanceLow', 'FeeOverflow', 'PaymentOverflow', 'WithdrawFailed', 'GasPriceTooLow', 'InvalidNonce']3004 },3005 /**3006 * Lookup408: fp_rpc::TransactionStatus3007 **/3008 FpRpcTransactionStatus: {3009 transactionHash: 'H256',3010 transactionIndex: 'u32',3011 from: 'H160',3012 to: 'Option<H160>',3013 contractAddress: 'Option<H160>',3014 logs: 'Vec<EthereumLog>',3015 logsBloom: 'EthbloomBloom'3016 },3017 /**3018 * Lookup410: ethbloom::Bloom3019 **/3020 EthbloomBloom: '[u8;256]',3021 /**3022 * Lookup412: ethereum::receipt::ReceiptV33023 **/3024 EthereumReceiptReceiptV3: {3025 _enum: {3026 Legacy: 'EthereumReceiptEip658ReceiptData',3027 EIP2930: 'EthereumReceiptEip658ReceiptData',3028 EIP1559: 'EthereumReceiptEip658ReceiptData'3029 }3030 },3031 /**3032 * Lookup413: ethereum::receipt::EIP658ReceiptData3033 **/3034 EthereumReceiptEip658ReceiptData: {3035 statusCode: 'u8',3036 usedGas: 'U256',3037 logsBloom: 'EthbloomBloom',3038 logs: 'Vec<EthereumLog>'3039 },3040 /**3041 * Lookup414: ethereum::block::Block<ethereum::transaction::TransactionV2>3042 **/3043 EthereumBlock: {3044 header: 'EthereumHeader',3045 transactions: 'Vec<EthereumTransactionTransactionV2>',3046 ommers: 'Vec<EthereumHeader>'3047 },3048 /**3049 * Lookup415: ethereum::header::Header3050 **/3051 EthereumHeader: {3052 parentHash: 'H256',3053 ommersHash: 'H256',3054 beneficiary: 'H160',3055 stateRoot: 'H256',3056 transactionsRoot: 'H256',3057 receiptsRoot: 'H256',3058 logsBloom: 'EthbloomBloom',3059 difficulty: 'U256',3060 number: 'U256',3061 gasLimit: 'U256',3062 gasUsed: 'U256',3063 timestamp: 'u64',3064 extraData: 'Bytes',3065 mixHash: 'H256',3066 nonce: 'EthereumTypesHashH64'3067 },3068 /**3069 * Lookup416: ethereum_types::hash::H643070 **/3071 EthereumTypesHashH64: '[u8;8]',3072 /**3073 * Lookup421: pallet_ethereum::pallet::Error<T>3074 **/3075 PalletEthereumError: {3076 _enum: ['InvalidSignature', 'PreLogExists']3077 },3078 /**3079 * Lookup422: pallet_evm_coder_substrate::pallet::Error<T>3080 **/3081 PalletEvmCoderSubstrateError: {3082 _enum: ['OutOfGas', 'OutOfFund']3083 },3084 /**3085 * Lookup423: pallet_evm_contract_helpers::SponsoringModeT3086 **/3087 PalletEvmContractHelpersSponsoringModeT: {3088 _enum: ['Disabled', 'Allowlisted', 'Generous']3089 },3090 /**3091 * Lookup425: pallet_evm_contract_helpers::pallet::Error<T>3092 **/3093 PalletEvmContractHelpersError: {3094 _enum: ['NoPermission']3095 },3096 /**3097 * Lookup426: pallet_evm_migration::pallet::Error<T>3098 **/3099 PalletEvmMigrationError: {3100 _enum: ['AccountNotEmpty', 'AccountIsNotMigrating']3101 },3102 /**3103 * Lookup428: sp_runtime::MultiSignature3104 **/3105 SpRuntimeMultiSignature: {3106 _enum: {3107 Ed25519: 'SpCoreEd25519Signature',3108 Sr25519: 'SpCoreSr25519Signature',3109 Ecdsa: 'SpCoreEcdsaSignature'3110 }3111 },3112 /**3113 * Lookup429: sp_core::ed25519::Signature3114 **/3115 SpCoreEd25519Signature: '[u8;64]',3116 /**3117 * Lookup431: sp_core::sr25519::Signature3118 **/3119 SpCoreSr25519Signature: '[u8;64]',3120 /**3121 * Lookup432: sp_core::ecdsa::Signature3122 **/3123 SpCoreEcdsaSignature: '[u8;65]',3124 /**3125 * Lookup435: frame_system::extensions::check_spec_version::CheckSpecVersion<T>3126 **/3127 FrameSystemExtensionsCheckSpecVersion: 'Null',3128 /**3129 * Lookup436: frame_system::extensions::check_genesis::CheckGenesis<T>3130 **/3131 FrameSystemExtensionsCheckGenesis: 'Null',3132 /**3133 * Lookup439: frame_system::extensions::check_nonce::CheckNonce<T>3134 **/3135 FrameSystemExtensionsCheckNonce: 'Compact<u32>',3136 /**3137 * Lookup440: frame_system::extensions::check_weight::CheckWeight<T>3138 **/3139 FrameSystemExtensionsCheckWeight: 'Null',3140 /**3141 * Lookup441: pallet_template_transaction_payment::ChargeTransactionPayment<opal_runtime::Runtime>3142 **/3143 PalletTemplateTransactionPaymentChargeTransactionPayment: 'Compact<u128>',3144 /**3145 * Lookup442: opal_runtime::Runtime3146 **/3147 OpalRuntimeRuntime: 'Null',3148 /**3149 * Lookup443: pallet_ethereum::FakeTransactionFinalizer<opal_runtime::Runtime>3150 **/3151 PalletEthereumFakeTransactionFinalizer: 'Null'3152};tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -1629,6 +1629,7 @@
export interface UpDataStructsCreateReFungibleData extends Struct {
readonly constData: Bytes;
readonly pieces: u128;
+ readonly properties: Vec<UpDataStructsProperty>;
}
/** @name UpDataStructsCreateItemExData (193) */
@@ -1654,6 +1655,7 @@
export interface UpDataStructsCreateRefungibleExData extends Struct {
readonly constData: Bytes;
readonly users: BTreeMap<PalletEvmAccountBasicCrossAccountIdRepr, u128>;
+ readonly properties: Vec<UpDataStructsProperty>;
}
/** @name PalletUniqueSchedulerCall (204) */
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -63,6 +63,11 @@
[collectionParam, tokenParam],
`Option<${CROSS_ACCOUNT_ID_TYPE}>`,
),
+ tokenOwners: fun(
+ 'Returns 10 tokens owners in no particular order',
+ [collectionParam, tokenParam],
+ `Vec<${CROSS_ACCOUNT_ID_TYPE}>`
+ ),
tokenChildren: fun(
'Get tokens nested directly into the token',
[collectionParam, tokenParam],
@@ -85,6 +90,17 @@
'Vec<UpDataStructsPropertyKeyPermission>',
),
+ constMetadata: fun(
+ 'Get token constant metadata',
+ [collectionParam, tokenParam],
+ 'Vec<u8>',
+ ),
+ variableMetadata: fun(
+ 'Get token variable metadata',
+ [collectionParam, tokenParam],
+ 'Vec<u8>',
+ ),
+
tokenData: fun(
'Get token data, including properties, optionally limited to the provided keys, and total pieces for an RFT',
[collectionParam, tokenParam, propertyKeysParam],
@@ -128,7 +144,11 @@
'bool',
),
- lastTokenId: fun('Get the last token ID created in a collection', [collectionParam], 'u32'),
+ lastTokenId: fun(
+ 'Get the last token ID created in a collection',
+ [collectionParam],
+ 'u32',
+ ),
collectionById: fun(
'Get a collection by the specified ID',
[collectionParam],
tests/src/nesting/properties.test.tsdiffbeforeafterboth--- a/tests/src/nesting/properties.test.ts
+++ b/tests/src/nesting/properties.test.ts
@@ -2,6 +2,7 @@
import usingApi, {executeTransaction} from '../substrate/substrate-api';
import {
addCollectionAdminExpectSuccess,
+ CollectionMode,
createCollectionExpectSuccess,
setCollectionPermissionsExpectSuccess,
createItemExpectSuccess,
@@ -23,9 +24,9 @@
});
});
- it('Makes sure collectionById supplies required fields', async () => {
+ async function testMakeSureSuppliesRequired(mode: CollectionMode) {
await usingApi(async api => {
- const collectionId = await createCollectionExpectSuccess();
+ const collectionId = await createCollectionExpectSuccess({mode: mode});
const collectionOption = await api.rpc.unique.collectionById(collectionId);
expect(collectionOption.isSome).to.be.true;
@@ -57,7 +58,15 @@
expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);
expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties);
});
+ }
+
+ it('Makes sure collectionById supplies required fields for NFT', async () => {
+ await testMakeSureSuppliesRequired({type: 'NFT'});
});
+
+ it('Makes sure collectionById supplies required fields for ReFungible', async () => {
+ await testMakeSureSuppliesRequired({type: 'ReFungible'});
+ });
});
// ---------- COLLECTION PROPERTIES
@@ -79,9 +88,10 @@
});
});
- it('Sets properties for a collection', async () => {
+
+ async function testSetsPropertiesForCollection(mode: string) {
await usingApi(async api => {
- const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));
+ const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));
const {collectionId} = getCreateCollectionResult(events);
// As owner
@@ -106,48 +116,54 @@
{key: 'black_hole', value: ''},
]);
});
+ }
+ it('Sets properties for a NFT collection', async () => {
+ await testSetsPropertiesForCollection('NFT');
});
+ it('Sets properties for a ReFungible collection', async () => {
+ await testSetsPropertiesForCollection('ReFungible');
+ });
- it('Check valid names for collection properties keys', async () => {
+ async function testCheckValidNames(mode: string) {
await usingApi(async api => {
- const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: 'NFT'}));
+ const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode}));
const {collectionId} = getCreateCollectionResult(events);
-
+
// alpha symbols
await expect(executeTransaction(
api,
bob,
api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]),
)).to.not.be.rejected;
-
+
// numeric symbols
await expect(executeTransaction(
api,
bob,
api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]),
)).to.not.be.rejected;
-
+
// underscore symbol
await expect(executeTransaction(
api,
bob,
api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]),
)).to.not.be.rejected;
-
+
// dash symbol
await expect(executeTransaction(
api,
bob,
api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]),
)).to.not.be.rejected;
-
+
// underscore symbol
await expect(executeTransaction(
api,
bob,
api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]),
)).to.not.be.rejected;
-
+
const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs'];
const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman();
expect(properties).to.be.deep.equal([
@@ -158,54 +174,72 @@
{key: 'build.rs', value: ''},
]);
});
+ }
+ it('Check valid names for NFT collection properties keys', async () => {
+ await testCheckValidNames('NFT');
+ });
+ it('Check valid names for ReFungible collection properties keys', async () => {
+ await testCheckValidNames('ReFungible');
});
- it('Changes properties of a collection', async () => {
+ async function testChangesProperties(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]),
)).to.not.be.rejected;
-
+
// Mutate the properties
await expect(executeTransaction(
api,
alice,
api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]),
)).to.not.be.rejected;
-
+
const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
expect(properties).to.be.deep.equal([
{key: 'electron', value: 'bonded'},
{key: 'black_hole', value: 'LIGO'},
]);
});
+ }
+ it('Changes properties of a NFT collection', async () => {
+ await testChangesProperties({type: 'NFT'});
+ });
+ it('Changes properties of a ReFungible collection', async () => {
+ await testChangesProperties({type: 'ReFungible'});
});
- it('Deletes properties of a collection', async () => {
+ async function testDeleteProperties(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]),
)).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.deleteCollectionProperties(collection, ['electron']),
)).to.not.be.rejected;
-
+
const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman();
expect(properties).to.be.deep.equal([
{key: 'black_hole', value: 'LIGO'},
]);
- });
+ });
+ }
+ it('Deletes properties of a NFT collection', async () => {
+ await testDeleteProperties({type: 'NFT'});
+ });
+ it('Deletes properties of a ReFungible collection', async () => {
+ await testDeleteProperties({type: 'ReFungible'});
});
});
@@ -217,10 +251,10 @@
});
});
- it('Fails to set properties in a collection if not its onwer/administrator', async () => {
+ async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
await expect(executeTransaction(
api,
bob,
@@ -230,14 +264,20 @@
const properties = (await api.query.common.collectionProperties(collection)).toJSON();
expect(properties.map).to.be.empty;
expect(properties.consumedSpace).to.equal(0);
- });
+ });
+ }
+ it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => {
+ await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'});
});
+ it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async () => {
+ await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'});
+ });
- it('Fails to set properties that exceed the limits', async () => {
+ async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
+ const collection = await createCollectionExpectSuccess({mode: mode});
const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number;
-
+
// Mute the general tx parsing error, too many bytes to process
{
console.error = () => {};
@@ -247,10 +287,10 @@
api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]),
)).to.be.rejected;
}
-
+
let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON();
expect(properties).to.be.empty;
-
+
await expect(executeTransaction(
api,
alice,
@@ -259,16 +299,22 @@
{key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))},
]),
)).to.be.rejectedWith(/common\.NoSpaceForProperty/);
-
+
properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON();
expect(properties).to.be.empty;
- });
+ });
+ }
+ it('Fails to set properties that exceed the limits (NFT)', async () => {
+ await testFailsSetPropertiesThatExeedLimits({type: 'NFT'});
+ });
+ it('Fails to set properties that exceed the limits (ReFungible)', async () => {
+ await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'});
});
- it('Fails to set more properties than it is allowed', async () => {
+ async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
const propertiesToBeSet = [];
for (let i = 0; i < 65; i++) {
propertiesToBeSet.push({
@@ -276,29 +322,35 @@
value: Math.random() > 0.5 ? 'high' : 'low',
});
}
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setCollectionProperties(collection, propertiesToBeSet),
)).to.be.rejectedWith(/common\.PropertyLimitReached/);
-
+
const properties = (await api.query.common.collectionProperties(collection)).toJSON();
expect(properties.map).to.be.empty;
expect(properties.consumedSpace).to.equal(0);
- });
+ });
+ }
+ it('Fails to set more properties than it is allowed (NFT)', async () => {
+ await testFailsSetMorePropertiesThanAllowed({type: 'NFT'});
});
-
- it('Fails to set properties with invalid names', async () => {
+ it('Fails to set more properties than it is allowed (ReFungible)', async () => {
+ await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'});
+ });
+
+ async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
const invalidProperties = [
[{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],
[{key: 'Mr/Sandman', value: 'Bring me a gene'}],
[{key: 'déjà vu', value: 'hmm...'}],
];
-
+
for (let i = 0; i < invalidProperties.length; i++) {
await expect(executeTransaction(
api,
@@ -306,13 +358,13 @@
api.tx.unique.setCollectionProperties(collection, invalidProperties[i]),
), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
}
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]),
), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);
-
+
await expect(executeTransaction(
api,
alice,
@@ -320,14 +372,14 @@
{key: 'CRISPR-Cas9', value: 'rewriting nature!'},
]),
), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
-
+
const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat('');
-
+
const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman();
expect(properties).to.be.deep.equal([
{key: 'CRISPR-Cas9', value: 'rewriting nature!'},
]);
-
+
for (let i = 0; i < invalidProperties.length; i++) {
await expect(executeTransaction(
api,
@@ -336,7 +388,13 @@
), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
}
});
+ }
+ it('Fails to set properties with invalid names (NFT)', async () => {
+ await testFailsSetPropertiesWithInvalidNames({type: 'NFT'});
});
+ it('Fails to set properties with invalid names (ReFungible)', async () => {
+ await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'});
+ });
});
// ---------- ACCESS RIGHTS
@@ -357,54 +415,66 @@
});
});
- it('Sets access rights to properties of a collection', async () => {
+ async function testSetsAccessRightsToProperties(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]),
)).to.not.be.rejected;
-
+
await addCollectionAdminExpectSuccess(alice, collection, bob.address);
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]),
)).to.not.be.rejected;
-
+
const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman();
expect(propertyRights).to.be.deep.equal([
{key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}},
{key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}},
]);
- });
+ });
+ }
+ it('Sets access rights to properties of a collection (NFT)', async () => {
+ await testSetsAccessRightsToProperties({type: 'NFT'});
});
+ it('Sets access rights to properties of a collection (ReFungible)', async () => {
+ await testSetsAccessRightsToProperties({type: 'ReFungible'});
+ });
- it('Changes access rights to properties of a collection', async () => {
+ async function testChangesAccessRightsToProperty(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]),
)).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]),
)).to.not.be.rejected;
-
+
const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
expect(propertyRights).to.be.deep.equal([
{key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
]);
});
+ }
+ it('Changes access rights to properties of a NFT collection', async () => {
+ await testChangesAccessRightsToProperty({type: 'NFT'});
});
+ it('Changes access rights to properties of a ReFungible collection', async () => {
+ await testChangesAccessRightsToProperty({type: 'ReFungible'});
+ });
});
describe('Negative Integration Test: Access Rights to Token Properties', () => {
@@ -415,25 +485,31 @@
});
});
- it('Prevents from setting access rights to properties of a collection if not an onwer/admin', async () => {
+ async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
await expect(executeTransaction(
api,
bob,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]),
)).to.be.rejectedWith(/common\.NoPermission/);
-
+
const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON();
expect(propertyRights).to.be.empty;
});
+ }
+ it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => {
+ await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'});
});
+ it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async () => {
+ await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'});
+ });
- it('Prevents from adding too many possible properties', async () => {
+ async function testPreventFromAddingTooManyPossibleProperties(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
const constitution = [];
for (let i = 0; i < 65; i++) {
constitution.push({
@@ -441,51 +517,63 @@
permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {},
});
}
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, constitution),
)).to.be.rejectedWith(/common\.PropertyLimitReached/);
-
+
const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON();
expect(propertyRights).to.be.empty;
- });
+ });
+ }
+ it('Prevents from adding too many possible properties (NFT)', async () => {
+ await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'});
});
+ it('Prevents from adding too many possible properties (ReFungible)', async () => {
+ await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'});
+ });
- it('Prevents access rights to be modified if constant', async () => {
+ async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]),
)).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]),
)).to.be.rejectedWith(/common\.NoPermission/);
-
+
const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman();
expect(propertyRights).to.deep.equal([
{key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}},
]);
- });
+ });
+ }
+ it('Prevents access rights to be modified if constant (NFT)', async () => {
+ await testPreventAccessRightsModifiedIfConstant({type: 'NFT'});
+ });
+ it('Prevents access rights to be modified if constant (ReFungible)', async () => {
+ await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'});
});
- it('Prevents adding properties with invalid names', async () => {
+ async function testPreventsAddingPropertiesWithInvalidNames(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
-
+ const collection = await createCollectionExpectSuccess({mode: mode});
+
const invalidProperties = [
[{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}],
[{key: 'G#4', permission: {tokenOwner: true}}],
[{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}],
];
-
+
for (let i = 0; i < invalidProperties.length; i++) {
await expect(executeTransaction(
api,
@@ -493,13 +581,13 @@
api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]),
), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);
}
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: '', permission: {}}]),
), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/);
-
+
const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string
await expect(executeTransaction(
api,
@@ -508,30 +596,33 @@
{key: correctKey, permission: {collectionAdmin: true}},
]),
), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected;
-
+
const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat('');
-
+
const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman();
expect(propertyRights).to.be.deep.equal([
{key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}},
]);
});
+ }
+ it('Prevents adding properties with invalid names (NFT)', async () => {
+ await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'});
+ });
+ it('Prevents adding properties with invalid names (ReFungible)', async () => {
+ await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'});
});
});
// ---------- TOKEN PROPERTIES
describe('Integration Test: Token Properties', () => {
- let collection: number;
- let token: number;
- let nestedToken: number;
let permissions: {permission: any, signers: IKeyringPair[]}[];
before(async () => {
await usingApi(async (api, privateKeyWrapper) => {
- alice = privateKeyWrapper('//Alice');
- bob = privateKeyWrapper('//Bob');
- charlie = privateKeyWrapper('//Charlie');
+ alice = privateKeyWrapper('//Alice'); // collection owner
+ bob = privateKeyWrapper('//Bob'); // collection admin
+ charlie = privateKeyWrapper('//Charlie'); // token owner
permissions = [
{permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]},
@@ -543,36 +634,34 @@
];
});
});
-
- beforeEach(async () => {
- await usingApi(async () => {
- collection = await createCollectionExpectSuccess();
- await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
-
- token = await createItemExpectSuccess(alice, collection, 'NFT');
- nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
-
- await addCollectionAdminExpectSuccess(alice, collection, bob.address);
- await transferExpectSuccess(collection, token, alice, charlie);
- });
- });
- it('Reads yet empty properties of a token', async () => {
+ async function testReadsYetEmptyProperties(mode: CollectionMode) {
await usingApi(async api => {
- const collection = await createCollectionExpectSuccess();
- const token = await createItemExpectSuccess(alice, collection, 'NFT');
+ const collection = await createCollectionExpectSuccess({mode: mode});
+ const token = await createItemExpectSuccess(alice, collection, mode.type);
const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
expect(properties.map).to.be.empty;
expect(properties.consumedSpace).to.be.equal(0);
-
+
const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties;
expect(tokenData).to.be.empty;
});
+ }
+ it('Reads yet empty properties of a token (NFT)', async () => {
+ await testReadsYetEmptyProperties({type: 'NFT'});
});
+ it('Reads yet empty properties of a token (ReFungible)', async () => {
+ await testReadsYetEmptyProperties({type: 'ReFungible'});
+ });
- it('Assigns properties to a token according to permissions', async () => {
+ async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) {
await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: mode});
+ const token = await createItemExpectSuccess(alice, collection, mode.type);
+ await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+ await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
+
const propertyKeys: string[] = [];
let i = 0;
for (const permission of permissions) {
@@ -603,10 +692,21 @@
expect(tokensData[i].value).to.be.equal('Serotonin increase');
}
});
+ }
+ it('Assigns properties to a token according to permissions (NFT)', async () => {
+ await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1);
+ });
+ it('Assigns properties to a token according to permissions (ReFungible)', async () => {
+ await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100);
});
- it('Changes properties of a token according to permissions', async () => {
+ async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) {
await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: mode});
+ const token = await createItemExpectSuccess(alice, collection, mode.type);
+ await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+ await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
+
const propertyKeys: string[] = [];
let i = 0;
for (const permission of permissions) {
@@ -615,29 +715,29 @@
for (const signer of permission.signers) {
const key = i + '_' + signer.address;
propertyKeys.push(key);
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]),
), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
signer,
api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]),
), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
signer,
api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]),
), `on changing property ${i} by ${signer.address}`).to.not.be.rejected;
}
-
+
i++;
}
-
+
const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[];
const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[];
for (let i = 0; i < properties.length; i++) {
@@ -645,32 +745,43 @@
expect(tokensData[i].value).to.be.equal('Serotonin stable');
}
});
+ }
+ it('Changes properties of a token according to permissions (NFT)', async () => {
+ await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1);
});
+ it('Changes properties of a token according to permissions (ReFungible)', async () => {
+ await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100);
+ });
- it('Deletes properties of a token according to permissions', async () => {
+ async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) {
await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: mode});
+ const token = await createItemExpectSuccess(alice, collection, mode.type);
+ await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+ await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
+
const propertyKeys: string[] = [];
let i = 0;
-
+
for (const permission of permissions) {
if (!permission.permission.mutable) continue;
for (const signer of permission.signers) {
const key = i + '_' + signer.address;
propertyKeys.push(key);
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]),
), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
signer,
api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]),
), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
signer,
@@ -680,17 +791,30 @@
i++;
}
-
+
const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[];
expect(properties).to.be.empty;
const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[];
expect(tokensData).to.be.empty;
expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0);
});
+ }
+ it('Deletes properties of a token according to permissions (NFT)', async () => {
+ await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1);
});
+ it('Deletes properties of a token according to permissions (ReFungible)', async () => {
+ await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100);
+ });
it('Assigns properties to a nested token according to permissions', async () => {
await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
+ const token = await createItemExpectSuccess(alice, collection, 'NFT');
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
+ await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+ await transferExpectSuccess(collection, token, alice, charlie);
+
const propertyKeys: string[] = [];
let i = 0;
for (const permission of permissions) {
@@ -725,6 +849,13 @@
it('Changes properties of a nested token according to permissions', async () => {
await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
+ const token = await createItemExpectSuccess(alice, collection, 'NFT');
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
+ await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+ await transferExpectSuccess(collection, token, alice, charlie);
+
const propertyKeys: string[] = [];
let i = 0;
for (const permission of permissions) {
@@ -767,6 +898,13 @@
it('Deletes properties of a nested token according to permissions', async () => {
await usingApi(async api => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});
+ const token = await createItemExpectSuccess(alice, collection, 'NFT');
+ const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)});
+ await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+ await transferExpectSuccess(collection, token, alice, charlie);
+
const propertyKeys: string[] = [];
let i = 0;
@@ -832,11 +970,11 @@
});
});
- beforeEach(async () => {
- collection = await createCollectionExpectSuccess();
- token = await createItemExpectSuccess(alice, collection, 'NFT');
+ async function prepare(mode: CollectionMode, pieces: number) {
+ collection = await createCollectionExpectSuccess({mode: mode});
+ token = await createItemExpectSuccess(alice, collection, mode.type);
await addCollectionAdminExpectSuccess(alice, collection, bob.address);
- await transferExpectSuccess(collection, token, alice, charlie);
+ await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type);
await usingApi(async api => {
let i = 0;
@@ -848,71 +986,89 @@
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]),
), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
signer,
api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]),
), `on adding property ${i} by ${signer.address}`).to.not.be.rejected;
-
+
i++;
}
-
+
originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number;
- });
- });
+ });
+ }
- it('Forbids changing/deleting properties of a token if the user is outside of permissions', async () => {
+ async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) {
+ await prepare(mode, pieces);
+
await usingApi(async api => {
let i = -1;
for (const forbiddance of constitution) {
i++;
if (!forbiddance.permission.mutable) continue;
-
+
await expect(executeTransaction(
api,
forbiddance.sinner,
api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]),
), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
-
+
await expect(executeTransaction(
api,
forbiddance.sinner,
api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]),
), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/);
}
-
+
const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
expect(properties.consumedSpace).to.be.equal(originalSpace);
});
+ }
+ it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => {
+ await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1);
});
+ it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async () => {
+ await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100);
+ });
- it('Forbids changing/deleting properties of a token if the property is permanent (immutable)', async () => {
+ async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) {
+ await prepare(mode, pieces);
+
await usingApi(async api => {
let i = -1;
for (const permission of constitution) {
i++;
if (permission.permission.mutable) continue;
-
+
await expect(executeTransaction(
api,
permission.signers[0],
api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]),
), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
-
+
await expect(executeTransaction(
api,
permission.signers[0],
api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]),
), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/);
}
-
+
const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
expect(properties.consumedSpace).to.be.equal(originalSpace);
- });
+ });
+ }
+ it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => {
+ await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1);
});
+ it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async () => {
+ await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100);
+ });
+
+ async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) {
+ await prepare(mode, pieces);
- it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission', async () => {
await usingApi(async api => {
await expect(executeTransaction(
api,
@@ -925,20 +1081,28 @@
alice,
api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]),
), 'on setting a new non-permitted property').to.not.be.rejected;
-
+
await expect(executeTransaction(
api,
alice,
api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]),
), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/);
-
+
expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty;
const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
expect(properties.consumedSpace).to.be.equal(originalSpace);
});
+ }
+ it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => {
+ await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1);
+ });
+ it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async () => {
+ await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100);
});
- it('Forbids adding too many properties to a token', async () => {
+ async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) {
+ await prepare(mode, pieces);
+
await usingApi(async api => {
await expect(executeTransaction(
api,
@@ -948,7 +1112,7 @@
{key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}},
]),
), 'on setting a new non-permitted property').to.not.be.rejected;
-
+
// Mute the general tx parsing error
{
console.error = () => {};
@@ -958,7 +1122,7 @@
api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]),
)).to.be.rejected;
}
-
+
await expect(executeTransaction(
api,
alice,
@@ -967,10 +1131,107 @@
{key: 'young_years', value: 'neverending'.repeat(1490)},
]),
)).to.be.rejectedWith(/common\.NoSpaceForProperty/);
-
+
expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty;
const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON();
expect(propertiesMap.consumedSpace).to.be.equal(originalSpace);
});
+ }
+ it('Forbids adding too many properties to a token (NFT)', async () => {
+ await testForbidsAddingTooManyProperties({type: 'NFT'}, 1);
+ });
+ it('Forbids adding too many properties to a token (ReFungible)', async () => {
+ await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100);
+ });
+});
+
+describe('ReFungible token properties permissions tests', () => {
+ let collection: number;
+ let token: number;
+
+ before(async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ alice = privateKeyWrapper('//Alice');
+ bob = privateKeyWrapper('//Bob');
+ charlie = privateKeyWrapper('//Charlie');
+ });
+ });
+
+ beforeEach(async () => {
+ await usingApi(async api => {
+ collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+ token = await createItemExpectSuccess(alice, collection, 'ReFungible');
+ await addCollectionAdminExpectSuccess(alice, collection, bob.address);
+
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]),
+ )).to.not.be.rejected;
+ });
+ });
+
+ it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => {
+ await usingApi(async api => {
+ await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');
+
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.setTokenProperties(collection, token, [
+ {key: 'key', value: 'word'},
+ ]),
+ )).to.be.rejectedWith(/common\.NoPermission/);
+ });
+ });
+
+ it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => {
+ await usingApi(async api => {
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]),
+ )).to.not.be.rejected;
+
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.setTokenProperties(collection, token, [
+ {key: 'key', value: 'word'},
+ ]),
+ )).to.be.not.rejected;
+
+ await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');
+
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.setTokenProperties(collection, token, [
+ {key: 'key', value: 'bad word'},
+ ]),
+ )).to.be.rejectedWith(/common\.NoPermission/);
+ });
+ });
+
+ it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => {
+ await usingApi(async api => {
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.setTokenProperties(collection, token, [
+ {key: 'key', value: 'word'},
+ ]),
+ )).to.be.not.rejected;
+
+ await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible');
+
+ await expect(executeTransaction(
+ api,
+ alice,
+ api.tx.unique.deleteTokenProperties(collection, token, [
+ 'key',
+ ]),
+ )).to.be.rejectedWith(/common\.NoPermission/);
+ });
});
});
tests/src/refungible.test.tsdiffbeforeafterboth--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
-import {default as usingApi} from './substrate/substrate-api';
+import {default as usingApi, submitTransactionAsync} from './substrate/substrate-api';
import {IKeyringPair} from '@polkadot/types/types';
import {
createCollectionExpectSuccess,
@@ -30,6 +30,12 @@
transfer,
burnItem,
repartitionRFT,
+ createCollectionWithPropsExpectSuccess,
+ getDetailedCollectionInfo,
+ normalizeAccountId,
+ CrossAccountId,
+ getCreateItemsResult,
+ getDestroyItemsResult,
} from './util/helpers';
import chai from 'chai';
@@ -51,14 +57,14 @@
it('Create refungible collection and token', async () => {
await usingApi(async api => {
const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
- expect(createCollectionResult.success).to.be.true;
+ expect(createCollectionResult.success).to.be.true;
const collectionId = createCollectionResult.collectionId;
-
+
const itemCountBefore = await getLastTokenId(api, collectionId);
const result = await createRefungibleToken(api, alice, collectionId, 100n);
-
+
const itemCountAfter = await getLastTokenId(api, collectionId);
-
+
// What to expect
// tslint:disable-next-line:no-unused-expression
expect(result.success).to.be.true;
@@ -67,7 +73,43 @@
expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString());
});
});
-
+
+ it('RPC method tokenOnewrs for refungible collection and token', async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+ const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+
+ const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
+ const collectionId = createCollectionResult.collectionId;
+
+ const result = await createRefungibleToken(api, alice, collectionId, 10_000n);
+ const aliceTokenId = result.itemId;
+
+
+ await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
+ await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
+
+ for (let i = 0; i < 7; i++) {
+ await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 50*(i+1));
+ }
+
+ const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
+ const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
+
+ const aliceID = normalizeAccountId(alice);
+ const bobId = normalizeAccountId(bob);
+
+ // What to expect
+ // tslint:disable-next-line:no-unused-expression
+ expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+ expect(owners.length).to.be.equal(10);
+
+ const eleven = privateKeyWrapper('11');
+ expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+ expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ });
+ });
+
it('Transfer token pieces', async () => {
await usingApi(async api => {
const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
@@ -186,4 +228,67 @@
await expect(transfer(api, collectionId, tokenId, bob, alice, 160n)).to.eventually.be.rejected;
});
});
+
+ it('Repartition with increased amount', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+
+ const tx = api.tx.unique.repartition(collectionId, tokenId, 200n);
+ const events = await submitTransactionAsync(alice, tx);
+ const substrateEvents = getCreateItemsResult(events);
+ expect(substrateEvents).to.include.deep.members([
+ {
+ success: true,
+ collectionId,
+ itemId: tokenId,
+ recipient: {Substrate: alice.address},
+ amount: 100,
+ },
+ ]);
+ });
+ });
+
+ it('Repartition with decreased amount', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
+ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId;
+
+ const tx = api.tx.unique.repartition(collectionId, tokenId, 50n);
+ const events = await submitTransactionAsync(alice, tx);
+ const substrateEvents = getDestroyItemsResult(events);
+ expect(substrateEvents).to.include.deep.members([
+ {
+ success: true,
+ collectionId,
+ itemId: tokenId,
+ owner: {Substrate: alice.address},
+ amount: 50,
+ },
+ ]);
+ });
+ });
+});
+
+describe('Test Refungible properties:', () => {
+ before(async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ alice = privateKeyWrapper('//Alice');
+ bob = privateKeyWrapper('//Bob');
+ });
+ });
+
+ it('Сreate new collection with properties', async () => {
+ await usingApi(async api => {
+ const properties = [{key: 'key1', value: 'val1'}];
+ const propertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}];
+ const collectionId = await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'ReFungible'},
+ properties: properties,
+ propPerm: propertyPermissions,
+ });
+ const collection = (await getDetailedCollectionInfo(api, collectionId))!;
+ expect(collection.properties.toHuman()).to.be.deep.equal(properties);
+ expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions);
+ });
+ });
});
tests/src/rpc.test.tsdiffbeforeafterboth--- a/tests/src/rpc.test.ts
+++ b/tests/src/rpc.test.ts
@@ -1,12 +1,57 @@
+import {IKeyringPair} from '@polkadot/types/types';
import {expect} from 'chai';
import usingApi from './substrate/substrate-api';
-import {createCollectionExpectSuccess, getTokenOwner} from './util/helpers';
+import {createCollection, createCollectionExpectSuccess, createFungibleItemExpectSuccess, CrossAccountId, getTokenOwner, normalizeAccountId, transfer, U128_MAX} from './util/helpers';
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
-describe('getTokenOwner', () => {
+
+describe('integration test: RPC methods', () => {
+ before(async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ alice = privateKeyWrapper('//Alice');
+ bob = privateKeyWrapper('//Bob');
+ });
+ });
+
+
it('returns None for fungible collection', async () => {
await usingApi(async api => {
const collection = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
await expect(getTokenOwner(api, collection, 0)).to.be.rejectedWith(/^owner == null$/);
});
});
+
+ it('RPC method tokenOwners for fungible collection and token', async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+ const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+
+ const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
+ const collectionId = createCollectionResult.collectionId;
+ const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
+
+ await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
+ await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
+
+ for (let i = 0; i < 7; i++) {
+ await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1);
+ }
+
+ const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
+ const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
+ const aliceID = normalizeAccountId(alice);
+ const bobId = normalizeAccountId(bob);
+
+ // What to expect
+ // tslint:disable-next-line:no-unused-expression
+ expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+ expect(owners.length == 10).to.be.true;
+
+ const eleven = privateKeyWrapper('11');
+ expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+ expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ });
+ });
});
\ No newline at end of file
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -103,6 +103,15 @@
collectionId: number;
itemId: number;
recipient?: CrossAccountId;
+ amount?: number;
+}
+
+interface DestroyItemResult {
+ success: boolean;
+ collectionId: number;
+ itemId: number;
+ owner: CrossAccountId;
+ amount: number;
}
interface TransferResult {
@@ -220,12 +229,14 @@
const collectionId = parseInt(data[0].toString(), 10);
const itemId = parseInt(data[1].toString(), 10);
const recipient = normalizeAccountId(data[2].toJSON() as any);
+ const amount = parseInt(data[3].toString(), 10);
const itemRes: CreateItemResult = {
success: true,
collectionId,
itemId,
recipient,
+ amount,
};
results.push(itemRes);
@@ -255,6 +266,31 @@
return result;
}
+export function getDestroyItemsResult(events: EventRecord[]): DestroyItemResult[] {
+ const results: DestroyItemResult[] = [];
+
+ const genericResult = getGenericResult<DestroyItemResult[]>(events, 'common', 'ItemDestroyed', (data) => {
+ const collectionId = parseInt(data[0].toString(), 10);
+ const itemId = parseInt(data[1].toString(), 10);
+ const owner = normalizeAccountId(data[2].toJSON() as any);
+ const amount = parseInt(data[3].toString(), 10);
+
+ const itemRes: DestroyItemResult = {
+ success: true,
+ collectionId,
+ itemId,
+ owner,
+ amount,
+ };
+
+ results.push(itemRes);
+ return results;
+ });
+
+ if (!genericResult.success) return [];
+ return results;
+}
+
export function getTransferResult(api: ApiPromise, events: EventRecord[]): TransferResult {
for (const {event} of events) {
if (api.events.common.Transfer.is(event)) {
@@ -284,7 +320,7 @@
type: 'ReFungible';
}
-type CollectionMode = Nft | Fungible | ReFungible;
+export type CollectionMode = Nft | Fungible | ReFungible;
export type Property = {
key: any,
@@ -1414,7 +1450,7 @@
tx = api.tx.unique.createItem(collectionId, to, createData as any);
}
- const events = await submitTransactionAsync(sender, tx);
+ const events = await executeTransaction(api, sender, tx);
const result = getCreateItemResult(events);
const itemCountAfter = await getLastTokenId(api, collectionId);