difftreelog
Merge branch 'develop' into feature/transferfromtests
in: master
43 files changed
.devcontainer/Dockerfilediffbeforeafterboth--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,14 @@
+FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1
+
+RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && \
+ apt-get -y install --no-install-recommends libssl-dev pkg-config libclang-dev clang
+
+USER vscode
+
+RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash && \
+ export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" && \
+ [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
+ nvm install v12.20.1 && \
+ rustup toolchain install nightly-2020-10-01 && \
+ rustup default nightly-2020-10-01 && \
+ rustup target add wasm32-unknown-unknown
\ No newline at end of file
.devcontainer/devcontainer.jsondiffbeforeafterboth--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,18 @@
+{
+ "name": "Rust",
+ "build": {
+ "dockerfile": "Dockerfile"
+ },
+ "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
+ "settings": {
+ "terminal.integrated.shell.linux": "/bin/bash",
+ "lldb.executable": "/usr/bin/lldb",
+ "files.watcherExclude": {
+ "**/target/**": true
+ }
+ },
+ "extensions": [
+ "matklad.rust-analyzer"
+ ],
+ "remoteUser": "vscode"
+}
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -543,9 +543,9 @@
[[package]]
name = "bumpalo"
-version = "3.5.0"
+version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59"
+checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
[[package]]
name = "byte-slice-cast"
@@ -985,9 +985,9 @@
[[package]]
name = "ctor"
-version = "0.1.18"
+version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10bcb9d7dcbf7002aaffbb53eac22906b64cdcc127971dcc387d8eb7c95d5560"
+checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19"
dependencies = [
"quote",
"syn",
@@ -1032,9 +1032,9 @@
[[package]]
name = "data-encoding"
-version = "2.3.1"
+version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "993a608597367c6377b258c25d7120740f00ed23a2252b729b1932dd7866f908"
+checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
[[package]]
name = "derive_more"
@@ -1151,7 +1151,7 @@
"ed25519",
"rand 0.7.3",
"serde",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"zeroize",
]
@@ -1342,9 +1342,9 @@
[[package]]
name = "flate2"
-version = "1.0.19"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
+checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
dependencies = [
"cfg-if 1.0.0",
"crc32fast",
@@ -1907,7 +1907,7 @@
"http 0.2.3",
"indexmap",
"slab",
- "tokio 0.2.24",
+ "tokio 0.2.25",
"tokio-util",
"tracing",
"tracing-futures",
@@ -2064,9 +2064,9 @@
[[package]]
name = "httparse"
-version = "1.3.4"
+version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
+checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691"
[[package]]
name = "httpdate"
@@ -2129,9 +2129,9 @@
"httparse",
"httpdate",
"itoa",
- "pin-project 1.0.4",
+ "pin-project 1.0.5",
"socket2",
- "tokio 0.2.24",
+ "tokio 0.2.25",
"tower-service",
"tracing",
"want 0.3.0",
@@ -2150,7 +2150,7 @@
"log",
"rustls 0.18.1",
"rustls-native-certs",
- "tokio 0.2.24",
+ "tokio 0.2.25",
"tokio-rustls",
"webpki",
]
@@ -2553,9 +2553,9 @@
[[package]]
name = "libc"
-version = "0.2.82"
+version = "0.2.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
+checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
[[package]]
name = "libloading"
@@ -2606,7 +2606,7 @@
"libp2p-yamux",
"parity-multiaddr",
"parking_lot 0.11.1",
- "pin-project 1.0.4",
+ "pin-project 1.0.5",
"smallvec 1.6.1",
"wasm-timer",
]
@@ -2631,13 +2631,13 @@
"multistream-select",
"parity-multiaddr",
"parking_lot 0.11.1",
- "pin-project 1.0.4",
+ "pin-project 1.0.5",
"prost",
"prost-build",
"rand 0.7.3",
"ring",
"rw-stream-sink",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"smallvec 1.6.1",
"thiserror",
"unsigned-varint 0.6.0",
@@ -2715,7 +2715,7 @@
"prost-build",
"rand 0.7.3",
"regex",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"smallvec 1.6.1",
"unsigned-varint 0.6.0",
"wasm-timer",
@@ -2755,7 +2755,7 @@
"prost",
"prost-build",
"rand 0.7.3",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"smallvec 1.6.1",
"uint",
"unsigned-varint 0.6.0",
@@ -2817,7 +2817,7 @@
"prost",
"prost-build",
"rand 0.7.3",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"snow",
"static_assertions",
"x25519-dalek",
@@ -2864,7 +2864,7 @@
dependencies = [
"futures 0.3.12",
"log",
- "pin-project 1.0.4",
+ "pin-project 1.0.5",
"rand 0.7.3",
"salsa20",
"sha3",
@@ -2892,9 +2892,9 @@
[[package]]
name = "libp2p-swarm"
-version = "0.27.1"
+version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4f89ebb4d8953bda12623e9871959fe728dea3bf6eae0421dc9c42dc821e488"
+checksum = "7955b973e1fd2bd61ffd43ce261c1223f61f4aacd5bae362a924993f9a25fd98"
dependencies = [
"either",
"futures 0.3.12",
@@ -3074,9 +3074,9 @@
[[package]]
name = "lru"
-version = "0.6.3"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3aae342b73d57ad0b8b364bd12584819f2c1fe9114285dfcf8b0722607671635"
+checksum = "fe2382d8ed3918ea4ba70d98d5b74e47a168e0331965f3f17cdbc748bdebc5a3"
dependencies = [
"hashbrown",
]
@@ -3310,7 +3310,7 @@
"digest 0.9.0",
"generic-array 0.14.4",
"multihash-derive",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"unsigned-varint 0.5.1",
]
@@ -3343,7 +3343,7 @@
"bytes 1.0.1",
"futures 0.3.12",
"log",
- "pin-project 1.0.4",
+ "pin-project 1.0.5",
"smallvec 1.6.1",
"unsigned-varint 0.6.0",
]
@@ -4269,11 +4269,11 @@
[[package]]
name = "pin-project"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2"
+checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63"
dependencies = [
- "pin-project-internal 1.0.4",
+ "pin-project-internal 1.0.5",
]
[[package]]
@@ -4289,9 +4289,9 @@
[[package]]
name = "pin-project-internal"
-version = "1.0.4"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71"
+checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b"
dependencies = [
"proc-macro2",
"quote",
@@ -4862,9 +4862,9 @@
[[package]]
name = "ring"
-version = "0.16.19"
+version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
@@ -5114,7 +5114,7 @@
"structopt",
"thiserror",
"tiny-bip39",
- "tokio 0.2.24",
+ "tokio 0.2.25",
]
[[package]]
@@ -6037,9 +6037,9 @@
[[package]]
name = "sha-1"
-version = "0.9.2"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
+checksum = "f4b312c3731e3fe78a185e6b9b911a7aa715b8e31cce117975219aab2acf285d"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
@@ -6062,9 +6062,9 @@
[[package]]
name = "sha2"
-version = "0.9.2"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e7aab86fe2149bad8c507606bdb3f4ef5e7b2380eb92350f56122cca72a42a8"
+checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
@@ -6171,7 +6171,7 @@
"rand_core 0.5.1",
"ring",
"rustc_version",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"subtle 2.4.0",
"x25519-dalek",
]
@@ -6200,7 +6200,7 @@
"httparse",
"log",
"rand 0.7.3",
- "sha-1 0.9.2",
+ "sha-1 0.9.3",
]
[[package]]
@@ -6428,7 +6428,7 @@
"schnorrkel",
"secrecy",
"serde",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"sp-debug-derive",
"sp-externalities",
"sp-runtime-interface",
@@ -6973,7 +6973,7 @@
"hyper 0.13.9",
"log",
"prometheus",
- "tokio 0.2.24",
+ "tokio 0.2.25",
]
[[package]]
@@ -7083,9 +7083,9 @@
[[package]]
name = "thread_local"
-version = "1.1.2"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
@@ -7122,7 +7122,7 @@
"pbkdf2 0.4.0",
"rand 0.7.3",
"rustc-hash",
- "sha2 0.9.2",
+ "sha2 0.9.3",
"thiserror",
"unicode-normalization",
"zeroize",
@@ -7178,9 +7178,9 @@
[[package]]
name = "tokio"
-version = "0.2.24"
+version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48"
+checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
dependencies = [
"bytes 0.5.6",
"fnv",
@@ -7302,7 +7302,7 @@
dependencies = [
"futures-core",
"rustls 0.18.1",
- "tokio 0.2.24",
+ "tokio 0.2.25",
"webpki",
]
@@ -7412,7 +7412,7 @@
"futures-sink",
"log",
"pin-project-lite 0.1.11",
- "tokio 0.2.24",
+ "tokio 0.2.25",
]
[[package]]
@@ -7432,9 +7432,9 @@
[[package]]
name = "tracing"
-version = "0.1.22"
+version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
+checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3"
dependencies = [
"cfg-if 1.0.0",
"log",
@@ -7445,9 +7445,9 @@
[[package]]
name = "tracing-attributes"
-version = "0.1.11"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80e0ccfc3378da0cce270c946b676a376943f5cd16aeba64568e7939806f4ada"
+checksum = "43f080ea7e4107844ef4766459426fa2d5c1ada2e47edba05dc7fa99d9629f47"
dependencies = [
"proc-macro2",
"quote",
@@ -7518,9 +7518,9 @@
[[package]]
name = "trie-db"
-version = "0.22.2"
+version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cc176c377eb24d652c9c69c832c832019011b6106182bf84276c66b66d5c9a6"
+checksum = "ec051edf7f0fc9499a2cb0947652cab2148b9d7f61cee7605e312e9f970dacaf"
dependencies = [
"hash-db",
"hashbrown",
@@ -8032,18 +8032,18 @@
[[package]]
name = "wast"
-version = "32.0.0"
+version = "33.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c24a3ee360d01d60ed0a0f960ab76a6acce64348cdb0bf8699c2a866fad57c7c"
+checksum = "1d04fe175c7f78214971293e7d8875673804e736092206a3a4544dbc12811c1b"
dependencies = [
"leb128",
]
[[package]]
name = "wat"
-version = "1.0.33"
+version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e8f7f34773fa6318e8897283abf7941c1f250faae4e1a52f82df09c3bad7cce"
+checksum = "7ec9c6ee01ae07a26adadcdfed22c7a97e0b8cbee9c06e0e96076ece5aeb5cfe"
dependencies = [
"wast",
]
Dockerfilediffbeforeafterboth--- a/Dockerfile
+++ b/Dockerfile
@@ -1,9 +1,9 @@
# ===== BUILD ======
-FROM phusion/baseimage:0.10.2 as builder
+FROM phusion/baseimage:18.04-1.0.0 as builder
LABEL maintainer="gz@usetech.com"
-ENV WASM_TOOLCHAIN=nightly-2020-10-01
+ENV WASM_TOOLCHAIN=nightly-2021-01-27
ARG PROFILE=release
@@ -37,7 +37,7 @@
# ===== RUN ======
-FROM phusion/baseimage:0.10.2
+FROM phusion/baseimage:18.04-1.0.0
ARG PROFILE=release
COPY --from=builder /nft_parachain/target/$PROFILE/nft /usr/local/bin
README.mddiffbeforeafterboth--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@
```bash
rustup toolchain install 1.49.0
-rustup toolchain install nightly-2020-10-01
+rustup toolchain install nightly-2020-01-27
rustup default nightly-2021-01-27
```
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, IsSubType,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial, DispatchClass,29 },30 StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use transaction_payment::OnChargeTransaction;4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;5556pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;57pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;58pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5960// Structs61// #region6263pub type CollectionId = u32;64pub type TokenId = u32;65pub type DecimalPoints = u8;6667#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]68#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]69pub enum CollectionMode {70 Invalid,71 NFT,72 // decimal points73 Fungible(DecimalPoints),74 // decimal points75 ReFungible(DecimalPoints),76}7778impl Default for CollectionMode {79 fn default() -> Self {80 Self::Invalid81 }82}8384impl Into<u8> for CollectionMode {85 fn into(self) -> u8 {86 match self {87 CollectionMode::Invalid => 0,88 CollectionMode::NFT => 1,89 CollectionMode::Fungible(_) => 2,90 CollectionMode::ReFungible(_) => 3,91 }92 }93}9495#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]96#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]97pub enum AccessMode {98 Normal,99 WhiteList,100}101impl Default for AccessMode {102 fn default() -> Self {103 Self::Normal104 }105}106107#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub enum SchemaVersion {110 ImageURL,111 Unique,112}113impl Default for SchemaVersion {114 fn default() -> Self {115 Self::ImageURL116 }117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct Ownership<AccountId> {122 pub owner: AccountId,123 pub fraction: u128,124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct CollectionType<AccountId> {129 pub owner: AccountId,130 pub mode: CollectionMode,131 pub access: AccessMode,132 pub decimal_points: DecimalPoints,133 pub name: Vec<u16>, // 64 include null escape char134 pub description: Vec<u16>, // 256 include null escape char135 pub token_prefix: Vec<u8>, // 16 include null escape char136 pub mint_mode: bool,137 pub offchain_schema: Vec<u8>,138 pub schema_version: SchemaVersion,139 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender140 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.141 pub limits: CollectionLimits, // Collection private restrictions 142 pub variable_on_chain_schema: Vec<u8>, //143 pub const_on_chain_schema: Vec<u8>, //144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct NftItemType<AccountId> {149 pub owner: AccountId,150 pub const_data: Vec<u8>,151 pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType {157 pub value: u128,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct ReFungibleItemType<AccountId> {163 pub owner: Vec<Ownership<AccountId>>,164 pub const_data: Vec<u8>,165 pub variable_data: Vec<u8>,166}167168// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170// pub struct VestingItem<AccountId, Moment> {171// pub sender: AccountId,172// pub recipient: AccountId,173// pub collection_id: CollectionId,174// pub item_id: TokenId,175// pub amount: u64,176// pub vesting_date: Moment,177// }178179#[derive(Encode, Decode, Debug, Clone, PartialEq)]180#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]181pub struct CollectionLimits {182 pub account_token_ownership_limit: u32,183 pub sponsored_data_size: u32,184 pub token_limit: u32,185186 // Timeouts for item types in passed blocks187 pub sponsor_transfer_timeout: u32,188}189190impl Default for CollectionLimits {191 fn default() -> CollectionLimits {192 CollectionLimits { 193 account_token_ownership_limit: 10_000_000, 194 token_limit: u32::max_value(),195 sponsored_data_size: u32::max_value(), 196 sponsor_transfer_timeout: 14400 }197 }198}199200#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]201#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]202pub struct ChainLimits {203 pub collection_numbers_limit: u32,204 pub account_token_ownership_limit: u32,205 pub collections_admins_limit: u64,206 pub custom_data_limit: u32,207208 // Timeouts for item types in passed blocks209 pub nft_sponsor_transfer_timeout: u32,210 pub fungible_sponsor_transfer_timeout: u32,211 pub refungible_sponsor_transfer_timeout: u32,212213 // Schema limits214 pub offchain_schema_limit: u32,215 pub variable_on_chain_schema_limit: u32,216 pub const_on_chain_schema_limit: u32,217}218219pub trait WeightInfo {220 fn create_collection() -> Weight;221 fn destroy_collection() -> Weight;222 fn add_to_white_list() -> Weight;223 fn remove_from_white_list() -> Weight;224 fn set_public_access_mode() -> Weight;225 fn set_mint_permission() -> Weight;226 fn change_collection_owner() -> Weight;227 fn add_collection_admin() -> Weight;228 fn remove_collection_admin() -> Weight;229 fn set_collection_sponsor() -> Weight;230 fn confirm_sponsorship() -> Weight;231 fn remove_collection_sponsor() -> Weight;232 fn create_item(s: usize) -> Weight;233 fn burn_item() -> Weight;234 fn transfer() -> Weight;235 fn approve() -> Weight;236 fn transfer_from() -> Weight;237 fn set_offchain_schema() -> Weight;238 fn set_const_on_chain_schema() -> Weight;239 fn set_variable_on_chain_schema() -> Weight;240 fn set_variable_meta_data() -> Weight;241 fn enable_contract_sponsoring() -> Weight;242 fn set_schema_version() -> Weight;243 fn set_chain_limits() -> Weight;244 fn set_contract_sponsoring_rate_limit() -> Weight;245 fn toggle_contract_white_list() -> Weight;246 fn add_to_contract_white_list() -> Weight;247 fn remove_from_contract_white_list() -> Weight;248 fn set_collection_limits() -> Weight;249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateNftData {254 pub const_data: Vec<u8>,255 pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub struct CreateFungibleData {261 pub value: u128,262}263264#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]265#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]266pub struct CreateReFungibleData {267 pub const_data: Vec<u8>,268 pub variable_data: Vec<u8>,269}270271#[derive(Encode, Decode, Debug, Clone, PartialEq)]272#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]273pub enum CreateItemData {274 NFT(CreateNftData),275 Fungible(CreateFungibleData),276 ReFungible(CreateReFungibleData),277}278279impl CreateItemData {280 pub fn len(&self) -> usize {281 let len = match self {282 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),283 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),284 _ => 0285 };286 287 return len;288 }289}290291impl From<CreateNftData> for CreateItemData {292 fn from(item: CreateNftData) -> Self {293 CreateItemData::NFT(item)294 }295}296297impl From<CreateReFungibleData> for CreateItemData {298 fn from(item: CreateReFungibleData) -> Self {299 CreateItemData::ReFungible(item)300 }301}302303impl From<CreateFungibleData> for CreateItemData {304 fn from(item: CreateFungibleData) -> Self {305 CreateItemData::Fungible(item)306 }307}308309310decl_error! {311 /// Error for non-fungible-token module.312 pub enum Error for Module<T: Config> {313 /// Total collections bound exceeded.314 TotalCollectionsLimitExceeded,315 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.316 CollectionDecimalPointLimitExceeded, 317 /// Collection name can not be longer than 63 char.318 CollectionNameLimitExceeded, 319 /// Collection description can not be longer than 255 char.320 CollectionDescriptionLimitExceeded, 321 /// Token prefix can not be longer than 15 char.322 CollectionTokenPrefixLimitExceeded,323 /// This collection does not exist.324 CollectionNotFound,325 /// Item not exists.326 TokenNotFound,327 /// Admin not found328 AdminNotFound,329 /// Arithmetic calculation overflow.330 NumOverflow, 331 /// Account already has admin role.332 AlreadyAdmin, 333 /// You do not own this collection.334 NoPermission,335 /// This address is not set as sponsor, use setCollectionSponsor first.336 ConfirmUnsetSponsorFail,337 /// Collection is not in mint mode.338 PublicMintingNotAllowed,339 /// Sender parameter and item owner must be equal.340 MustBeTokenOwner,341 /// Item balance not enough.342 TokenValueTooLow,343 /// Size of item is too large.344 NftSizeLimitExceeded,345 /// No approve found346 ApproveNotFound,347 /// Requested value more than approved.348 TokenValueNotEnough,349 /// Only approved addresses can call this method.350 ApproveRequired,351 /// Address is not in white list.352 AddresNotInWhiteList,353 /// Number of collection admins bound exceeded.354 CollectionAdminsLimitExceeded,355 /// Owned tokens by a single address bound exceeded.356 AddressOwnershipLimitExceeded,357 /// Length of items properties must be greater than 0.358 EmptyArgument,359 /// const_data exceeded data limit.360 TokenConstDataLimitExceeded,361 /// variable_data exceeded data limit.362 TokenVariableDataLimitExceeded,363 /// Not NFT item data used to mint in NFT collection.364 NotNftDataUsedToMintNftCollectionToken,365 /// Not Fungible item data used to mint in Fungible collection.366 NotFungibleDataUsedToMintFungibleCollectionToken,367 /// Not Re Fungible item data used to mint in Re Fungible collection.368 NotReFungibleDataUsedToMintReFungibleCollectionToken,369 /// Unexpected collection type.370 UnexpectedCollectionType,371 /// Can't store metadata in fungible tokens.372 CantStoreMetadataInFungibleTokens,373 /// Collection token limit exceeded374 CollectionTokenLimitExceeded,375 /// Account token limit exceeded per collection376 AccountTokenLimitExceeded,377 /// Collection limit bounds per collection exceeded378 CollectionLimitBoundsExceeded,379 /// Schema data size limit bound exceeded380 SchemaDataLimitExceeded381 }382}383384pub trait Config: system::Config + Sized + transaction_payment::Config + pallet_contracts::Config {385 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;386387 /// Weight information for extrinsics in this pallet.388 type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397 trait Store for Module<T: Config> as Nft {398399 // Private members400 NextCollectionID: CollectionId;401 CreatedCollectionCount: u32;402 ChainVersion: u64;403 ItemListIndex: map hasher(identity) CollectionId => TokenId;404405 // Chain limits struct406 pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408 // Bound counters409 CollectionCount: u32;410 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412 // Basic collections413 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;416417 /// Balance owner per collection map418 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420 /// second parameter: item id + owner account id + spender account id421 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;422423 /// Item collections424 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;426 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428 /// Index list429 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431 /// Tokens transfer baskets432 pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;433 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;434 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;435 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;436437 // Contract Sponsorship and Ownership438 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;439 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;440 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;441 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;442 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 443 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 444 }445 add_extra_genesis {446 build(|config: &GenesisConfig<T>| {447 // Modification of storage448 for (_num, _c) in &config.collection {449 <Module<T>>::init_collection(_c);450 }451452 for (_num, _c, _i) in &config.nft_item_id {453 <Module<T>>::init_nft_token(*_c, _i);454 }455456 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {457 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);458 }459460 for (_num, _c, _i) in &config.refungible_item_id {461 <Module<T>>::init_refungible_token(*_c, _i);462 }463 })464 }465}466467decl_event!(468 pub enum Event<T>469 where470 AccountId = <T as system::Config>::AccountId,471 {472 /// New collection was created473 /// 474 /// # Arguments475 /// 476 /// * collection_id: Globally unique identifier of newly created collection.477 /// 478 /// * mode: [CollectionMode] converted into u8.479 /// 480 /// * account_id: Collection owner.481 Created(CollectionId, u8, AccountId),482483 /// New item was created.484 /// 485 /// # Arguments486 /// 487 /// * collection_id: Id of the collection where item was created.488 /// 489 /// * item_id: Id of an item. Unique within the collection.490 ItemCreated(CollectionId, TokenId),491492 /// Collection item was burned.493 /// 494 /// # Arguments495 /// 496 /// collection_id.497 /// 498 /// item_id: Identifier of burned NFT.499 ItemDestroyed(CollectionId, TokenId),500 }501);502503decl_module! {504 pub struct Module<T: Config> for enum Call 505 where 506 origin: T::Origin507 {508 fn deposit_event() = default;509 type Error = Error<T>;510511 fn on_initialize(now: T::BlockNumber) -> Weight {512513 if ChainVersion::get() < 2514 {515 let value = NextCollectionID::get();516 CreatedCollectionCount::put(value);517 ChainVersion::put(2);518 }519520 0521 }522523 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.524 /// 525 /// # Permissions526 /// 527 /// * Anyone.528 /// 529 /// # Arguments530 /// 531 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.532 /// 533 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.534 /// 535 /// * token_prefix: UTF-8 string with token prefix.536 /// 537 /// * mode: [CollectionMode] collection type and type dependent data.538 // returns collection ID539 #[weight = <T as Config>::WeightInfo::create_collection()]540 pub fn create_collection(origin,541 collection_name: Vec<u16>,542 collection_description: Vec<u16>,543 token_prefix: Vec<u8>,544 mode: CollectionMode) -> DispatchResult {545546 // Anyone can create a collection547 let who = ensure_signed(origin)?;548549 let decimal_points = match mode {550 CollectionMode::Fungible(points) => points,551 CollectionMode::ReFungible(points) => points,552 _ => 0553 };554555 // bound Total number of collections556 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);557558 // check params559 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);560 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);561 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);562 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);563564 // Generate next collection ID565 let next_id = CreatedCollectionCount::get()566 .checked_add(1)567 .ok_or(Error::<T>::NumOverflow)?;568569 // bound counter570 let total = CollectionCount::get()571 .checked_add(1)572 .ok_or(Error::<T>::NumOverflow)?;573574 CreatedCollectionCount::put(next_id);575 CollectionCount::put(total);576577 // Create new collection578 let new_collection = CollectionType {579 owner: who.clone(),580 name: collection_name,581 mode: mode.clone(),582 mint_mode: false,583 access: AccessMode::Normal,584 description: collection_description,585 decimal_points: decimal_points,586 token_prefix: token_prefix,587 offchain_schema: Vec::new(),588 schema_version: SchemaVersion::ImageURL,589 sponsor: T::AccountId::default(),590 sponsor_confirmed: false,591 variable_on_chain_schema: Vec::new(),592 const_on_chain_schema: Vec::new(),593 limits: CollectionLimits::default(),594 };595596 // Add new collection to map597 <Collection<T>>::insert(next_id, new_collection);598599 // call event600 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));601602 Ok(())603 }604605 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.606 /// 607 /// # Permissions608 /// 609 /// * Collection Owner.610 /// 611 /// # Arguments612 /// 613 /// * collection_id: collection to destroy.614 #[weight = <T as Config>::WeightInfo::destroy_collection()]615 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {616617 let sender = ensure_signed(origin)?;618 Self::check_owner_permissions(collection_id, sender)?;619620 <AddressTokens<T>>::remove_prefix(collection_id);621 <Allowances<T>>::remove_prefix(collection_id);622 <Balance<T>>::remove_prefix(collection_id);623 <ItemListIndex>::remove(collection_id);624 <AdminList<T>>::remove(collection_id);625 <Collection<T>>::remove(collection_id);626 <WhiteList<T>>::remove_prefix(collection_id);627628 <NftItemList<T>>::remove_prefix(collection_id);629 <FungibleItemList<T>>::remove_prefix(collection_id);630 <ReFungibleItemList<T>>::remove_prefix(collection_id);631632 <NftTransferBasket<T>>::remove_prefix(collection_id);633 <FungibleTransferBasket<T>>::remove_prefix(collection_id);634 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);635636 if CollectionCount::get() > 0637 {638 // bound couter639 let total = CollectionCount::get()640 .checked_sub(1)641 .ok_or(Error::<T>::NumOverflow)?;642643 CollectionCount::put(total);644 }645646 Ok(())647 }648649 /// Add an address to white list.650 /// 651 /// # Permissions652 /// 653 /// * Collection Owner654 /// * Collection Admin655 /// 656 /// # Arguments657 /// 658 /// * collection_id.659 /// 660 /// * address.661 #[weight = <T as Config>::WeightInfo::add_to_white_list()]662 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{663664 let sender = ensure_signed(origin)?;665 Self::check_owner_or_admin_permissions(collection_id, sender)?;666667 <WhiteList<T>>::insert(collection_id, address, true);668 669 Ok(())670 }671672 /// Remove an address from white list.673 /// 674 /// # Permissions675 /// 676 /// * Collection Owner677 /// * Collection Admin678 /// 679 /// # Arguments680 /// 681 /// * collection_id.682 /// 683 /// * address.684 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]685 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{686687 let sender = ensure_signed(origin)?;688 Self::check_owner_or_admin_permissions(collection_id, sender)?;689690 <WhiteList<T>>::remove(collection_id, address);691692 Ok(())693 }694695 /// Toggle between normal and white list access for the methods with access for `Anyone`.696 /// 697 /// # Permissions698 /// 699 /// * Collection Owner.700 /// 701 /// # Arguments702 /// 703 /// * collection_id.704 /// 705 /// * mode: [AccessMode]706 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]707 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult708 {709 let sender = ensure_signed(origin)?;710711 Self::check_owner_permissions(collection_id, sender)?;712 let mut target_collection = <Collection<T>>::get(collection_id);713 target_collection.access = mode;714 <Collection<T>>::insert(collection_id, target_collection);715716 Ok(())717 }718719 /// Allows Anyone to create tokens if:720 /// * White List is enabled, and721 /// * Address is added to white list, and722 /// * This method was called with True parameter723 /// 724 /// # Permissions725 /// * Collection Owner726 ///727 /// # Arguments728 /// 729 /// * collection_id.730 /// 731 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.732 #[weight = <T as Config>::WeightInfo::set_mint_permission()]733 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult734 {735 let sender = ensure_signed(origin)?;736737 Self::check_owner_permissions(collection_id, sender)?;738 let mut target_collection = <Collection<T>>::get(collection_id);739 target_collection.mint_mode = mint_permission;740 <Collection<T>>::insert(collection_id, target_collection);741742 Ok(())743 }744745 /// Change the owner of the collection.746 /// 747 /// # Permissions748 /// 749 /// * Collection Owner.750 /// 751 /// # Arguments752 /// 753 /// * collection_id.754 /// 755 /// * new_owner.756 #[weight = <T as Config>::WeightInfo::change_collection_owner()]757 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {758759 let sender = ensure_signed(origin)?;760 Self::check_owner_permissions(collection_id, sender)?;761 let mut target_collection = <Collection<T>>::get(collection_id);762 target_collection.owner = new_owner;763 <Collection<T>>::insert(collection_id, target_collection);764765 Ok(())766 }767768 /// Adds an admin of the Collection.769 /// 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. 770 /// 771 /// # Permissions772 /// 773 /// * Collection Owner.774 /// * Collection Admin.775 /// 776 /// # Arguments777 /// 778 /// * collection_id: ID of the Collection to add admin for.779 /// 780 /// * new_admin_id: Address of new admin to add.781 #[weight = <T as Config>::WeightInfo::add_collection_admin()]782 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {783784 let sender = ensure_signed(origin)?;785 Self::check_owner_or_admin_permissions(collection_id, sender)?;786 let mut admin_arr: Vec<T::AccountId> = Vec::new();787788 if <AdminList<T>>::contains_key(collection_id)789 {790 admin_arr = <AdminList<T>>::get(collection_id);791 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);792 }793794 // Number of collection admins795 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);796797 admin_arr.push(new_admin_id);798 <AdminList<T>>::insert(collection_id, admin_arr);799800 Ok(())801 }802803 /// 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.804 ///805 /// # Permissions806 /// 807 /// * Collection Owner.808 /// * Collection Admin.809 /// 810 /// # Arguments811 /// 812 /// * collection_id: ID of the Collection to remove admin for.813 /// 814 /// * account_id: Address of admin to remove.815 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]816 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {817818 let sender = ensure_signed(origin)?;819 Self::check_owner_or_admin_permissions(collection_id, sender)?;820 ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);821822 let mut admin_arr = <AdminList<T>>::get(collection_id);823 admin_arr.retain(|i| *i != account_id);824 <AdminList<T>>::insert(collection_id, admin_arr);825826 Ok(())827 }828829 /// # Permissions830 /// 831 /// * Collection Owner832 /// 833 /// # Arguments834 /// 835 /// * collection_id.836 /// 837 /// * new_sponsor.838 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]839 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {840841 let sender = ensure_signed(origin)?;842 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);843844 let mut target_collection = <Collection<T>>::get(collection_id);845 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);846847 target_collection.sponsor = new_sponsor;848 target_collection.sponsor_confirmed = false;849 <Collection<T>>::insert(collection_id, target_collection);850851 Ok(())852 }853854 /// # Permissions855 /// 856 /// * Sponsor.857 /// 858 /// # Arguments859 /// 860 /// * collection_id.861 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]862 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {863864 let sender = ensure_signed(origin)?;865 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);866867 let mut target_collection = <Collection<T>>::get(collection_id);868 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);869870 target_collection.sponsor_confirmed = true;871 <Collection<T>>::insert(collection_id, target_collection);872873 Ok(())874 }875876 /// Switch back to pay-per-own-transaction model.877 ///878 /// # Permissions879 ///880 /// * Collection owner.881 /// 882 /// # Arguments883 /// 884 /// * collection_id.885 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]886 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {887888 let sender = ensure_signed(origin)?;889 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);890891 let mut target_collection = <Collection<T>>::get(collection_id);892 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);893894 target_collection.sponsor = T::AccountId::default();895 target_collection.sponsor_confirmed = false;896 <Collection<T>>::insert(collection_id, target_collection);897898 Ok(())899 }900901 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.902 /// 903 /// # Permissions904 /// 905 /// * Collection Owner.906 /// * Collection Admin.907 /// * Anyone if908 /// * White List is enabled, and909 /// * Address is added to white list, and910 /// * MintPermission is enabled (see SetMintPermission method)911 /// 912 /// # Arguments913 /// 914 /// * collection_id: ID of the collection.915 /// 916 /// * owner: Address, initial owner of the NFT.917 ///918 /// * data: Token data to store on chain.919 // #[weight =920 // (130_000_000 as Weight)921 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))922 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))923 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]924925 #[weight = <T as Config>::WeightInfo::create_item(data.len())]926 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {927928 let sender = ensure_signed(origin)?;929930 Self::collection_exists(collection_id)?;931932 let target_collection = <Collection<T>>::get(collection_id);933934 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;935 Self::validate_create_item_args(&target_collection, &data)?;936 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;937938 Ok(())939 }940941 /// This method creates multiple instances of NFT Collection created with CreateCollection method.942 /// 943 /// # Permissions944 /// 945 /// * Collection Owner.946 /// * Collection Admin.947 /// * Anyone if948 /// * White List is enabled, and949 /// * Address is added to white list, and950 /// * MintPermission is enabled (see SetMintPermission method)951 /// 952 /// # Arguments953 /// 954 /// * collection_id: ID of the collection.955 /// 956 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].957 /// 958 /// * owner: Address, initial owner of the NFT.959 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()960 .map(|data| { data.len() })961 .sum())]962 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {963964 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);965 let sender = ensure_signed(origin)?;966967 Self::collection_exists(collection_id)?;968 let target_collection = <Collection<T>>::get(collection_id);969970 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;971972 for data in &items_data {973 Self::validate_create_item_args(&target_collection, data)?;974 }975 for data in &items_data {976 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;977 }978979 Ok(())980 }981982 /// Destroys a concrete instance of NFT.983 /// 984 /// # Permissions985 /// 986 /// * Collection Owner.987 /// * Collection Admin.988 /// * Current NFT Owner.989 /// 990 /// # Arguments991 /// 992 /// * collection_id: ID of the collection.993 /// 994 /// * item_id: ID of NFT to burn.995 #[weight = <T as Config>::WeightInfo::burn_item()]996 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {997998 let sender = ensure_signed(origin)?;999 Self::collection_exists(collection_id)?;10001001 // Transfer permissions check1002 let target_collection = <Collection<T>>::get(collection_id);1003 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1004 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1005 Error::<T>::NoPermission);10061007 if target_collection.access == AccessMode::WhiteList {1008 Self::check_white_list(collection_id, &sender)?;1009 }10101011 match target_collection.mode1012 {1013 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1014 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1015 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, &sender)?,1016 _ => ()1017 };10181019 // call event1020 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10211022 Ok(())1023 }10241025 /// Change ownership of the token.1026 /// 1027 /// # Permissions1028 /// 1029 /// * Collection Owner1030 /// * Collection Admin1031 /// * Current NFT owner1032 ///1033 /// # Arguments1034 /// 1035 /// * recipient: Address of token recipient.1036 /// 1037 /// * collection_id.1038 /// 1039 /// * item_id: ID of the item1040 /// * Non-Fungible Mode: Required.1041 /// * Fungible Mode: Ignored.1042 /// * Re-Fungible Mode: Required.1043 /// 1044 /// * value: Amount to transfer.1045 /// * Non-Fungible Mode: Ignored1046 /// * Fungible Mode: Must specify transferred amount1047 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1048 #[weight = <T as Config>::WeightInfo::transfer()]1049 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1050 let sender = ensure_signed(origin)?;1051 Self::transfer_internal(sender, recipient, collection_id, item_id, value)1052 }10531054 /// Set, change, or remove approved address to transfer the ownership of the NFT.1055 /// 1056 /// # Permissions1057 /// 1058 /// * Collection Owner1059 /// * Collection Admin1060 /// * Current NFT owner1061 /// 1062 /// # Arguments1063 /// 1064 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1065 /// 1066 /// * collection_id.1067 /// 1068 /// * item_id: ID of the item.1069 #[weight = <T as Config>::WeightInfo::approve()]1070 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10711072 let sender = ensure_signed(origin)?;10731074 Self::collection_exists(collection_id)?;1075 Self::token_exists(collection_id, item_id, &sender)?;10761077 // Transfer permissions check1078 let target_collection = <Collection<T>>::get(collection_id);1079 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1080 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1081 Error::<T>::NoPermission);10821083 if target_collection.access == AccessMode::WhiteList {1084 Self::check_white_list(collection_id, &sender)?;1085 Self::check_white_list(collection_id, &spender)?;1086 }10871088 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1089 let mut allowance: u128 = amount;1090 if allowance_exists {1091 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1092 }1093 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);10941095 Ok(())1096 }1097 1098 /// 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.1099 /// 1100 /// # Permissions1101 /// * Collection Owner1102 /// * Collection Admin1103 /// * Current NFT owner1104 /// * Address approved by current NFT owner1105 /// 1106 /// # Arguments1107 /// 1108 /// * from: Address that owns token.1109 /// 1110 /// * recipient: Address of token recipient.1111 /// 1112 /// * collection_id.1113 /// 1114 /// * item_id: ID of the item.1115 /// 1116 /// * value: Amount to transfer.1117 #[weight = <T as Config>::WeightInfo::transfer_from()]1118 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11191120 let sender = ensure_signed(origin)?;1121 let mut appoved_transfer = false;11221123 // Check approval1124 let mut approval: u128 = 0;1125 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1126 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1127 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1128 appoved_transfer = true;1129 }11301131 let target_collection = <Collection<T>>::get(collection_id);11321133 // Limits check1134 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11351136 // Transfer permissions check 1137 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1138 Error::<T>::NoPermission);11391140 if target_collection.access == AccessMode::WhiteList {1141 Self::check_white_list(collection_id, &sender)?;1142 Self::check_white_list(collection_id, &recipient)?;1143 }11441145 // Reduce approval by transferred amount or remove if remaining approval drops to 01146 if approval.checked_sub(value).unwrap_or(0) > 0 {1147 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1148 }1149 else {1150 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1151 }11521153 match target_collection.mode1154 {1155 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1156 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1157 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1158 _ => ()1159 };11601161 Ok(())1162 }11631164 // #[weight = 0]1165 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11661167 // // let no_perm_mes = "You do not have permissions to modify this collection";1168 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1169 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1170 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11711172 // // // on_nft_received call11731174 // // Self::transfer(origin, collection_id, item_id, new_owner)?;11751176 // Ok(())1177 // }11781179 /// Set off-chain data schema.1180 /// 1181 /// # Permissions1182 /// 1183 /// * Collection Owner1184 /// * Collection Admin1185 /// 1186 /// # Arguments1187 /// 1188 /// * collection_id.1189 /// 1190 /// * schema: String representing the offchain data schema.1191 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1192 pub fn set_variable_meta_data (1193 origin,1194 collection_id: CollectionId,1195 item_id: TokenId,1196 data: Vec<u8>1197 ) -> DispatchResult {1198 let sender = ensure_signed(origin)?;1199 1200 Self::collection_exists(collection_id)?;1201 Self::token_exists(collection_id, item_id, &sender)?;12021203 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12041205 // Modify permissions check1206 let target_collection = <Collection<T>>::get(collection_id);1207 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1208 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1209 Error::<T>::NoPermission);12101211 match target_collection.mode1212 {1213 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1214 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1215 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1216 _ => fail!(Error::<T>::UnexpectedCollectionType)1217 };12181219 Ok(())1220 }1221 1222 /// Set schema standard1223 /// ImageURL1224 /// Unique1225 /// 1226 /// # Permissions1227 /// 1228 /// * Collection Owner1229 /// * Collection Admin1230 /// 1231 /// # Arguments1232 /// 1233 /// * collection_id.1234 /// 1235 /// * schema: SchemaVersion: enum1236 #[weight = <T as Config>::WeightInfo::set_schema_version()]1237 pub fn set_schema_version(1238 origin,1239 collection_id: CollectionId,1240 version: SchemaVersion1241 ) -> DispatchResult {1242 let sender = ensure_signed(origin)?;1243 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1244 let mut target_collection = <Collection<T>>::get(collection_id);1245 target_collection.schema_version = version;1246 <Collection<T>>::insert(collection_id, target_collection);12471248 Ok(())1249 }12501251 /// Set off-chain data schema.1252 /// 1253 /// # Permissions1254 /// 1255 /// * Collection Owner1256 /// * Collection Admin1257 /// 1258 /// # Arguments1259 /// 1260 /// * collection_id.1261 /// 1262 /// * schema: String representing the offchain data schema.1263 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1264 pub fn set_offchain_schema(1265 origin,1266 collection_id: CollectionId,1267 schema: Vec<u8>1268 ) -> DispatchResult {1269 let sender = ensure_signed(origin)?;1270 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12711272 // check schema limit1273 ensure!(schema.len() as u32 > ChainLimit::get().offchain_schema_limit, "");12741275 let mut target_collection = <Collection<T>>::get(collection_id);1276 target_collection.offchain_schema = schema;1277 <Collection<T>>::insert(collection_id, target_collection);12781279 Ok(())1280 }12811282 /// Set const on-chain data schema.1283 /// 1284 /// # Permissions1285 /// 1286 /// * Collection Owner1287 /// * Collection Admin1288 /// 1289 /// # Arguments1290 /// 1291 /// * collection_id.1292 /// 1293 /// * schema: String representing the const on-chain data schema.1294 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1295 pub fn set_const_on_chain_schema (1296 origin,1297 collection_id: CollectionId,1298 schema: Vec<u8>1299 ) -> DispatchResult {1300 let sender = ensure_signed(origin)?;1301 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13021303 // check schema limit1304 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");13051306 let mut target_collection = <Collection<T>>::get(collection_id);1307 target_collection.const_on_chain_schema = schema;1308 <Collection<T>>::insert(collection_id, target_collection);13091310 Ok(())1311 }13121313 /// Set variable on-chain data schema.1314 /// 1315 /// # Permissions1316 /// 1317 /// * Collection Owner1318 /// * Collection Admin1319 /// 1320 /// # Arguments1321 /// 1322 /// * collection_id.1323 /// 1324 /// * schema: String representing the variable on-chain data schema.1325 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1326 pub fn set_variable_on_chain_schema (1327 origin,1328 collection_id: CollectionId,1329 schema: Vec<u8>1330 ) -> DispatchResult {1331 let sender = ensure_signed(origin)?;1332 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13331334 // check schema limit1335 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");13361337 let mut target_collection = <Collection<T>>::get(collection_id);1338 target_collection.variable_on_chain_schema = schema;1339 <Collection<T>>::insert(collection_id, target_collection);13401341 Ok(())1342 }13431344 // Sudo permissions function1345 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1346 pub fn set_chain_limits(1347 origin,1348 limits: ChainLimits1349 ) -> DispatchResult {13501351 #[cfg(not(feature = "runtime-benchmarks"))]1352 ensure_root(origin)?;13531354 <ChainLimit>::put(limits);1355 Ok(())1356 }13571358 /// Enable smart contract self-sponsoring.1359 /// 1360 /// # Permissions1361 /// 1362 /// * Contract Owner1363 /// 1364 /// # Arguments1365 /// 1366 /// * contract address1367 /// * enable flag1368 /// 1369 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1370 pub fn enable_contract_sponsoring(1371 origin,1372 contract_address: T::AccountId,1373 enable: bool1374 ) -> DispatchResult {13751376 let sender = ensure_signed(origin)?;13771378 #[cfg(feature = "runtime-benchmarks")]1379 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13801381 Self::ensure_contract_owned(sender, &contract_address)?;13821383 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1384 Ok(())1385 }13861387 /// Set the rate limit for contract sponsoring to specified number of blocks.1388 /// 1389 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1390 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1391 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1392 /// from contract endowment if there are at least B blocks between such transactions. 1393 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1394 /// 1395 /// # Permissions1396 /// 1397 /// * Contract Owner1398 /// 1399 /// # Arguments1400 /// 1401 /// -`contract_address`: Address of the contract to sponsor1402 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1403 /// 1404 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1405 pub fn set_contract_sponsoring_rate_limit(1406 origin,1407 contract_address: T::AccountId,1408 rate_limit: T::BlockNumber1409 ) -> DispatchResult {1410 let sender = ensure_signed(origin)?;14111412 #[cfg(feature = "runtime-benchmarks")]1413 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14141415 Self::ensure_contract_owned(sender, &contract_address)?;1416 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1417 Ok(())1418 }14191420 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1421 /// 1422 /// # Permissions1423 /// 1424 /// * Address that deployed smart contract.1425 /// 1426 /// # Arguments1427 /// 1428 /// -`contract_address`: Address of the contract.1429 /// 1430 /// - `enable`: . 1431 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1432 pub fn toggle_contract_white_list(1433 origin,1434 contract_address: T::AccountId,1435 enable: bool1436 ) -> DispatchResult {1437 let sender = ensure_signed(origin)?;14381439 #[cfg(feature = "runtime-benchmarks")]1440 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14411442 Self::ensure_contract_owned(sender, &contract_address)?;1443 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1444 Ok(())1445 }1446 1447 /// Add an address to smart contract white list.1448 /// 1449 /// # Permissions1450 /// 1451 /// * Address that deployed smart contract.1452 /// 1453 /// # Arguments1454 /// 1455 /// -`contract_address`: Address of the contract.1456 ///1457 /// -`account_address`: Address to add.1458 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1459 pub fn add_to_contract_white_list(1460 origin,1461 contract_address: T::AccountId,1462 account_address: T::AccountId1463 ) -> DispatchResult {1464 let sender = ensure_signed(origin)?;14651466 #[cfg(feature = "runtime-benchmarks")]1467 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1468 1469 Self::ensure_contract_owned(sender, &contract_address)?; 1470 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1471 Ok(())1472 }14731474 /// Remove an address from smart contract white list.1475 /// 1476 /// # Permissions1477 /// 1478 /// * Address that deployed smart contract.1479 /// 1480 /// # Arguments1481 /// 1482 /// -`contract_address`: Address of the contract.1483 ///1484 /// -`account_address`: Address to remove.1485 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1486 pub fn remove_from_contract_white_list(1487 origin,1488 contract_address: T::AccountId,1489 account_address: T::AccountId1490 ) -> DispatchResult {1491 let sender = ensure_signed(origin)?;14921493 #[cfg(feature = "runtime-benchmarks")]1494 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14951496 Self::ensure_contract_owned(sender, &contract_address)?;1497 <ContractWhiteList<T>>::remove(contract_address, account_address);1498 Ok(())1499 }15001501 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1502 pub fn set_collection_limits(1503 origin,1504 collection_id: u32,1505 limits: CollectionLimits,1506 ) -> DispatchResult {1507 let sender = ensure_signed(origin)?;1508 Self::check_owner_permissions(collection_id, sender.clone())?;1509 let mut target_collection = <Collection<T>>::get(collection_id);1510 let chain_limits = ChainLimit::get();1511 let climits = target_collection.limits;15121513 // collection bounds1514 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1515 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1516 Error::<T>::CollectionLimitBoundsExceeded);15171518 // token_limit check prev1519 ensure!(climits.token_limit > limits.token_limit && 1520 limits.token_limit <= chain_limits.account_token_ownership_limit, 1521 Error::<T>::AccountTokenLimitExceeded);15221523 target_collection.limits = limits;1524 <Collection<T>>::insert(collection_id, target_collection);15251526 Ok(())1527 } 1528 }1529}15301531impl<T: Config> Module<T> {15321533 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {15341535 let target_collection = <Collection<T>>::get(collection_id);15361537 // Limits check1538 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;15391540 // Transfer permissions check1541 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1542 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1543 Error::<T>::NoPermission);15441545 if target_collection.access == AccessMode::WhiteList {1546 Self::check_white_list(collection_id, &sender)?;1547 Self::check_white_list(collection_id, &recipient)?;1548 }15491550 match target_collection.mode1551 {1552 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1553 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1554 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1555 _ => ()1556 };15571558 Ok(())1559 }156015611562 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15631564 // check token limit and account token limit1565 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1566 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1567 1568 Ok(())1569 }15701571 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15721573 // check token limit and account token limit1574 let total_items: u32 = ItemListIndex::get(collection_id);1575 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1576 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1577 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15781579 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1580 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1581 Self::check_white_list(collection_id, owner)?;1582 Self::check_white_list(collection_id, sender)?;1583 }15841585 Ok(())1586 }15871588 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1589 match target_collection.mode1590 {1591 CollectionMode::NFT => {1592 if let CreateItemData::NFT(data) = data {1593 // check sizes1594 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1595 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1596 } else {1597 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1598 }1599 },1600 CollectionMode::Fungible(_) => {1601 if let CreateItemData::Fungible(_) = data {1602 } else {1603 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1604 }1605 },1606 CollectionMode::ReFungible(_) => {1607 if let CreateItemData::ReFungible(data) = data {16081609 // check sizes1610 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1611 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1612 } else {1613 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1614 }1615 },1616 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1617 };16181619 Ok(())1620 }16211622 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1623 match data1624 {1625 CreateItemData::NFT(data) => {1626 let item = NftItemType {1627 owner,1628 const_data: data.const_data,1629 variable_data: data.variable_data1630 };16311632 Self::add_nft_item(collection_id, item)?;1633 },1634 CreateItemData::Fungible(data) => {1635 Self::add_fungible_item(collection_id, &owner, data.value)?;1636 },1637 CreateItemData::ReFungible(data) => {1638 let mut owner_list = Vec::new();1639 let value = (10 as u128).pow(collection.decimal_points as u32);1640 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16411642 let item = ReFungibleItemType {1643 owner: owner_list,1644 const_data: data.const_data,1645 variable_data: data.variable_data1646 };16471648 Self::add_refungible_item(collection_id, item)?;1649 }1650 };16511652 // call event1653 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16541655 Ok(())1656 }16571658 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16591660 // Does new owner already have an account?1661 let mut balance: u128 = 0;1662 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1663 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1664 } 16651666 // Mint 1667 let item = FungibleItemType {1668 value: balance + value1669 };1670 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16711672 // Update balance1673 let new_balance = <Balance<T>>::get(collection_id, owner)1674 .checked_add(value)1675 .ok_or(Error::<T>::NumOverflow)?;1676 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16771678 Ok(())1679 }16801681 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1682 let current_index = <ItemListIndex>::get(collection_id)1683 .checked_add(1)1684 .ok_or(Error::<T>::NumOverflow)?;1685 let itemcopy = item.clone();16861687 let value = item.owner.first().unwrap().fraction;1688 let owner = item.owner.first().unwrap().owner.clone();16891690 Self::add_token_index(collection_id, current_index, &owner)?;16911692 <ItemListIndex>::insert(collection_id, current_index);1693 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16941695 // Update balance1696 let new_balance = <Balance<T>>::get(collection_id, &owner)1697 .checked_add(value)1698 .ok_or(Error::<T>::NumOverflow)?;1699 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17001701 Ok(())1702 }17031704 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1705 let current_index = <ItemListIndex>::get(collection_id)1706 .checked_add(1)1707 .ok_or(Error::<T>::NumOverflow)?;17081709 let item_owner = item.owner.clone();1710 Self::add_token_index(collection_id, current_index, &item.owner)?;17111712 <ItemListIndex>::insert(collection_id, current_index);1713 <NftItemList<T>>::insert(collection_id, current_index, item);17141715 // Update balance1716 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1717 .checked_add(1)1718 .ok_or(Error::<T>::NumOverflow)?;1719 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17201721 Ok(())1722 }17231724 fn burn_refungible_item(1725 collection_id: CollectionId,1726 item_id: TokenId,1727 owner: &T::AccountId,1728 ) -> DispatchResult {1729 ensure!(1730 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1731 Error::<T>::TokenNotFound1732 );1733 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1734 let rft_balance = token1735 .owner1736 .iter()1737 .filter(|&i| i.owner == *owner)1738 .next()1739 .unwrap();1740 Self::remove_token_index(collection_id, item_id, owner)?;17411742 // update balance1743 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1744 .checked_sub(rft_balance.fraction)1745 .ok_or(Error::<T>::NumOverflow)?;1746 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17471748 // Re-create owners list with sender removed1749 let index = token1750 .owner1751 .iter()1752 .position(|i| i.owner == *owner)1753 .unwrap();1754 token.owner.remove(index);1755 let owner_count = token.owner.len();17561757 // Burn the token completely if this was the last (only) owner1758 if owner_count == 0 {1759 <ReFungibleItemList<T>>::remove(collection_id, item_id);1760 }1761 else {1762 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1763 }17641765 Ok(())1766 }17671768 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1769 ensure!(1770 <NftItemList<T>>::contains_key(collection_id, item_id),1771 Error::<T>::TokenNotFound1772 );1773 let item = <NftItemList<T>>::get(collection_id, item_id);1774 Self::remove_token_index(collection_id, item_id, &item.owner)?;17751776 // update balance1777 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1778 .checked_sub(1)1779 .ok_or(Error::<T>::NumOverflow)?;1780 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1781 <NftItemList<T>>::remove(collection_id, item_id);17821783 Ok(())1784 }17851786 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1787 ensure!(1788 <FungibleItemList<T>>::contains_key(collection_id, owner),1789 Error::<T>::TokenNotFound1790 );1791 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1792 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17931794 // update balance1795 let new_balance = <Balance<T>>::get(collection_id, owner)1796 .checked_sub(value)1797 .ok_or(Error::<T>::NumOverflow)?;1798 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17991800 if balance.value - value > 0 {1801 balance.value -= value;1802 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1803 }1804 else {1805 <FungibleItemList<T>>::remove(collection_id, owner);1806 }18071808 Ok(())1809 }18101811 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1812 ensure!(1813 <Collection<T>>::contains_key(collection_id),1814 Error::<T>::CollectionNotFound1815 );1816 Ok(())1817 }18181819 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1820 Self::collection_exists(collection_id)?;18211822 let target_collection = <Collection<T>>::get(collection_id);1823 ensure!(1824 subject == target_collection.owner,1825 Error::<T>::NoPermission1826 );18271828 Ok(())1829 }18301831 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1832 let target_collection = <Collection<T>>::get(collection_id);1833 let mut result: bool = subject == target_collection.owner;1834 let exists = <AdminList<T>>::contains_key(collection_id);18351836 if !result & exists {1837 if <AdminList<T>>::get(collection_id).contains(&subject) {1838 result = true1839 }1840 }18411842 result1843 }18441845 fn check_owner_or_admin_permissions(1846 collection_id: CollectionId,1847 subject: T::AccountId,1848 ) -> DispatchResult {1849 Self::collection_exists(collection_id)?;1850 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18511852 ensure!(1853 result,1854 Error::<T>::NoPermission1855 );1856 Ok(())1857 }18581859 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1860 let target_collection = <Collection<T>>::get(collection_id);18611862 match target_collection.mode {1863 CollectionMode::NFT => {1864 <NftItemList<T>>::get(collection_id, item_id).owner == subject1865 }1866 CollectionMode::Fungible(_) => {1867 <FungibleItemList<T>>::contains_key(collection_id, &subject)1868 }1869 CollectionMode::ReFungible(_) => {1870 <ReFungibleItemList<T>>::get(collection_id, item_id)1871 .owner1872 .iter()1873 .any(|i| i.owner == subject)1874 }1875 CollectionMode::Invalid => false,1876 }1877 }18781879 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1880 let mes = Error::<T>::AddresNotInWhiteList;1881 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18821883 Ok(())1884 }18851886 /// Check if token exists. In case of Fungible, check if there is an entry for 1887 /// the owner in fungible balances double map1888 fn token_exists(1889 collection_id: CollectionId,1890 item_id: TokenId,1891 owner: &T::AccountId1892 ) -> DispatchResult {1893 let target_collection = <Collection<T>>::get(collection_id);1894 let exists = match target_collection.mode1895 {1896 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1897 CollectionMode::Fungible(_) => <FungibleItemList<T>>::contains_key(collection_id, owner),1898 CollectionMode::ReFungible(_) => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1899 _ => false1900 };19011902 ensure!(exists == true, Error::<T>::TokenNotFound);1903 Ok(())1904 }19051906 fn transfer_fungible(1907 collection_id: CollectionId,1908 value: u128,1909 owner: &T::AccountId,1910 recipient: &T::AccountId,1911 ) -> DispatchResult {1912 Self::token_exists(collection_id, 0, owner)?;19131914 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1915 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19161917 // Send balance to recipient (updates balanceOf of recipient)1918 Self::add_fungible_item(collection_id, recipient, value)?;19191920 // update balanceOf of sender1921 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19221923 // Reduce or remove sender1924 if balance.value == value {1925 <FungibleItemList<T>>::remove(collection_id, owner);1926 }1927 else {1928 balance.value -= value;1929 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1930 }19311932 Ok(())1933 }19341935 fn transfer_refungible(1936 collection_id: CollectionId,1937 item_id: TokenId,1938 value: u128,1939 owner: T::AccountId,1940 new_owner: T::AccountId,1941 ) -> DispatchResult {1942 Self::token_exists(collection_id, item_id, &owner)?;19431944 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1945 let item = full_item1946 .owner1947 .iter()1948 .filter(|i| i.owner == owner)1949 .next()1950 .ok_or(Error::<T>::NumOverflow)?;1951 let amount = item.fraction;19521953 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19541955 // update balance1956 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1957 .checked_sub(value)1958 .ok_or(Error::<T>::NumOverflow)?;1959 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19601961 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1962 .checked_add(value)1963 .ok_or(Error::<T>::NumOverflow)?;1964 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19651966 let old_owner = item.owner.clone();1967 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19681969 // transfer1970 if amount == value && !new_owner_has_account {1971 // change owner1972 // new owner do not have account1973 let mut new_full_item = full_item.clone();1974 new_full_item1975 .owner1976 .iter_mut()1977 .find(|i| i.owner == owner)1978 .unwrap()1979 .owner = new_owner.clone();1980 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19811982 // update index collection1983 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1984 } else {1985 let mut new_full_item = full_item.clone();1986 new_full_item1987 .owner1988 .iter_mut()1989 .find(|i| i.owner == owner)1990 .unwrap()1991 .fraction -= value;19921993 // separate amount1994 if new_owner_has_account {1995 // new owner has account1996 new_full_item1997 .owner1998 .iter_mut()1999 .find(|i| i.owner == new_owner)2000 .unwrap()2001 .fraction += value;2002 } else {2003 // new owner do not have account2004 new_full_item.owner.push(Ownership {2005 owner: new_owner.clone(),2006 fraction: value,2007 });2008 Self::add_token_index(collection_id, item_id, &new_owner)?;2009 }20102011 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2012 }20132014 Ok(())2015 }20162017 fn transfer_nft(2018 collection_id: CollectionId,2019 item_id: TokenId,2020 sender: T::AccountId,2021 new_owner: T::AccountId,2022 ) -> DispatchResult {2023 Self::token_exists(collection_id, item_id, &sender)?;20242025 let mut item = <NftItemList<T>>::get(collection_id, item_id);20262027 ensure!(2028 sender == item.owner,2029 Error::<T>::MustBeTokenOwner2030 );20312032 // update balance2033 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2034 .checked_sub(1)2035 .ok_or(Error::<T>::NumOverflow)?;2036 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20372038 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2039 .checked_add(1)2040 .ok_or(Error::<T>::NumOverflow)?;2041 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20422043 // change owner2044 let old_owner = item.owner.clone();2045 item.owner = new_owner.clone();2046 <NftItemList<T>>::insert(collection_id, item_id, item);20472048 // update index collection2049 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20502051 Ok(())2052 }2053 2054 fn set_re_fungible_variable_data(2055 collection_id: CollectionId,2056 item_id: TokenId,2057 data: Vec<u8>2058 ) -> DispatchResult {2059 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20602061 item.variable_data = data;20622063 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20642065 Ok(())2066 }20672068 fn set_nft_variable_data(2069 collection_id: CollectionId,2070 item_id: TokenId,2071 data: Vec<u8>2072 ) -> DispatchResult {2073 let mut item = <NftItemList<T>>::get(collection_id, item_id);2074 2075 item.variable_data = data;20762077 <NftItemList<T>>::insert(collection_id, item_id, item);2078 2079 Ok(())2080 }20812082 fn init_collection(item: &CollectionType<T::AccountId>) {2083 // check params2084 assert!(2085 item.decimal_points <= MAX_DECIMAL_POINTS,2086 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2087 );2088 assert!(2089 item.name.len() <= 64,2090 "Collection name can not be longer than 63 char"2091 );2092 assert!(2093 item.name.len() <= 256,2094 "Collection description can not be longer than 255 char"2095 );2096 assert!(2097 item.token_prefix.len() <= 16,2098 "Token prefix can not be longer than 15 char"2099 );21002101 // Generate next collection ID2102 let next_id = CreatedCollectionCount::get()2103 .checked_add(1)2104 .unwrap();21052106 CreatedCollectionCount::put(next_id);2107 }21082109 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2110 let current_index = <ItemListIndex>::get(collection_id)2111 .checked_add(1)2112 .unwrap();21132114 let item_owner = item.owner.clone();2115 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21162117 <ItemListIndex>::insert(collection_id, current_index);21182119 // Update balance2120 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2121 .checked_add(1)2122 .unwrap();2123 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2124 }21252126 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2127 let current_index = <ItemListIndex>::get(collection_id)2128 .checked_add(1)2129 .unwrap();21302131 Self::add_token_index(collection_id, current_index, owner).unwrap();21322133 <ItemListIndex>::insert(collection_id, current_index);21342135 // Update balance2136 let new_balance = <Balance<T>>::get(collection_id, owner)2137 .checked_add(item.value)2138 .unwrap();2139 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2140 }21412142 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2143 let current_index = <ItemListIndex>::get(collection_id)2144 .checked_add(1)2145 .unwrap();21462147 let value = item.owner.first().unwrap().fraction;2148 let owner = item.owner.first().unwrap().owner.clone();21492150 Self::add_token_index(collection_id, current_index, &owner).unwrap();21512152 <ItemListIndex>::insert(collection_id, current_index);21532154 // Update balance2155 let new_balance = <Balance<T>>::get(collection_id, &owner)2156 .checked_add(value)2157 .unwrap();2158 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2159 }21602161 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {21622163 // add to account limit2164 if <AccountItemCount<T>>::contains_key(owner) {21652166 // bound Owned tokens by a single address2167 let count = <AccountItemCount<T>>::get(owner);2168 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21692170 <AccountItemCount<T>>::insert(owner.clone(), count2171 .checked_add(1)2172 .ok_or(Error::<T>::NumOverflow)?);2173 }2174 else {2175 <AccountItemCount<T>>::insert(owner.clone(), 1);2176 }21772178 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2179 if list_exists {2180 let mut list = <AddressTokens<T>>::get(collection_id, owner);2181 let item_contains = list.contains(&item_index.clone());21822183 if !item_contains {2184 list.push(item_index.clone());2185 }21862187 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2188 } else {2189 let mut itm = Vec::new();2190 itm.push(item_index.clone());2191 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2192 }21932194 Ok(())2195 }21962197 fn remove_token_index(2198 collection_id: CollectionId,2199 item_index: TokenId,2200 owner: &T::AccountId,2201 ) -> DispatchResult {22022203 // update counter2204 <AccountItemCount<T>>::insert(owner.clone(), 2205 <AccountItemCount<T>>::get(owner)2206 .checked_sub(1)2207 .ok_or(Error::<T>::NumOverflow)?);220822092210 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2211 if list_exists {2212 let mut list = <AddressTokens<T>>::get(collection_id, owner);2213 let item_contains = list.contains(&item_index.clone());22142215 if item_contains {2216 list.retain(|&item| item != item_index);2217 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2218 }2219 }22202221 Ok(())2222 }22232224 fn move_token_index(2225 collection_id: CollectionId,2226 item_index: TokenId,2227 old_owner: &T::AccountId,2228 new_owner: &T::AccountId,2229 ) -> DispatchResult {2230 Self::remove_token_index(collection_id, item_index, old_owner)?;2231 Self::add_token_index(collection_id, item_index, new_owner)?;22322233 Ok(())2234 }2235 2236 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2237 if <ContractOwner<T>>::contains_key(contract.clone()) {2238 let owner = <ContractOwner<T>>::get(contract);2239 ensure!(account == owner, Error::<T>::NoPermission);2240 } else {2241 fail!(Error::<T>::NoPermission);2242 }22432244 Ok(())2245 }2246}22472248////////////////////////////////////////////////////////////////////////////////////////////////////2249// Economic models2250// #region22512252/// Fee multiplier.2253pub type Multiplier = FixedU128;22542255type BalanceOf<T> = <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::Balance;22562257/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2258/// in the queue.2259#[derive(Encode, Decode, Clone, Eq, PartialEq)]2260pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);22612262impl<T: Config + Send + Sync> sp_std::fmt::Debug 2263 for ChargeTransactionPayment<T>2264{2265 #[cfg(feature = "std")]2266 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2267 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2268 }2269 #[cfg(not(feature = "std"))]2270 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2271 Ok(())2272 }2273}22742275impl<T: Config> ChargeTransactionPayment<T>2276where2277 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2278 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2279 T::AccountId: AsRef<[u8]>,2280 T::AccountId: UncheckedFrom<T::Hash>,2281{2282 fn traditional_fee(2283 len: usize,2284 info: &DispatchInfoOf<T::Call>,2285 tip: BalanceOf<T>,2286 ) -> BalanceOf<T>2287 where2288 T::Call: Dispatchable<Info = DispatchInfo>,2289 {2290 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2291 }22922293 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2294 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2295 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2296 let len_saturation = max_block_length as u64 / (len as u64).max(1);2297 let coefficient: BalanceOf<T> = weight_saturation2298 .min(len_saturation)2299 .saturated_into::<BalanceOf<T>>();2300 final_fee2301 .saturating_mul(coefficient)2302 .saturated_into::<TransactionPriority>()2303 }23042305 fn withdraw_fee(2306 &self,2307 who: &T::AccountId,2308 call: &T::Call,2309 info: &DispatchInfoOf<T::Call>,2310 len: usize,2311 ) -> Result<2312 (2313 BalanceOf<T>,2314 <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2315 ),2316 TransactionValidityError,2317 > {2318 let tip = self.0;23192320 // Set fee based on call type. Creating collection costs 1 Unique.2321 // All other transactions have traditional fees so far2322 // let fee = match call.is_sub_type() {2323 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2324 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2325 // // _ => <BalanceOf<T>>::from(100)2326 // };2327 let fee = Self::traditional_fee(len, info, tip);23282329 // Only mess with balances if fee is not zero.2330 if fee.is_zero() {2331 return <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2332 .map(|i| (fee, i));2333 }23342335 // Determine who is paying transaction fee based on ecnomic model2336 // Parse call to extract collection ID and access collection sponsor2337 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2338 Some(Call::create_item(collection_id, _owner, _properties)) => {23392340 // sponsor timeout2341 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23422343 let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2344 let mut sponsored = true;2345 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2346 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2347 let limit_time = last_tx_block + limit.into();2348 if block_number <= limit_time {2349 sponsored = false;2350 }2351 }2352 if sponsored {2353 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2354 }23552356 // check free create limit2357 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2358 (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2359 (sponsored)2360 {2361 <Collection<T>>::get(collection_id).sponsor2362 } else {2363 T::AccountId::default()2364 }2365 }2366 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2367 2368 let mut sponsor_transfer = false;2369 if <Collection<T>>::get(collection_id).sponsor_confirmed {23702371 let collection_limits = <Collection<T>>::get(collection_id).limits;2372 let collection_mode = <Collection<T>>::get(collection_id).mode;2373 2374 // sponsor timeout2375 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2376 sponsor_transfer = match collection_mode {2377 CollectionMode::NFT => {2378 2379 // get correct limit2380 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2381 collection_limits.sponsor_transfer_timeout2382 } else {2383 ChainLimit::get().nft_sponsor_transfer_timeout2384 };2385 2386 let mut sponsored = true;2387 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2388 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2389 let limit_time = last_tx_block + limit.into();2390 if block_number <= limit_time {2391 sponsored = false;2392 }2393 }2394 if sponsored {2395 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2396 }23972398 sponsored2399 }2400 CollectionMode::Fungible(_) => {2401 2402 // get correct limit2403 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2404 collection_limits.sponsor_transfer_timeout2405 } else {2406 ChainLimit::get().fungible_sponsor_transfer_timeout2407 };2408 2409 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2410 let mut sponsored = true;2411 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2412 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2413 let limit_time = last_tx_block + limit.into();2414 if block_number <= limit_time {2415 sponsored = false;2416 }2417 }2418 if sponsored {2419 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2420 }24212422 sponsored2423 }2424 CollectionMode::ReFungible(_) => {2425 2426 // get correct limit2427 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2428 collection_limits.sponsor_transfer_timeout2429 } else {2430 ChainLimit::get().refungible_sponsor_transfer_timeout2431 };2432 2433 let mut sponsored = true;2434 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2435 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2436 let limit_time = last_tx_block + limit.into();2437 if block_number <= limit_time {2438 sponsored = false;2439 }2440 }2441 if sponsored {2442 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2443 }24442445 sponsored2446 }2447 _ => {2448 false2449 },2450 };2451 }24522453 if !sponsor_transfer {2454 T::AccountId::default()2455 } else {2456 <Collection<T>>::get(collection_id).sponsor2457 }2458 }24592460 _ => T::AccountId::default(),2461 };24622463 // Sponsor smart contracts2464 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24652466 // On instantiation: set the contract owner2467 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {24682469 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2470 &who,2471 code_hash,2472 salt,2473 );2474 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24752476 T::AccountId::default()2477 },24782479 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2480 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24812482 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24832484 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2485 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2486 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2487 2488 if !owned_contract && white_list_enabled {2489 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2490 return Err(InvalidTransaction::Call.into());2491 }2492 }24932494 let mut sponsor_transfer = false;2495 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2496 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2497 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2498 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2499 let limit_time = last_tx_block + rate_limit;25002501 if block_number >= limit_time {2502 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2503 sponsor_transfer = true;2504 }2505 } else {2506 sponsor_transfer = false;2507 }2508 2509 2510 let mut sp = T::AccountId::default();2511 if sponsor_transfer {2512 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2513 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2514 sp = called_contract;2515 }2516 }2517 }25182519 sp2520 },25212522 _ => sponsor,2523 };25242525 let mut who_pays_fee: T::AccountId = sponsor.clone();2526 if sponsor == T::AccountId::default() {2527 who_pays_fee = who.clone();2528 }25292530 <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2531 .map(|i| (fee, i))2532 }2533}253425352536impl<T: Config + Send + Sync> SignedExtension2537 for ChargeTransactionPayment<T>2538where2539 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2540 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2541 T::AccountId: AsRef<[u8]>,2542 T::AccountId: UncheckedFrom<T::Hash>,2543{2544 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2545 type AccountId = T::AccountId;2546 type Call = T::Call;2547 type AdditionalSigned = ();2548 type Pre = (2549 // tip2550 BalanceOf<T>,2551 // who pays fee2552 Self::AccountId,2553 // imbalance resulting from withdrawing the fee2554 <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2555 );2556 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2557 Ok(())2558 }25592560 fn validate(2561 &self,2562 who: &Self::AccountId,2563 call: &Self::Call,2564 info: &DispatchInfoOf<Self::Call>,2565 len: usize,2566 ) -> TransactionValidity {2567 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2568 Ok(ValidTransaction {2569 priority: Self::get_priority(len, info, fee),2570 ..Default::default()2571 })2572 }25732574 fn pre_dispatch(2575 self,2576 who: &Self::AccountId,2577 call: &Self::Call,2578 info: &DispatchInfoOf<Self::Call>,2579 len: usize,2580 ) -> Result<Self::Pre, TransactionValidityError> {2581 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2582 Ok((self.0, who.clone(), imbalance))2583 }25842585 fn post_dispatch(2586 pre: Self::Pre,2587 info: &DispatchInfoOf<Self::Call>,2588 post_info: &PostDispatchInfoOf<Self::Call>,2589 len: usize,2590 _result: &DispatchResult,2591 ) -> Result<(), TransactionValidityError> {2592 let (tip, who, imbalance) = pre;2593 let actual_fee = transaction_payment::Module::<T>::compute_actual_fee(2594 len as u32,2595 info,2596 post_info,2597 tip,2598 );2599 <T as transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2600 Ok(())2601 }2602}26032604// #endregion1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, IsSubType,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial, DispatchClass,29 },30 StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use transaction_payment::OnChargeTransaction;4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;5556pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;57pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;58pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;59pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6061// Structs62// #region6364pub type CollectionId = u32;65pub type TokenId = u32;66pub type DecimalPoints = u8;6768#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]69#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]70pub enum CollectionMode {71 Invalid,72 NFT,73 // decimal points74 Fungible(DecimalPoints),75 ReFungible,76}7778impl Default for CollectionMode {79 fn default() -> Self {80 Self::Invalid81 }82}8384impl Into<u8> for CollectionMode {85 fn into(self) -> u8 {86 match self {87 CollectionMode::Invalid => 0,88 CollectionMode::NFT => 1,89 CollectionMode::Fungible(_) => 2,90 CollectionMode::ReFungible => 3,91 }92 }93}9495#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]96#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]97pub enum AccessMode {98 Normal,99 WhiteList,100}101impl Default for AccessMode {102 fn default() -> Self {103 Self::Normal104 }105}106107#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub enum SchemaVersion {110 ImageURL,111 Unique,112}113impl Default for SchemaVersion {114 fn default() -> Self {115 Self::ImageURL116 }117}118119#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]120#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]121pub struct Ownership<AccountId> {122 pub owner: AccountId,123 pub fraction: u128,124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct CollectionType<AccountId> {129 pub owner: AccountId,130 pub mode: CollectionMode,131 pub access: AccessMode,132 pub decimal_points: DecimalPoints,133 pub name: Vec<u16>, // 64 include null escape char134 pub description: Vec<u16>, // 256 include null escape char135 pub token_prefix: Vec<u8>, // 16 include null escape char136 pub mint_mode: bool,137 pub offchain_schema: Vec<u8>,138 pub schema_version: SchemaVersion,139 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender140 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.141 pub limits: CollectionLimits, // Collection private restrictions 142 pub variable_on_chain_schema: Vec<u8>, //143 pub const_on_chain_schema: Vec<u8>, //144}145146#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]147#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]148pub struct NftItemType<AccountId> {149 pub owner: AccountId,150 pub const_data: Vec<u8>,151 pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType {157 pub value: u128,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct ReFungibleItemType<AccountId> {163 pub owner: Vec<Ownership<AccountId>>,164 pub const_data: Vec<u8>,165 pub variable_data: Vec<u8>,166}167168// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170// pub struct VestingItem<AccountId, Moment> {171// pub sender: AccountId,172// pub recipient: AccountId,173// pub collection_id: CollectionId,174// pub item_id: TokenId,175// pub amount: u64,176// pub vesting_date: Moment,177// }178179#[derive(Encode, Decode, Debug, Clone, PartialEq)]180#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]181pub struct CollectionLimits {182 pub account_token_ownership_limit: u32,183 pub sponsored_data_size: u32,184 pub token_limit: u32,185186 // Timeouts for item types in passed blocks187 pub sponsor_transfer_timeout: u32,188}189190impl Default for CollectionLimits {191 fn default() -> CollectionLimits {192 CollectionLimits { 193 account_token_ownership_limit: 10_000_000, 194 token_limit: u32::max_value(),195 sponsored_data_size: u32::max_value(), 196 sponsor_transfer_timeout: 14400 }197 }198}199200#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]201#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]202pub struct ChainLimits {203 pub collection_numbers_limit: u32,204 pub account_token_ownership_limit: u32,205 pub collections_admins_limit: u64,206 pub custom_data_limit: u32,207208 // Timeouts for item types in passed blocks209 pub nft_sponsor_transfer_timeout: u32,210 pub fungible_sponsor_transfer_timeout: u32,211 pub refungible_sponsor_transfer_timeout: u32,212213 // Schema limits214 pub offchain_schema_limit: u32,215 pub variable_on_chain_schema_limit: u32,216 pub const_on_chain_schema_limit: u32,217}218219pub trait WeightInfo {220 fn create_collection() -> Weight;221 fn destroy_collection() -> Weight;222 fn add_to_white_list() -> Weight;223 fn remove_from_white_list() -> Weight;224 fn set_public_access_mode() -> Weight;225 fn set_mint_permission() -> Weight;226 fn change_collection_owner() -> Weight;227 fn add_collection_admin() -> Weight;228 fn remove_collection_admin() -> Weight;229 fn set_collection_sponsor() -> Weight;230 fn confirm_sponsorship() -> Weight;231 fn remove_collection_sponsor() -> Weight;232 fn create_item(s: usize) -> Weight;233 fn burn_item() -> Weight;234 fn transfer() -> Weight;235 fn approve() -> Weight;236 fn transfer_from() -> Weight;237 fn set_offchain_schema() -> Weight;238 fn set_const_on_chain_schema() -> Weight;239 fn set_variable_on_chain_schema() -> Weight;240 fn set_variable_meta_data() -> Weight;241 fn enable_contract_sponsoring() -> Weight;242 fn set_schema_version() -> Weight;243 fn set_chain_limits() -> Weight;244 fn set_contract_sponsoring_rate_limit() -> Weight;245 fn toggle_contract_white_list() -> Weight;246 fn add_to_contract_white_list() -> Weight;247 fn remove_from_contract_white_list() -> Weight;248 fn set_collection_limits() -> Weight;249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateNftData {254 pub const_data: Vec<u8>,255 pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub struct CreateFungibleData {261 pub value: u128,262}263264#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]265#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]266pub struct CreateReFungibleData {267 pub const_data: Vec<u8>,268 pub variable_data: Vec<u8>,269 pub pieces: u128,270}271272#[derive(Encode, Decode, Debug, Clone, PartialEq)]273#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]274pub enum CreateItemData {275 NFT(CreateNftData),276 Fungible(CreateFungibleData),277 ReFungible(CreateReFungibleData),278}279280impl CreateItemData {281 pub fn len(&self) -> usize {282 let len = match self {283 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),284 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),285 _ => 0286 };287 288 return len;289 }290}291292impl From<CreateNftData> for CreateItemData {293 fn from(item: CreateNftData) -> Self {294 CreateItemData::NFT(item)295 }296}297298impl From<CreateReFungibleData> for CreateItemData {299 fn from(item: CreateReFungibleData) -> Self {300 CreateItemData::ReFungible(item)301 }302}303304impl From<CreateFungibleData> for CreateItemData {305 fn from(item: CreateFungibleData) -> Self {306 CreateItemData::Fungible(item)307 }308}309310311decl_error! {312 /// Error for non-fungible-token module.313 pub enum Error for Module<T: Config> {314 /// Total collections bound exceeded.315 TotalCollectionsLimitExceeded,316 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.317 CollectionDecimalPointLimitExceeded, 318 /// Collection name can not be longer than 63 char.319 CollectionNameLimitExceeded, 320 /// Collection description can not be longer than 255 char.321 CollectionDescriptionLimitExceeded, 322 /// Token prefix can not be longer than 15 char.323 CollectionTokenPrefixLimitExceeded,324 /// This collection does not exist.325 CollectionNotFound,326 /// Item not exists.327 TokenNotFound,328 /// Admin not found329 AdminNotFound,330 /// Arithmetic calculation overflow.331 NumOverflow, 332 /// Account already has admin role.333 AlreadyAdmin, 334 /// You do not own this collection.335 NoPermission,336 /// This address is not set as sponsor, use setCollectionSponsor first.337 ConfirmUnsetSponsorFail,338 /// Collection is not in mint mode.339 PublicMintingNotAllowed,340 /// Sender parameter and item owner must be equal.341 MustBeTokenOwner,342 /// Item balance not enough.343 TokenValueTooLow,344 /// Size of item is too large.345 NftSizeLimitExceeded,346 /// No approve found347 ApproveNotFound,348 /// Requested value more than approved.349 TokenValueNotEnough,350 /// Only approved addresses can call this method.351 ApproveRequired,352 /// Address is not in white list.353 AddresNotInWhiteList,354 /// Number of collection admins bound exceeded.355 CollectionAdminsLimitExceeded,356 /// Owned tokens by a single address bound exceeded.357 AddressOwnershipLimitExceeded,358 /// Length of items properties must be greater than 0.359 EmptyArgument,360 /// const_data exceeded data limit.361 TokenConstDataLimitExceeded,362 /// variable_data exceeded data limit.363 TokenVariableDataLimitExceeded,364 /// Not NFT item data used to mint in NFT collection.365 NotNftDataUsedToMintNftCollectionToken,366 /// Not Fungible item data used to mint in Fungible collection.367 NotFungibleDataUsedToMintFungibleCollectionToken,368 /// Not Re Fungible item data used to mint in Re Fungible collection.369 NotReFungibleDataUsedToMintReFungibleCollectionToken,370 /// Unexpected collection type.371 UnexpectedCollectionType,372 /// Can't store metadata in fungible tokens.373 CantStoreMetadataInFungibleTokens,374 /// Collection token limit exceeded375 CollectionTokenLimitExceeded,376 /// Account token limit exceeded per collection377 AccountTokenLimitExceeded,378 /// Collection limit bounds per collection exceeded379 CollectionLimitBoundsExceeded,380 /// Schema data size limit bound exceeded381 SchemaDataLimitExceeded,382 /// Maximum refungibility exceeded383 WrongRefungiblePieces384 }385}386387pub trait Config: system::Config + Sized + transaction_payment::Config + pallet_contracts::Config {388 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;389390 /// Weight information for extrinsics in this pallet.391 type WeightInfo: WeightInfo;392}393394#[cfg(feature = "runtime-benchmarks")]395mod benchmarking;396397// #endregion398399decl_storage! {400 trait Store for Module<T: Config> as Nft {401402 // Private members403 NextCollectionID: CollectionId;404 CreatedCollectionCount: u32;405 ChainVersion: u64;406 ItemListIndex: map hasher(identity) CollectionId => TokenId;407408 // Chain limits struct409 pub ChainLimit get(fn chain_limit) config(): ChainLimits;410411 // Bound counters412 CollectionCount: u32;413 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;414415 // Basic collections416 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;417 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;418 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;419420 /// Balance owner per collection map421 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;422423 /// second parameter: item id + owner account id + spender account id424 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;425426 /// Item collections427 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;428 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;429 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;430431 /// Index list432 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;433434 /// Tokens transfer baskets435 pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;436 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;437 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;438 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;439440 // Contract Sponsorship and Ownership441 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;442 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;443 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;444 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;445 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 446 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 447 }448 add_extra_genesis {449 build(|config: &GenesisConfig<T>| {450 // Modification of storage451 for (_num, _c) in &config.collection {452 <Module<T>>::init_collection(_c);453 }454455 for (_num, _c, _i) in &config.nft_item_id {456 <Module<T>>::init_nft_token(*_c, _i);457 }458459 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {460 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);461 }462463 for (_num, _c, _i) in &config.refungible_item_id {464 <Module<T>>::init_refungible_token(*_c, _i);465 }466 })467 }468}469470decl_event!(471 pub enum Event<T>472 where473 AccountId = <T as system::Config>::AccountId,474 {475 /// New collection was created476 /// 477 /// # Arguments478 /// 479 /// * collection_id: Globally unique identifier of newly created collection.480 /// 481 /// * mode: [CollectionMode] converted into u8.482 /// 483 /// * account_id: Collection owner.484 Created(CollectionId, u8, AccountId),485486 /// New item was created.487 /// 488 /// # Arguments489 /// 490 /// * collection_id: Id of the collection where item was created.491 /// 492 /// * item_id: Id of an item. Unique within the collection.493 ItemCreated(CollectionId, TokenId),494495 /// Collection item was burned.496 /// 497 /// # Arguments498 /// 499 /// collection_id.500 /// 501 /// item_id: Identifier of burned NFT.502 ItemDestroyed(CollectionId, TokenId),503 }504);505506decl_module! {507 pub struct Module<T: Config> for enum Call 508 where 509 origin: T::Origin510 {511 fn deposit_event() = default;512 type Error = Error<T>;513514 fn on_initialize(now: T::BlockNumber) -> Weight {515516 if ChainVersion::get() < 2517 {518 let value = NextCollectionID::get();519 CreatedCollectionCount::put(value);520 ChainVersion::put(2);521 }522523 0524 }525526 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.527 /// 528 /// # Permissions529 /// 530 /// * Anyone.531 /// 532 /// # Arguments533 /// 534 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.535 /// 536 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.537 /// 538 /// * token_prefix: UTF-8 string with token prefix.539 /// 540 /// * mode: [CollectionMode] collection type and type dependent data.541 // returns collection ID542 #[weight = <T as Config>::WeightInfo::create_collection()]543 pub fn create_collection(origin,544 collection_name: Vec<u16>,545 collection_description: Vec<u16>,546 token_prefix: Vec<u8>,547 mode: CollectionMode) -> DispatchResult {548549 // Anyone can create a collection550 let who = ensure_signed(origin)?;551552 let decimal_points = match mode {553 CollectionMode::Fungible(points) => points,554 _ => 0555 };556557 // bound Total number of collections558 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);559560 // check params561 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);562 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);563 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);564 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);565566 // Generate next collection ID567 let next_id = CreatedCollectionCount::get()568 .checked_add(1)569 .ok_or(Error::<T>::NumOverflow)?;570571 // bound counter572 let total = CollectionCount::get()573 .checked_add(1)574 .ok_or(Error::<T>::NumOverflow)?;575576 CreatedCollectionCount::put(next_id);577 CollectionCount::put(total);578579 // Create new collection580 let new_collection = CollectionType {581 owner: who.clone(),582 name: collection_name,583 mode: mode.clone(),584 mint_mode: false,585 access: AccessMode::Normal,586 description: collection_description,587 decimal_points: decimal_points,588 token_prefix: token_prefix,589 offchain_schema: Vec::new(),590 schema_version: SchemaVersion::ImageURL,591 sponsor: T::AccountId::default(),592 sponsor_confirmed: false,593 variable_on_chain_schema: Vec::new(),594 const_on_chain_schema: Vec::new(),595 limits: CollectionLimits::default(),596 };597598 // Add new collection to map599 <Collection<T>>::insert(next_id, new_collection);600601 // call event602 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));603604 Ok(())605 }606607 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.608 /// 609 /// # Permissions610 /// 611 /// * Collection Owner.612 /// 613 /// # Arguments614 /// 615 /// * collection_id: collection to destroy.616 #[weight = <T as Config>::WeightInfo::destroy_collection()]617 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {618619 let sender = ensure_signed(origin)?;620 Self::check_owner_permissions(collection_id, sender)?;621622 <AddressTokens<T>>::remove_prefix(collection_id);623 <Allowances<T>>::remove_prefix(collection_id);624 <Balance<T>>::remove_prefix(collection_id);625 <ItemListIndex>::remove(collection_id);626 <AdminList<T>>::remove(collection_id);627 <Collection<T>>::remove(collection_id);628 <WhiteList<T>>::remove_prefix(collection_id);629630 <NftItemList<T>>::remove_prefix(collection_id);631 <FungibleItemList<T>>::remove_prefix(collection_id);632 <ReFungibleItemList<T>>::remove_prefix(collection_id);633634 <NftTransferBasket<T>>::remove_prefix(collection_id);635 <FungibleTransferBasket<T>>::remove_prefix(collection_id);636 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);637638 if CollectionCount::get() > 0639 {640 // bound couter641 let total = CollectionCount::get()642 .checked_sub(1)643 .ok_or(Error::<T>::NumOverflow)?;644645 CollectionCount::put(total);646 }647648 Ok(())649 }650651 /// Add an address to white list.652 /// 653 /// # Permissions654 /// 655 /// * Collection Owner656 /// * Collection Admin657 /// 658 /// # Arguments659 /// 660 /// * collection_id.661 /// 662 /// * address.663 #[weight = <T as Config>::WeightInfo::add_to_white_list()]664 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{665666 let sender = ensure_signed(origin)?;667 Self::check_owner_or_admin_permissions(collection_id, sender)?;668669 <WhiteList<T>>::insert(collection_id, address, true);670 671 Ok(())672 }673674 /// Remove an address from white list.675 /// 676 /// # Permissions677 /// 678 /// * Collection Owner679 /// * Collection Admin680 /// 681 /// # Arguments682 /// 683 /// * collection_id.684 /// 685 /// * address.686 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]687 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{688689 let sender = ensure_signed(origin)?;690 Self::check_owner_or_admin_permissions(collection_id, sender)?;691692 <WhiteList<T>>::remove(collection_id, address);693694 Ok(())695 }696697 /// Toggle between normal and white list access for the methods with access for `Anyone`.698 /// 699 /// # Permissions700 /// 701 /// * Collection Owner.702 /// 703 /// # Arguments704 /// 705 /// * collection_id.706 /// 707 /// * mode: [AccessMode]708 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]709 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult710 {711 let sender = ensure_signed(origin)?;712713 Self::check_owner_permissions(collection_id, sender)?;714 let mut target_collection = <Collection<T>>::get(collection_id);715 target_collection.access = mode;716 <Collection<T>>::insert(collection_id, target_collection);717718 Ok(())719 }720721 /// Allows Anyone to create tokens if:722 /// * White List is enabled, and723 /// * Address is added to white list, and724 /// * This method was called with True parameter725 /// 726 /// # Permissions727 /// * Collection Owner728 ///729 /// # Arguments730 /// 731 /// * collection_id.732 /// 733 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.734 #[weight = <T as Config>::WeightInfo::set_mint_permission()]735 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult736 {737 let sender = ensure_signed(origin)?;738739 Self::check_owner_permissions(collection_id, sender)?;740 let mut target_collection = <Collection<T>>::get(collection_id);741 target_collection.mint_mode = mint_permission;742 <Collection<T>>::insert(collection_id, target_collection);743744 Ok(())745 }746747 /// Change the owner of the collection.748 /// 749 /// # Permissions750 /// 751 /// * Collection Owner.752 /// 753 /// # Arguments754 /// 755 /// * collection_id.756 /// 757 /// * new_owner.758 #[weight = <T as Config>::WeightInfo::change_collection_owner()]759 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {760761 let sender = ensure_signed(origin)?;762 Self::check_owner_permissions(collection_id, sender)?;763 let mut target_collection = <Collection<T>>::get(collection_id);764 target_collection.owner = new_owner;765 <Collection<T>>::insert(collection_id, target_collection);766767 Ok(())768 }769770 /// Adds an admin of the Collection.771 /// 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. 772 /// 773 /// # Permissions774 /// 775 /// * Collection Owner.776 /// * Collection Admin.777 /// 778 /// # Arguments779 /// 780 /// * collection_id: ID of the Collection to add admin for.781 /// 782 /// * new_admin_id: Address of new admin to add.783 #[weight = <T as Config>::WeightInfo::add_collection_admin()]784 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {785786 let sender = ensure_signed(origin)?;787 Self::check_owner_or_admin_permissions(collection_id, sender)?;788 let mut admin_arr: Vec<T::AccountId> = Vec::new();789790 if <AdminList<T>>::contains_key(collection_id)791 {792 admin_arr = <AdminList<T>>::get(collection_id);793 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);794 }795796 // Number of collection admins797 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);798799 admin_arr.push(new_admin_id);800 <AdminList<T>>::insert(collection_id, admin_arr);801802 Ok(())803 }804805 /// 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.806 ///807 /// # Permissions808 /// 809 /// * Collection Owner.810 /// * Collection Admin.811 /// 812 /// # Arguments813 /// 814 /// * collection_id: ID of the Collection to remove admin for.815 /// 816 /// * account_id: Address of admin to remove.817 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]818 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {819820 let sender = ensure_signed(origin)?;821 Self::check_owner_or_admin_permissions(collection_id, sender)?;822 ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);823824 let mut admin_arr = <AdminList<T>>::get(collection_id);825 admin_arr.retain(|i| *i != account_id);826 <AdminList<T>>::insert(collection_id, admin_arr);827828 Ok(())829 }830831 /// # Permissions832 /// 833 /// * Collection Owner834 /// 835 /// # Arguments836 /// 837 /// * collection_id.838 /// 839 /// * new_sponsor.840 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]841 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {842843 let sender = ensure_signed(origin)?;844 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);845846 let mut target_collection = <Collection<T>>::get(collection_id);847 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);848849 target_collection.sponsor = new_sponsor;850 target_collection.sponsor_confirmed = false;851 <Collection<T>>::insert(collection_id, target_collection);852853 Ok(())854 }855856 /// # Permissions857 /// 858 /// * Sponsor.859 /// 860 /// # Arguments861 /// 862 /// * collection_id.863 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]864 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {865866 let sender = ensure_signed(origin)?;867 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);868869 let mut target_collection = <Collection<T>>::get(collection_id);870 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);871872 target_collection.sponsor_confirmed = true;873 <Collection<T>>::insert(collection_id, target_collection);874875 Ok(())876 }877878 /// Switch back to pay-per-own-transaction model.879 ///880 /// # Permissions881 ///882 /// * Collection owner.883 /// 884 /// # Arguments885 /// 886 /// * collection_id.887 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]888 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {889890 let sender = ensure_signed(origin)?;891 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);892893 let mut target_collection = <Collection<T>>::get(collection_id);894 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);895896 target_collection.sponsor = T::AccountId::default();897 target_collection.sponsor_confirmed = false;898 <Collection<T>>::insert(collection_id, target_collection);899900 Ok(())901 }902903 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.904 /// 905 /// # Permissions906 /// 907 /// * Collection Owner.908 /// * Collection Admin.909 /// * Anyone if910 /// * White List is enabled, and911 /// * Address is added to white list, and912 /// * MintPermission is enabled (see SetMintPermission method)913 /// 914 /// # Arguments915 /// 916 /// * collection_id: ID of the collection.917 /// 918 /// * owner: Address, initial owner of the NFT.919 ///920 /// * data: Token data to store on chain.921 // #[weight =922 // (130_000_000 as Weight)923 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))924 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))925 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]926927 #[weight = <T as Config>::WeightInfo::create_item(data.len())]928 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {929930 let sender = ensure_signed(origin)?;931932 Self::collection_exists(collection_id)?;933934 let target_collection = <Collection<T>>::get(collection_id);935936 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;937 Self::validate_create_item_args(&target_collection, &data)?;938 Self::create_item_no_validation(collection_id, owner, data)?;939940 Ok(())941 }942943 /// This method creates multiple instances of NFT Collection created with CreateCollection method.944 /// 945 /// # Permissions946 /// 947 /// * Collection Owner.948 /// * Collection Admin.949 /// * Anyone if950 /// * White List is enabled, and951 /// * Address is added to white list, and952 /// * MintPermission is enabled (see SetMintPermission method)953 /// 954 /// # Arguments955 /// 956 /// * collection_id: ID of the collection.957 /// 958 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].959 /// 960 /// * owner: Address, initial owner of the NFT.961 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()962 .map(|data| { data.len() })963 .sum())]964 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {965966 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);967 let sender = ensure_signed(origin)?;968969 Self::collection_exists(collection_id)?;970 let target_collection = <Collection<T>>::get(collection_id);971972 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;973974 for data in &items_data {975 Self::validate_create_item_args(&target_collection, data)?;976 }977 for data in &items_data {978 Self::create_item_no_validation(collection_id, owner.clone(), data.clone())?;979 }980981 Ok(())982 }983984 /// Destroys a concrete instance of NFT.985 /// 986 /// # Permissions987 /// 988 /// * Collection Owner.989 /// * Collection Admin.990 /// * Current NFT Owner.991 /// 992 /// # Arguments993 /// 994 /// * collection_id: ID of the collection.995 /// 996 /// * item_id: ID of NFT to burn.997 #[weight = <T as Config>::WeightInfo::burn_item()]998 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {9991000 let sender = ensure_signed(origin)?;1001 Self::collection_exists(collection_id)?;10021003 // Transfer permissions check1004 let target_collection = <Collection<T>>::get(collection_id);1005 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1006 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1007 Error::<T>::NoPermission);10081009 if target_collection.access == AccessMode::WhiteList {1010 Self::check_white_list(collection_id, &sender)?;1011 }10121013 match target_collection.mode1014 {1015 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1016 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1017 CollectionMode::ReFungible => Self::burn_refungible_item(collection_id, item_id, &sender)?,1018 _ => ()1019 };10201021 // call event1022 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10231024 Ok(())1025 }10261027 /// Change ownership of the token.1028 /// 1029 /// # Permissions1030 /// 1031 /// * Collection Owner1032 /// * Collection Admin1033 /// * Current NFT owner1034 ///1035 /// # Arguments1036 /// 1037 /// * recipient: Address of token recipient.1038 /// 1039 /// * collection_id.1040 /// 1041 /// * item_id: ID of the item1042 /// * Non-Fungible Mode: Required.1043 /// * Fungible Mode: Ignored.1044 /// * Re-Fungible Mode: Required.1045 /// 1046 /// * value: Amount to transfer.1047 /// * Non-Fungible Mode: Ignored1048 /// * Fungible Mode: Must specify transferred amount1049 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1050 #[weight = <T as Config>::WeightInfo::transfer()]1051 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1052 let sender = ensure_signed(origin)?;1053 Self::transfer_internal(sender, recipient, collection_id, item_id, value)1054 }10551056 /// Set, change, or remove approved address to transfer the ownership of the NFT.1057 /// 1058 /// # Permissions1059 /// 1060 /// * Collection Owner1061 /// * Collection Admin1062 /// * Current NFT owner1063 /// 1064 /// # Arguments1065 /// 1066 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1067 /// 1068 /// * collection_id.1069 /// 1070 /// * item_id: ID of the item.1071 #[weight = <T as Config>::WeightInfo::approve()]1072 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10731074 let sender = ensure_signed(origin)?;10751076 Self::collection_exists(collection_id)?;1077 Self::token_exists(collection_id, item_id, &sender)?;10781079 // Transfer permissions check1080 let target_collection = <Collection<T>>::get(collection_id);1081 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1082 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1083 Error::<T>::NoPermission);10841085 if target_collection.access == AccessMode::WhiteList {1086 Self::check_white_list(collection_id, &sender)?;1087 Self::check_white_list(collection_id, &spender)?;1088 }10891090 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1091 let mut allowance: u128 = amount;1092 if allowance_exists {1093 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1094 }1095 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);10961097 Ok(())1098 }1099 1100 /// 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.1101 /// 1102 /// # Permissions1103 /// * Collection Owner1104 /// * Collection Admin1105 /// * Current NFT owner1106 /// * Address approved by current NFT owner1107 /// 1108 /// # Arguments1109 /// 1110 /// * from: Address that owns token.1111 /// 1112 /// * recipient: Address of token recipient.1113 /// 1114 /// * collection_id.1115 /// 1116 /// * item_id: ID of the item.1117 /// 1118 /// * value: Amount to transfer.1119 #[weight = <T as Config>::WeightInfo::transfer_from()]1120 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11211122 let sender = ensure_signed(origin)?;1123 let mut appoved_transfer = false;11241125 // Check approval1126 let mut approval: u128 = 0;1127 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {1128 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));1129 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1130 appoved_transfer = true;1131 }11321133 let target_collection = <Collection<T>>::get(collection_id);11341135 // Limits check1136 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11371138 // Transfer permissions check 1139 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1140 Error::<T>::NoPermission);11411142 if target_collection.access == AccessMode::WhiteList {1143 Self::check_white_list(collection_id, &sender)?;1144 Self::check_white_list(collection_id, &recipient)?;1145 }11461147 // Reduce approval by transferred amount or remove if remaining approval drops to 01148 if approval.checked_sub(value).unwrap_or(0) > 0 {1149 <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1150 }1151 else {1152 <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1153 }11541155 match target_collection.mode1156 {1157 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1158 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1159 CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1160 _ => ()1161 };11621163 Ok(())1164 }11651166 // #[weight = 0]1167 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11681169 // // let no_perm_mes = "You do not have permissions to modify this collection";1170 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1171 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1172 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11731174 // // // on_nft_received call11751176 // // Self::transfer(origin, collection_id, item_id, new_owner)?;11771178 // Ok(())1179 // }11801181 /// Set off-chain data schema.1182 /// 1183 /// # Permissions1184 /// 1185 /// * Collection Owner1186 /// * Collection Admin1187 /// 1188 /// # Arguments1189 /// 1190 /// * collection_id.1191 /// 1192 /// * schema: String representing the offchain data schema.1193 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1194 pub fn set_variable_meta_data (1195 origin,1196 collection_id: CollectionId,1197 item_id: TokenId,1198 data: Vec<u8>1199 ) -> DispatchResult {1200 let sender = ensure_signed(origin)?;1201 1202 Self::collection_exists(collection_id)?;1203 Self::token_exists(collection_id, item_id, &sender)?;12041205 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12061207 // Modify permissions check1208 let target_collection = <Collection<T>>::get(collection_id);1209 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1210 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1211 Error::<T>::NoPermission);12121213 match target_collection.mode1214 {1215 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1216 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1217 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1218 _ => fail!(Error::<T>::UnexpectedCollectionType)1219 };12201221 Ok(())1222 }1223 1224 /// Set schema standard1225 /// ImageURL1226 /// Unique1227 /// 1228 /// # Permissions1229 /// 1230 /// * Collection Owner1231 /// * Collection Admin1232 /// 1233 /// # Arguments1234 /// 1235 /// * collection_id.1236 /// 1237 /// * schema: SchemaVersion: enum1238 #[weight = <T as Config>::WeightInfo::set_schema_version()]1239 pub fn set_schema_version(1240 origin,1241 collection_id: CollectionId,1242 version: SchemaVersion1243 ) -> DispatchResult {1244 let sender = ensure_signed(origin)?;1245 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1246 let mut target_collection = <Collection<T>>::get(collection_id);1247 target_collection.schema_version = version;1248 <Collection<T>>::insert(collection_id, target_collection);12491250 Ok(())1251 }12521253 /// Set off-chain data schema.1254 /// 1255 /// # Permissions1256 /// 1257 /// * Collection Owner1258 /// * Collection Admin1259 /// 1260 /// # Arguments1261 /// 1262 /// * collection_id.1263 /// 1264 /// * schema: String representing the offchain data schema.1265 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1266 pub fn set_offchain_schema(1267 origin,1268 collection_id: CollectionId,1269 schema: Vec<u8>1270 ) -> DispatchResult {1271 let sender = ensure_signed(origin)?;1272 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12731274 // check schema limit1275 ensure!(schema.len() as u32 > ChainLimit::get().offchain_schema_limit, "");12761277 let mut target_collection = <Collection<T>>::get(collection_id);1278 target_collection.offchain_schema = schema;1279 <Collection<T>>::insert(collection_id, target_collection);12801281 Ok(())1282 }12831284 /// Set const on-chain data schema.1285 /// 1286 /// # Permissions1287 /// 1288 /// * Collection Owner1289 /// * Collection Admin1290 /// 1291 /// # Arguments1292 /// 1293 /// * collection_id.1294 /// 1295 /// * schema: String representing the const on-chain data schema.1296 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1297 pub fn set_const_on_chain_schema (1298 origin,1299 collection_id: CollectionId,1300 schema: Vec<u8>1301 ) -> DispatchResult {1302 let sender = ensure_signed(origin)?;1303 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13041305 // check schema limit1306 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");13071308 let mut target_collection = <Collection<T>>::get(collection_id);1309 target_collection.const_on_chain_schema = schema;1310 <Collection<T>>::insert(collection_id, target_collection);13111312 Ok(())1313 }13141315 /// Set variable on-chain data schema.1316 /// 1317 /// # Permissions1318 /// 1319 /// * Collection Owner1320 /// * Collection Admin1321 /// 1322 /// # Arguments1323 /// 1324 /// * collection_id.1325 /// 1326 /// * schema: String representing the variable on-chain data schema.1327 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1328 pub fn set_variable_on_chain_schema (1329 origin,1330 collection_id: CollectionId,1331 schema: Vec<u8>1332 ) -> DispatchResult {1333 let sender = ensure_signed(origin)?;1334 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13351336 // check schema limit1337 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");13381339 let mut target_collection = <Collection<T>>::get(collection_id);1340 target_collection.variable_on_chain_schema = schema;1341 <Collection<T>>::insert(collection_id, target_collection);13421343 Ok(())1344 }13451346 // Sudo permissions function1347 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1348 pub fn set_chain_limits(1349 origin,1350 limits: ChainLimits1351 ) -> DispatchResult {13521353 #[cfg(not(feature = "runtime-benchmarks"))]1354 ensure_root(origin)?;13551356 <ChainLimit>::put(limits);1357 Ok(())1358 }13591360 /// Enable smart contract self-sponsoring.1361 /// 1362 /// # Permissions1363 /// 1364 /// * Contract Owner1365 /// 1366 /// # Arguments1367 /// 1368 /// * contract address1369 /// * enable flag1370 /// 1371 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1372 pub fn enable_contract_sponsoring(1373 origin,1374 contract_address: T::AccountId,1375 enable: bool1376 ) -> DispatchResult {13771378 let sender = ensure_signed(origin)?;13791380 #[cfg(feature = "runtime-benchmarks")]1381 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13821383 Self::ensure_contract_owned(sender, &contract_address)?;13841385 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1386 Ok(())1387 }13881389 /// Set the rate limit for contract sponsoring to specified number of blocks.1390 /// 1391 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1392 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1393 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1394 /// from contract endowment if there are at least B blocks between such transactions. 1395 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1396 /// 1397 /// # Permissions1398 /// 1399 /// * Contract Owner1400 /// 1401 /// # Arguments1402 /// 1403 /// -`contract_address`: Address of the contract to sponsor1404 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1405 /// 1406 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1407 pub fn set_contract_sponsoring_rate_limit(1408 origin,1409 contract_address: T::AccountId,1410 rate_limit: T::BlockNumber1411 ) -> DispatchResult {1412 let sender = ensure_signed(origin)?;14131414 #[cfg(feature = "runtime-benchmarks")]1415 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14161417 Self::ensure_contract_owned(sender, &contract_address)?;1418 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1419 Ok(())1420 }14211422 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1423 /// 1424 /// # Permissions1425 /// 1426 /// * Address that deployed smart contract.1427 /// 1428 /// # Arguments1429 /// 1430 /// -`contract_address`: Address of the contract.1431 /// 1432 /// - `enable`: . 1433 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1434 pub fn toggle_contract_white_list(1435 origin,1436 contract_address: T::AccountId,1437 enable: bool1438 ) -> DispatchResult {1439 let sender = ensure_signed(origin)?;14401441 #[cfg(feature = "runtime-benchmarks")]1442 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14431444 Self::ensure_contract_owned(sender, &contract_address)?;1445 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1446 Ok(())1447 }1448 1449 /// Add an address to smart contract white list.1450 /// 1451 /// # Permissions1452 /// 1453 /// * Address that deployed smart contract.1454 /// 1455 /// # Arguments1456 /// 1457 /// -`contract_address`: Address of the contract.1458 ///1459 /// -`account_address`: Address to add.1460 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1461 pub fn add_to_contract_white_list(1462 origin,1463 contract_address: T::AccountId,1464 account_address: T::AccountId1465 ) -> DispatchResult {1466 let sender = ensure_signed(origin)?;14671468 #[cfg(feature = "runtime-benchmarks")]1469 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1470 1471 Self::ensure_contract_owned(sender, &contract_address)?; 1472 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1473 Ok(())1474 }14751476 /// Remove an address from smart contract white list.1477 /// 1478 /// # Permissions1479 /// 1480 /// * Address that deployed smart contract.1481 /// 1482 /// # Arguments1483 /// 1484 /// -`contract_address`: Address of the contract.1485 ///1486 /// -`account_address`: Address to remove.1487 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1488 pub fn remove_from_contract_white_list(1489 origin,1490 contract_address: T::AccountId,1491 account_address: T::AccountId1492 ) -> DispatchResult {1493 let sender = ensure_signed(origin)?;14941495 #[cfg(feature = "runtime-benchmarks")]1496 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14971498 Self::ensure_contract_owned(sender, &contract_address)?;1499 <ContractWhiteList<T>>::remove(contract_address, account_address);1500 Ok(())1501 }15021503 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1504 pub fn set_collection_limits(1505 origin,1506 collection_id: u32,1507 limits: CollectionLimits,1508 ) -> DispatchResult {1509 let sender = ensure_signed(origin)?;1510 Self::check_owner_permissions(collection_id, sender.clone())?;1511 let mut target_collection = <Collection<T>>::get(collection_id);1512 let chain_limits = ChainLimit::get();1513 let climits = target_collection.limits;15141515 // collection bounds1516 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1517 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1518 Error::<T>::CollectionLimitBoundsExceeded);15191520 // token_limit check prev1521 ensure!(climits.token_limit > limits.token_limit && 1522 limits.token_limit <= chain_limits.account_token_ownership_limit, 1523 Error::<T>::AccountTokenLimitExceeded);15241525 target_collection.limits = limits;1526 <Collection<T>>::insert(collection_id, target_collection);15271528 Ok(())1529 } 1530 }1531}15321533impl<T: Config> Module<T> {15341535 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {15361537 let target_collection = <Collection<T>>::get(collection_id);15381539 // Limits check1540 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;15411542 // Transfer permissions check1543 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1544 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1545 Error::<T>::NoPermission);15461547 if target_collection.access == AccessMode::WhiteList {1548 Self::check_white_list(collection_id, &sender)?;1549 Self::check_white_list(collection_id, &recipient)?;1550 }15511552 match target_collection.mode1553 {1554 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1555 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1556 CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1557 _ => ()1558 };15591560 Ok(())1561 }156215631564 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15651566 // check token limit and account token limit1567 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1568 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1569 1570 Ok(())1571 }15721573 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15741575 // check token limit and account token limit1576 let total_items: u32 = ItemListIndex::get(collection_id);1577 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1578 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1579 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15801581 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1582 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1583 Self::check_white_list(collection_id, owner)?;1584 Self::check_white_list(collection_id, sender)?;1585 }15861587 Ok(())1588 }15891590 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1591 match target_collection.mode1592 {1593 CollectionMode::NFT => {1594 if let CreateItemData::NFT(data) = data {1595 // check sizes1596 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1597 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1598 } else {1599 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1600 }1601 },1602 CollectionMode::Fungible(_) => {1603 if let CreateItemData::Fungible(_) = data {1604 } else {1605 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1606 }1607 },1608 CollectionMode::ReFungible => {1609 if let CreateItemData::ReFungible(data) = data {16101611 // check sizes1612 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1613 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);16141615 // Check refungibility limits1616 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1617 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1618 } else {1619 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1620 }1621 },1622 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1623 };16241625 Ok(())1626 }16271628 fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1629 match data1630 {1631 CreateItemData::NFT(data) => {1632 let item = NftItemType {1633 owner,1634 const_data: data.const_data,1635 variable_data: data.variable_data1636 };16371638 Self::add_nft_item(collection_id, item)?;1639 },1640 CreateItemData::Fungible(data) => {1641 Self::add_fungible_item(collection_id, &owner, data.value)?;1642 },1643 CreateItemData::ReFungible(data) => {1644 let mut owner_list = Vec::new();1645 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16461647 let item = ReFungibleItemType {1648 owner: owner_list,1649 const_data: data.const_data,1650 variable_data: data.variable_data1651 };16521653 Self::add_refungible_item(collection_id, item)?;1654 }1655 };16561657 // call event1658 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16591660 Ok(())1661 }16621663 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16641665 // Does new owner already have an account?1666 let mut balance: u128 = 0;1667 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1668 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1669 } 16701671 // Mint 1672 let item = FungibleItemType {1673 value: balance + value1674 };1675 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16761677 // Update balance1678 let new_balance = <Balance<T>>::get(collection_id, owner)1679 .checked_add(value)1680 .ok_or(Error::<T>::NumOverflow)?;1681 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16821683 Ok(())1684 }16851686 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1687 let current_index = <ItemListIndex>::get(collection_id)1688 .checked_add(1)1689 .ok_or(Error::<T>::NumOverflow)?;1690 let itemcopy = item.clone();16911692 let value = item.owner.first().unwrap().fraction;1693 let owner = item.owner.first().unwrap().owner.clone();16941695 Self::add_token_index(collection_id, current_index, &owner)?;16961697 <ItemListIndex>::insert(collection_id, current_index);1698 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16991700 // Update balance1701 let new_balance = <Balance<T>>::get(collection_id, &owner)1702 .checked_add(value)1703 .ok_or(Error::<T>::NumOverflow)?;1704 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);17051706 Ok(())1707 }17081709 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1710 let current_index = <ItemListIndex>::get(collection_id)1711 .checked_add(1)1712 .ok_or(Error::<T>::NumOverflow)?;17131714 let item_owner = item.owner.clone();1715 Self::add_token_index(collection_id, current_index, &item.owner)?;17161717 <ItemListIndex>::insert(collection_id, current_index);1718 <NftItemList<T>>::insert(collection_id, current_index, item);17191720 // Update balance1721 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1722 .checked_add(1)1723 .ok_or(Error::<T>::NumOverflow)?;1724 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17251726 Ok(())1727 }17281729 fn burn_refungible_item(1730 collection_id: CollectionId,1731 item_id: TokenId,1732 owner: &T::AccountId,1733 ) -> DispatchResult {1734 ensure!(1735 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1736 Error::<T>::TokenNotFound1737 );1738 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1739 let rft_balance = token1740 .owner1741 .iter()1742 .filter(|&i| i.owner == *owner)1743 .next()1744 .unwrap();1745 Self::remove_token_index(collection_id, item_id, owner)?;17461747 // update balance1748 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1749 .checked_sub(rft_balance.fraction)1750 .ok_or(Error::<T>::NumOverflow)?;1751 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17521753 // Re-create owners list with sender removed1754 let index = token1755 .owner1756 .iter()1757 .position(|i| i.owner == *owner)1758 .unwrap();1759 token.owner.remove(index);1760 let owner_count = token.owner.len();17611762 // Burn the token completely if this was the last (only) owner1763 if owner_count == 0 {1764 <ReFungibleItemList<T>>::remove(collection_id, item_id);1765 }1766 else {1767 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1768 }17691770 Ok(())1771 }17721773 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1774 ensure!(1775 <NftItemList<T>>::contains_key(collection_id, item_id),1776 Error::<T>::TokenNotFound1777 );1778 let item = <NftItemList<T>>::get(collection_id, item_id);1779 Self::remove_token_index(collection_id, item_id, &item.owner)?;17801781 // update balance1782 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1783 .checked_sub(1)1784 .ok_or(Error::<T>::NumOverflow)?;1785 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1786 <NftItemList<T>>::remove(collection_id, item_id);17871788 Ok(())1789 }17901791 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1792 ensure!(1793 <FungibleItemList<T>>::contains_key(collection_id, owner),1794 Error::<T>::TokenNotFound1795 );1796 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1797 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17981799 // update balance1800 let new_balance = <Balance<T>>::get(collection_id, owner)1801 .checked_sub(value)1802 .ok_or(Error::<T>::NumOverflow)?;1803 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18041805 if balance.value - value > 0 {1806 balance.value -= value;1807 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1808 }1809 else {1810 <FungibleItemList<T>>::remove(collection_id, owner);1811 }18121813 Ok(())1814 }18151816 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1817 ensure!(1818 <Collection<T>>::contains_key(collection_id),1819 Error::<T>::CollectionNotFound1820 );1821 Ok(())1822 }18231824 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1825 Self::collection_exists(collection_id)?;18261827 let target_collection = <Collection<T>>::get(collection_id);1828 ensure!(1829 subject == target_collection.owner,1830 Error::<T>::NoPermission1831 );18321833 Ok(())1834 }18351836 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1837 let target_collection = <Collection<T>>::get(collection_id);1838 let mut result: bool = subject == target_collection.owner;1839 let exists = <AdminList<T>>::contains_key(collection_id);18401841 if !result & exists {1842 if <AdminList<T>>::get(collection_id).contains(&subject) {1843 result = true1844 }1845 }18461847 result1848 }18491850 fn check_owner_or_admin_permissions(1851 collection_id: CollectionId,1852 subject: T::AccountId,1853 ) -> DispatchResult {1854 Self::collection_exists(collection_id)?;1855 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18561857 ensure!(1858 result,1859 Error::<T>::NoPermission1860 );1861 Ok(())1862 }18631864 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1865 let target_collection = <Collection<T>>::get(collection_id);18661867 match target_collection.mode {1868 CollectionMode::NFT => {1869 <NftItemList<T>>::get(collection_id, item_id).owner == subject1870 }1871 CollectionMode::Fungible(_) => {1872 <FungibleItemList<T>>::contains_key(collection_id, &subject)1873 }1874 CollectionMode::ReFungible => {1875 <ReFungibleItemList<T>>::get(collection_id, item_id)1876 .owner1877 .iter()1878 .any(|i| i.owner == subject)1879 }1880 CollectionMode::Invalid => false,1881 }1882 }18831884 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1885 let mes = Error::<T>::AddresNotInWhiteList;1886 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18871888 Ok(())1889 }18901891 /// Check if token exists. In case of Fungible, check if there is an entry for 1892 /// the owner in fungible balances double map1893 fn token_exists(1894 collection_id: CollectionId,1895 item_id: TokenId,1896 owner: &T::AccountId1897 ) -> DispatchResult {1898 let target_collection = <Collection<T>>::get(collection_id);1899 let exists = match target_collection.mode1900 {1901 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1902 CollectionMode::Fungible(_) => <FungibleItemList<T>>::contains_key(collection_id, owner),1903 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1904 _ => false1905 };19061907 ensure!(exists == true, Error::<T>::TokenNotFound);1908 Ok(())1909 }19101911 fn transfer_fungible(1912 collection_id: CollectionId,1913 value: u128,1914 owner: &T::AccountId,1915 recipient: &T::AccountId,1916 ) -> DispatchResult {1917 Self::token_exists(collection_id, 0, owner)?;19181919 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1920 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19211922 // Send balance to recipient (updates balanceOf of recipient)1923 Self::add_fungible_item(collection_id, recipient, value)?;19241925 // update balanceOf of sender1926 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19271928 // Reduce or remove sender1929 if balance.value == value {1930 <FungibleItemList<T>>::remove(collection_id, owner);1931 }1932 else {1933 balance.value -= value;1934 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1935 }19361937 Ok(())1938 }19391940 fn transfer_refungible(1941 collection_id: CollectionId,1942 item_id: TokenId,1943 value: u128,1944 owner: T::AccountId,1945 new_owner: T::AccountId,1946 ) -> DispatchResult {1947 Self::token_exists(collection_id, item_id, &owner)?;19481949 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1950 let item = full_item1951 .owner1952 .iter()1953 .filter(|i| i.owner == owner)1954 .next()1955 .ok_or(Error::<T>::NumOverflow)?;1956 let amount = item.fraction;19571958 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19591960 // update balance1961 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1962 .checked_sub(value)1963 .ok_or(Error::<T>::NumOverflow)?;1964 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19651966 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1967 .checked_add(value)1968 .ok_or(Error::<T>::NumOverflow)?;1969 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19701971 let old_owner = item.owner.clone();1972 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19731974 // transfer1975 if amount == value && !new_owner_has_account {1976 // change owner1977 // new owner do not have account1978 let mut new_full_item = full_item.clone();1979 new_full_item1980 .owner1981 .iter_mut()1982 .find(|i| i.owner == owner)1983 .unwrap()1984 .owner = new_owner.clone();1985 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19861987 // update index collection1988 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1989 } else {1990 let mut new_full_item = full_item.clone();1991 new_full_item1992 .owner1993 .iter_mut()1994 .find(|i| i.owner == owner)1995 .unwrap()1996 .fraction -= value;19971998 // separate amount1999 if new_owner_has_account {2000 // new owner has account2001 new_full_item2002 .owner2003 .iter_mut()2004 .find(|i| i.owner == new_owner)2005 .unwrap()2006 .fraction += value;2007 } else {2008 // new owner do not have account2009 new_full_item.owner.push(Ownership {2010 owner: new_owner.clone(),2011 fraction: value,2012 });2013 Self::add_token_index(collection_id, item_id, &new_owner)?;2014 }20152016 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2017 }20182019 Ok(())2020 }20212022 fn transfer_nft(2023 collection_id: CollectionId,2024 item_id: TokenId,2025 sender: T::AccountId,2026 new_owner: T::AccountId,2027 ) -> DispatchResult {2028 Self::token_exists(collection_id, item_id, &sender)?;20292030 let mut item = <NftItemList<T>>::get(collection_id, item_id);20312032 ensure!(2033 sender == item.owner,2034 Error::<T>::MustBeTokenOwner2035 );20362037 // update balance2038 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2039 .checked_sub(1)2040 .ok_or(Error::<T>::NumOverflow)?;2041 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20422043 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2044 .checked_add(1)2045 .ok_or(Error::<T>::NumOverflow)?;2046 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20472048 // change owner2049 let old_owner = item.owner.clone();2050 item.owner = new_owner.clone();2051 <NftItemList<T>>::insert(collection_id, item_id, item);20522053 // update index collection2054 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20552056 Ok(())2057 }2058 2059 fn set_re_fungible_variable_data(2060 collection_id: CollectionId,2061 item_id: TokenId,2062 data: Vec<u8>2063 ) -> DispatchResult {2064 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20652066 item.variable_data = data;20672068 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20692070 Ok(())2071 }20722073 fn set_nft_variable_data(2074 collection_id: CollectionId,2075 item_id: TokenId,2076 data: Vec<u8>2077 ) -> DispatchResult {2078 let mut item = <NftItemList<T>>::get(collection_id, item_id);2079 2080 item.variable_data = data;20812082 <NftItemList<T>>::insert(collection_id, item_id, item);2083 2084 Ok(())2085 }20862087 fn init_collection(item: &CollectionType<T::AccountId>) {2088 // check params2089 assert!(2090 item.decimal_points <= MAX_DECIMAL_POINTS,2091 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2092 );2093 assert!(2094 item.name.len() <= 64,2095 "Collection name can not be longer than 63 char"2096 );2097 assert!(2098 item.name.len() <= 256,2099 "Collection description can not be longer than 255 char"2100 );2101 assert!(2102 item.token_prefix.len() <= 16,2103 "Token prefix can not be longer than 15 char"2104 );21052106 // Generate next collection ID2107 let next_id = CreatedCollectionCount::get()2108 .checked_add(1)2109 .unwrap();21102111 CreatedCollectionCount::put(next_id);2112 }21132114 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2115 let current_index = <ItemListIndex>::get(collection_id)2116 .checked_add(1)2117 .unwrap();21182119 let item_owner = item.owner.clone();2120 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21212122 <ItemListIndex>::insert(collection_id, current_index);21232124 // Update balance2125 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2126 .checked_add(1)2127 .unwrap();2128 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2129 }21302131 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2132 let current_index = <ItemListIndex>::get(collection_id)2133 .checked_add(1)2134 .unwrap();21352136 Self::add_token_index(collection_id, current_index, owner).unwrap();21372138 <ItemListIndex>::insert(collection_id, current_index);21392140 // Update balance2141 let new_balance = <Balance<T>>::get(collection_id, owner)2142 .checked_add(item.value)2143 .unwrap();2144 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2145 }21462147 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2148 let current_index = <ItemListIndex>::get(collection_id)2149 .checked_add(1)2150 .unwrap();21512152 let value = item.owner.first().unwrap().fraction;2153 let owner = item.owner.first().unwrap().owner.clone();21542155 Self::add_token_index(collection_id, current_index, &owner).unwrap();21562157 <ItemListIndex>::insert(collection_id, current_index);21582159 // Update balance2160 let new_balance = <Balance<T>>::get(collection_id, &owner)2161 .checked_add(value)2162 .unwrap();2163 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2164 }21652166 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {21672168 // add to account limit2169 if <AccountItemCount<T>>::contains_key(owner) {21702171 // bound Owned tokens by a single address2172 let count = <AccountItemCount<T>>::get(owner);2173 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21742175 <AccountItemCount<T>>::insert(owner.clone(), count2176 .checked_add(1)2177 .ok_or(Error::<T>::NumOverflow)?);2178 }2179 else {2180 <AccountItemCount<T>>::insert(owner.clone(), 1);2181 }21822183 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2184 if list_exists {2185 let mut list = <AddressTokens<T>>::get(collection_id, owner);2186 let item_contains = list.contains(&item_index.clone());21872188 if !item_contains {2189 list.push(item_index.clone());2190 }21912192 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2193 } else {2194 let mut itm = Vec::new();2195 itm.push(item_index.clone());2196 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2197 }21982199 Ok(())2200 }22012202 fn remove_token_index(2203 collection_id: CollectionId,2204 item_index: TokenId,2205 owner: &T::AccountId,2206 ) -> DispatchResult {22072208 // update counter2209 <AccountItemCount<T>>::insert(owner.clone(), 2210 <AccountItemCount<T>>::get(owner)2211 .checked_sub(1)2212 .ok_or(Error::<T>::NumOverflow)?);221322142215 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2216 if list_exists {2217 let mut list = <AddressTokens<T>>::get(collection_id, owner);2218 let item_contains = list.contains(&item_index.clone());22192220 if item_contains {2221 list.retain(|&item| item != item_index);2222 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2223 }2224 }22252226 Ok(())2227 }22282229 fn move_token_index(2230 collection_id: CollectionId,2231 item_index: TokenId,2232 old_owner: &T::AccountId,2233 new_owner: &T::AccountId,2234 ) -> DispatchResult {2235 Self::remove_token_index(collection_id, item_index, old_owner)?;2236 Self::add_token_index(collection_id, item_index, new_owner)?;22372238 Ok(())2239 }2240 2241 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2242 if <ContractOwner<T>>::contains_key(contract.clone()) {2243 let owner = <ContractOwner<T>>::get(contract);2244 ensure!(account == owner, Error::<T>::NoPermission);2245 } else {2246 fail!(Error::<T>::NoPermission);2247 }22482249 Ok(())2250 }2251}22522253////////////////////////////////////////////////////////////////////////////////////////////////////2254// Economic models2255// #region22562257/// Fee multiplier.2258pub type Multiplier = FixedU128;22592260type BalanceOf<T> = <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::Balance;22612262/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2263/// in the queue.2264#[derive(Encode, Decode, Clone, Eq, PartialEq)]2265pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);22662267impl<T: Config + Send + Sync> sp_std::fmt::Debug 2268 for ChargeTransactionPayment<T>2269{2270 #[cfg(feature = "std")]2271 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2272 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2273 }2274 #[cfg(not(feature = "std"))]2275 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2276 Ok(())2277 }2278}22792280impl<T: Config> ChargeTransactionPayment<T>2281where2282 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2283 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2284 T::AccountId: AsRef<[u8]>,2285 T::AccountId: UncheckedFrom<T::Hash>,2286{2287 fn traditional_fee(2288 len: usize,2289 info: &DispatchInfoOf<T::Call>,2290 tip: BalanceOf<T>,2291 ) -> BalanceOf<T>2292 where2293 T::Call: Dispatchable<Info = DispatchInfo>,2294 {2295 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2296 }22972298 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2299 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2300 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2301 let len_saturation = max_block_length as u64 / (len as u64).max(1);2302 let coefficient: BalanceOf<T> = weight_saturation2303 .min(len_saturation)2304 .saturated_into::<BalanceOf<T>>();2305 final_fee2306 .saturating_mul(coefficient)2307 .saturated_into::<TransactionPriority>()2308 }23092310 fn withdraw_fee(2311 &self,2312 who: &T::AccountId,2313 call: &T::Call,2314 info: &DispatchInfoOf<T::Call>,2315 len: usize,2316 ) -> Result<2317 (2318 BalanceOf<T>,2319 <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2320 ),2321 TransactionValidityError,2322 > {2323 let tip = self.0;23242325 // Set fee based on call type. Creating collection costs 1 Unique.2326 // All other transactions have traditional fees so far2327 // let fee = match call.is_sub_type() {2328 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2329 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2330 // // _ => <BalanceOf<T>>::from(100)2331 // };2332 let fee = Self::traditional_fee(len, info, tip);23332334 // Only mess with balances if fee is not zero.2335 if fee.is_zero() {2336 return <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2337 .map(|i| (fee, i));2338 }23392340 // Determine who is paying transaction fee based on ecnomic model2341 // Parse call to extract collection ID and access collection sponsor2342 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2343 Some(Call::create_item(collection_id, _owner, _properties)) => {23442345 // sponsor timeout2346 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23472348 let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2349 let mut sponsored = true;2350 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2351 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2352 let limit_time = last_tx_block + limit.into();2353 if block_number <= limit_time {2354 sponsored = false;2355 }2356 }2357 if sponsored {2358 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2359 }23602361 // check free create limit2362 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2363 (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2364 (sponsored)2365 {2366 <Collection<T>>::get(collection_id).sponsor2367 } else {2368 T::AccountId::default()2369 }2370 }2371 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2372 2373 let mut sponsor_transfer = false;2374 if <Collection<T>>::get(collection_id).sponsor_confirmed {23752376 let collection_limits = <Collection<T>>::get(collection_id).limits;2377 let collection_mode = <Collection<T>>::get(collection_id).mode;2378 2379 // sponsor timeout2380 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2381 sponsor_transfer = match collection_mode {2382 CollectionMode::NFT => {2383 2384 // get correct limit2385 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2386 collection_limits.sponsor_transfer_timeout2387 } else {2388 ChainLimit::get().nft_sponsor_transfer_timeout2389 };2390 2391 let mut sponsored = true;2392 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2393 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2394 let limit_time = last_tx_block + limit.into();2395 if block_number <= limit_time {2396 sponsored = false;2397 }2398 }2399 if sponsored {2400 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2401 }24022403 sponsored2404 }2405 CollectionMode::Fungible(_) => {2406 2407 // get correct limit2408 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2409 collection_limits.sponsor_transfer_timeout2410 } else {2411 ChainLimit::get().fungible_sponsor_transfer_timeout2412 };2413 2414 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2415 let mut sponsored = true;2416 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2417 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2418 let limit_time = last_tx_block + limit.into();2419 if block_number <= limit_time {2420 sponsored = false;2421 }2422 }2423 if sponsored {2424 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2425 }24262427 sponsored2428 }2429 CollectionMode::ReFungible => {2430 2431 // get correct limit2432 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2433 collection_limits.sponsor_transfer_timeout2434 } else {2435 ChainLimit::get().refungible_sponsor_transfer_timeout2436 };2437 2438 let mut sponsored = true;2439 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2440 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2441 let limit_time = last_tx_block + limit.into();2442 if block_number <= limit_time {2443 sponsored = false;2444 }2445 }2446 if sponsored {2447 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2448 }24492450 sponsored2451 }2452 _ => {2453 false2454 },2455 };2456 }24572458 if !sponsor_transfer {2459 T::AccountId::default()2460 } else {2461 <Collection<T>>::get(collection_id).sponsor2462 }2463 }24642465 _ => T::AccountId::default(),2466 };24672468 // Sponsor smart contracts2469 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24702471 // On instantiation: set the contract owner2472 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {24732474 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2475 &who,2476 code_hash,2477 salt,2478 );2479 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24802481 T::AccountId::default()2482 },24832484 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2485 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24862487 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24882489 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2490 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2491 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2492 2493 if !owned_contract && white_list_enabled {2494 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2495 return Err(InvalidTransaction::Call.into());2496 }2497 }24982499 let mut sponsor_transfer = false;2500 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2501 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2502 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2503 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2504 let limit_time = last_tx_block + rate_limit;25052506 if block_number >= limit_time {2507 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2508 sponsor_transfer = true;2509 }2510 } else {2511 sponsor_transfer = false;2512 }2513 2514 2515 let mut sp = T::AccountId::default();2516 if sponsor_transfer {2517 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2518 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2519 sp = called_contract;2520 }2521 }2522 }25232524 sp2525 },25262527 _ => sponsor,2528 };25292530 let mut who_pays_fee: T::AccountId = sponsor.clone();2531 if sponsor == T::AccountId::default() {2532 who_pays_fee = who.clone();2533 }25342535 <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2536 .map(|i| (fee, i))2537 }2538}253925402541impl<T: Config + Send + Sync> SignedExtension2542 for ChargeTransactionPayment<T>2543where2544 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2545 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2546 T::AccountId: AsRef<[u8]>,2547 T::AccountId: UncheckedFrom<T::Hash>,2548{2549 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2550 type AccountId = T::AccountId;2551 type Call = T::Call;2552 type AdditionalSigned = ();2553 type Pre = (2554 // tip2555 BalanceOf<T>,2556 // who pays fee2557 Self::AccountId,2558 // imbalance resulting from withdrawing the fee2559 <<T as transaction_payment::Config>::OnChargeTransaction as transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2560 );2561 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2562 Ok(())2563 }25642565 fn validate(2566 &self,2567 who: &Self::AccountId,2568 call: &Self::Call,2569 info: &DispatchInfoOf<Self::Call>,2570 len: usize,2571 ) -> TransactionValidity {2572 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2573 Ok(ValidTransaction {2574 priority: Self::get_priority(len, info, fee),2575 ..Default::default()2576 })2577 }25782579 fn pre_dispatch(2580 self,2581 who: &Self::AccountId,2582 call: &Self::Call,2583 info: &DispatchInfoOf<Self::Call>,2584 len: usize,2585 ) -> Result<Self::Pre, TransactionValidityError> {2586 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2587 Ok((self.0, who.clone(), imbalance))2588 }25892590 fn post_dispatch(2591 pre: Self::Pre,2592 info: &DispatchInfoOf<Self::Call>,2593 post_info: &PostDispatchInfoOf<Self::Call>,2594 len: usize,2595 _result: &DispatchResult,2596 ) -> Result<(), TransactionValidityError> {2597 let (tip, who, imbalance) = pre;2598 let actual_fee = transaction_payment::Module::<T>::compute_actual_fee(2599 len as u32,2600 info,2601 post_info,2602 tip,2603 );2604 <T as transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2605 Ok(())2606 }2607}26082609// #endregionpallets/nft/src/mock.rsdiffbeforeafterboth--- a/pallets/nft/src/mock.rs
+++ b/pallets/nft/src/mock.rs
@@ -1,26 +1,21 @@
// Creating mock runtime here
-use crate::{Module, Trait};
-
-use pallet_contracts::{
- ContractAddressFor, TrieId, TrieIdGenerator,
-};
+use crate::{Module, Config};
use frame_support::{
impl_outer_origin, parameter_types,
- weights::{
- // constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight},
- Weight, IdentityFee,
- },
+ weights::{Weight, IdentityFee},
};
use frame_system as system;
-use transaction_payment;
+use system::limits::{BlockLength, BlockWeights};
+use transaction_payment::{self, CurrencyAdapter};
use sp_core::H256;
use sp_runtime::{
testing::Header,
- traits::{BlakeTwo256, IdentityLookup, Saturating},
+ traits::{BlakeTwo256, IdentityLookup},
Perbill,
};
+use pallet_contracts::WeightInfo;
pub use pallet_balances;
impl_outer_origin! {
@@ -34,14 +29,12 @@
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
- pub const MaximumBlockWeight: Weight = 1024;
- pub const MaximumBlockLength: u32 = 2 * 1024;
- pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75);
- pub MaximumExtrinsicWeight: Weight = AvailableBlockRatio::get()
- .saturating_sub(Perbill::from_percent(10)) * MaximumBlockWeight::get();
+ pub TestBlockWeights: BlockWeights = BlockWeights::default();
+ pub TestBlockLength: BlockLength = BlockLength::default();
+ pub SS58Prefix: u8 = 0;
}
-impl system::Trait for Test {
+impl system::Config for Test {
type BaseCallFilter = ();
type Origin = Origin;
type Call = ();
@@ -54,18 +47,15 @@
type Header = Header;
type Event = ();
type BlockHashCount = BlockHashCount;
- type MaximumBlockWeight = MaximumBlockWeight;
+ type BlockWeights = TestBlockWeights;
type DbWeight = ();
- type BlockExecutionWeight = ();
- type ExtrinsicBaseWeight = ();
- type MaximumExtrinsicWeight = MaximumBlockWeight;
- type MaximumBlockLength = MaximumBlockLength;
- type AvailableBlockRatio = AvailableBlockRatio;
+ type BlockLength = TestBlockLength;
type Version = ();
type PalletInfo = ();
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
+ type SS58Prefix = SS58Prefix;
type SystemWeightInfo = ();
}
@@ -75,7 +65,7 @@
}
type System = frame_system::Module<Test>;
-impl pallet_balances::Trait for Test {
+impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = u64;
type DustRemoval = ();
@@ -88,9 +78,9 @@
parameter_types! {
pub const TransactionByteFee: u64 = 1;
}
-impl transaction_payment::Trait for Test {
- type Currency = pallet_balances::Module<Test>;
- type OnTransactionPayment = ();
+
+impl transaction_payment::Config for Test {
+ type OnChargeTransaction = CurrencyAdapter<pallet_balances::Module<Test>, ()>;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = IdentityFee<u64>;
type FeeMultiplierUpdate = ();
@@ -100,7 +90,7 @@
parameter_types! {
pub const MinimumPeriod: u64 = 1;
}
-impl pallet_timestamp::Trait for Test {
+impl pallet_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = MinimumPeriod;
@@ -112,49 +102,45 @@
parameter_types! {
pub const TombstoneDeposit: u64 = 1;
- pub const RentByteFee: u64 = 1;
- pub const RentDepositOffset: u64 = 1;
+ pub const DepositPerContract: u64 = 1;
+ pub const DepositPerStorageByte: u64 = 1;
+ pub const DepositPerStorageItem: u64 = 1;
+ pub RentFraction: Perbill = Perbill::from_rational_approximation(1u32, 30 * 24 * 60 * 10);
pub const SurchargeReward: u64 = 1;
-}
-
-pub struct DummyTrieIdGenerator;
-impl TrieIdGenerator<u64> for DummyTrieIdGenerator {
- fn trie_id(account_id: &u64) -> TrieId {
- let new_seed = *account_id + 1;
- let mut res = vec![];
- res.extend_from_slice(&new_seed.to_le_bytes());
- res.extend_from_slice(&account_id.to_le_bytes());
- res
- }
-}
-
-pub struct DummyContractAddressFor;
-impl ContractAddressFor<H256, u64> for DummyContractAddressFor {
- fn contract_address_for(_code_hash: &H256, _data: &[u8], origin: &u64) -> u64 {
- *origin + 1
- }
+ pub const SignedClaimHandicap: u32 = 2;
+ pub const MaxDepth: u32 = 32;
+ pub const MaxValueSize: u32 = 16 * 1024;
+ pub DeletionWeightLimit: Weight = Perbill::from_percent(10) *
+ TestBlockWeights::get().max_block;
+ pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / (
+ <Test as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
+ <Test as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
+ )) / 5) as u32;
}
-impl pallet_contracts::Trait for Test {
+impl pallet_contracts::Config for Test {
type Time = Timestamp;
type Randomness = Randomness;
type Currency = pallet_balances::Module<Test>;
type Event = ();
- type DetermineContractAddress = DummyContractAddressFor;
- type TrieIdGenerator = DummyTrieIdGenerator;
type RentPayment = ();
- type SignedClaimHandicap = pallet_contracts::DefaultSignedClaimHandicap;
+ type SignedClaimHandicap = SignedClaimHandicap;
type TombstoneDeposit = TombstoneDeposit;
- type StorageSizeOffset = pallet_contracts::DefaultStorageSizeOffset;
- type RentByteFee = RentByteFee;
- type RentDepositOffset = RentDepositOffset;
+ type DepositPerContract = DepositPerContract;
+ type DepositPerStorageByte = DepositPerStorageByte;
+ type DepositPerStorageItem = DepositPerStorageItem;
+ type RentFraction = RentFraction;
type SurchargeReward = SurchargeReward;
- type MaxDepth = pallet_contracts::DefaultMaxDepth;
- type MaxValueSize = pallet_contracts::DefaultMaxValueSize;
+ type DeletionWeightLimit = DeletionWeightLimit;
+ type MaxDepth = MaxDepth;
+ type DeletionQueueDepth = DeletionQueueDepth;
+ type MaxValueSize = MaxValueSize;
+ type ChainExtension = ();
type WeightPrice = ();
+ type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
}
-impl Trait for Test {
+impl Config for Test {
type Event = ();
type WeightInfo = ();
pallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -35,7 +35,7 @@
}
fn default_re_fungible_data () -> CreateReFungibleData {
- CreateReFungibleData { const_data: vec![1, 2, 3], variable_data: vec![3, 2, 1] }
+ CreateReFungibleData { const_data: vec![1, 2, 3], variable_data: vec![3, 2, 1], pieces: 1023 }
}
fn create_test_collection_for_owner(mode: &CollectionMode, owner: u64, id: CollectionId) -> CollectionId {
@@ -114,26 +114,6 @@
}
#[test]
-fn create_re_fungible_collection_fails_with_large_decimal_numbers() {
- new_test_ext().execute_with(|| {
- default_limits();
-
- let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
- let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
- let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
-
- let origin1 = Origin::signed(1);
- assert_noop!(TemplateModule::create_collection(
- origin1,
- col_name1,
- col_desc1,
- token_prefix1,
- CollectionMode::ReFungible(MAX_DECIMAL_POINTS + 1)
- ), Error::<Test>::CollectionDecimalPointLimitExceeded);
- });
-}
-
-#[test]
fn create_nft_item() {
new_test_ext().execute_with(|| {
default_limits();
@@ -176,7 +156,7 @@
fn create_refungible_item() {
new_test_ext().execute_with(|| {
default_limits();
- let collection_id = create_test_collection(&CollectionMode::ReFungible(3), 1);
+ let collection_id = create_test_collection(&CollectionMode::ReFungible, 1);
let data = default_re_fungible_data();
create_test_item(collection_id, &data.clone().into());
@@ -192,7 +172,7 @@
TemplateModule::refungible_item_id(collection_id, 1).owner[0],
Ownership {
owner: 1,
- fraction: 1000
+ fraction: 1023
}
);
});
@@ -203,7 +183,7 @@
new_test_ext().execute_with(|| {
default_limits();
- create_test_collection(&CollectionMode::ReFungible(3), 1);
+ create_test_collection(&CollectionMode::ReFungible, 1);
let origin1 = Origin::signed(1);
@@ -224,7 +204,7 @@
item.owner[0],
Ownership {
owner: 1,
- fraction: 1000
+ fraction: 1023
}
);
}
@@ -312,7 +292,7 @@
new_test_ext().execute_with(|| {
default_limits();
- let collection_id = create_test_collection(&CollectionMode::ReFungible(3), 1);
+ let collection_id = create_test_collection(&CollectionMode::ReFungible, 1);
let data = default_re_fungible_data();
create_test_item(collection_id, &data.clone().into());
@@ -331,23 +311,23 @@
TemplateModule::refungible_item_id(collection_id, 1).owner[0],
Ownership {
owner: 1,
- fraction: 1000
+ fraction: 1023
}
);
- assert_eq!(TemplateModule::balance_count(1, 1), 1000);
+ assert_eq!(TemplateModule::balance_count(1, 1), 1023);
assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
// change owner scenario
- assert_ok!(TemplateModule::transfer(origin1.clone(), 2, 1, 1, 1000));
+ assert_ok!(TemplateModule::transfer(origin1.clone(), 2, 1, 1, 1023));
assert_eq!(
TemplateModule::refungible_item_id(1, 1).owner[0],
Ownership {
owner: 2,
- fraction: 1000
+ fraction: 1023
}
);
assert_eq!(TemplateModule::balance_count(1, 1), 0);
- assert_eq!(TemplateModule::balance_count(1, 2), 1000);
+ assert_eq!(TemplateModule::balance_count(1, 2), 1023);
// assert_eq!(TemplateModule::address_tokens(1, 1), []);
assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
@@ -357,7 +337,7 @@
TemplateModule::refungible_item_id(1, 1).owner[0],
Ownership {
owner: 2,
- fraction: 500
+ fraction: 523
}
);
assert_eq!(
@@ -367,7 +347,7 @@
fraction: 500
}
);
- assert_eq!(TemplateModule::balance_count(1, 2), 500);
+ assert_eq!(TemplateModule::balance_count(1, 2), 523);
assert_eq!(TemplateModule::balance_count(1, 3), 500);
assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
assert_eq!(TemplateModule::address_tokens(1, 3), [1]);
@@ -378,7 +358,7 @@
TemplateModule::refungible_item_id(1, 1).owner[0],
Ownership {
owner: 2,
- fraction: 300
+ fraction: 323
}
);
assert_eq!(
@@ -388,7 +368,7 @@
fraction: 700
}
);
- assert_eq!(TemplateModule::balance_count(1, 2), 300);
+ assert_eq!(TemplateModule::balance_count(1, 2), 323);
assert_eq!(TemplateModule::balance_count(1, 3), 700);
assert_eq!(TemplateModule::address_tokens(1, 2), [1]);
assert_eq!(TemplateModule::address_tokens(1, 3), [1]);
@@ -454,7 +434,7 @@
assert_ok!(TemplateModule::transfer_from(
origin2.clone(),
1,
- 2,
+ 3,
1,
1,
1
@@ -508,7 +488,7 @@
1,
1
));
- assert_eq!(TemplateModule::approved(1, (1, 1, 3)), 4);
+ assert_eq!(TemplateModule::approved(1, (1, 1, 2)), 4);
});
}
@@ -517,7 +497,7 @@
new_test_ext().execute_with(|| {
default_limits();
- let collection_id = create_test_collection(&CollectionMode::ReFungible(3), 1);
+ let collection_id = create_test_collection(&CollectionMode::ReFungible, 1);
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
@@ -525,7 +505,7 @@
let data = default_re_fungible_data();
create_test_item(collection_id, &data.into());
- assert_eq!(TemplateModule::balance_count(1, 1), 1000);
+ assert_eq!(TemplateModule::balance_count(1, 1), 1023);
assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
assert_ok!(TemplateModule::set_mint_permission(
@@ -543,10 +523,8 @@
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3));
// do approve
- assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1, 5));
- assert_eq!(TemplateModule::approved(1, (1, 1, 2)), 5);
- assert_ok!(TemplateModule::approve(origin1.clone(), 3, 1, 1, 1000));
- assert_eq!(TemplateModule::approved(1, (1, 1, 3)), 1000);
+ assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1, 1023));
+ assert_eq!(TemplateModule::approved(1, (1, 1, 2)), 1023);
assert_ok!(TemplateModule::transfer_from(
origin2.clone(),
@@ -556,14 +534,14 @@
1,
100
));
- assert_eq!(TemplateModule::balance_count(1, 1), 900);
+ assert_eq!(TemplateModule::balance_count(1, 1), 923);
assert_eq!(TemplateModule::balance_count(1, 3), 100);
assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
assert_eq!(TemplateModule::address_tokens(1, 3), [1]);
assert_eq!(
- TemplateModule::approved(1, (1, 1, 3)),
- 900
+ TemplateModule::approved(1, (1, 1, 2)),
+ 923
);
});
}
@@ -618,10 +596,8 @@
assert_eq!(TemplateModule::balance_count(1, 1), 1);
assert_eq!(TemplateModule::balance_count(1, 3), 4);
- assert_eq!(TemplateModule::approved(1, (1, 1, 2)), 5);
- assert_eq!(TemplateModule::approved(1, (1, 1, 3)), 1);
+ assert_eq!(TemplateModule::approved(1, (1, 1, 2)), 1);
- assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1, 5));
assert_noop!(TemplateModule::transfer_from(
origin2.clone(),
1,
@@ -721,7 +697,7 @@
new_test_ext().execute_with(|| {
default_limits();
- let collection_id = create_test_collection(&CollectionMode::ReFungible(3), 1);
+ let collection_id = create_test_collection(&CollectionMode::ReFungible, 1);
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::set_mint_permission(
@@ -742,12 +718,12 @@
create_test_item(collection_id, &data.into());
// check balance (collection with id = 1, user id = 2)
- assert_eq!(TemplateModule::balance_count(1, 1), 1000);
+ assert_eq!(TemplateModule::balance_count(1, 1), 1023);
// burn item
- assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1, 1000));
+ assert_ok!(TemplateModule::burn_item(origin1.clone(), 1, 1, 1023));
assert_noop!(
- TemplateModule::burn_item(origin1.clone(), 1, 1, 1000),
+ TemplateModule::burn_item(origin1.clone(), 1, 1, 1023),
Error::<Test>::TokenNotFound
);
@@ -811,7 +787,7 @@
let nft_collection_id = create_test_collection(&CollectionMode::NFT, 1);
let fungible_collection_id = create_test_collection(&CollectionMode::Fungible(3), 2);
- let re_fungible_collection_id = create_test_collection(&CollectionMode::ReFungible(3), 3);
+ let re_fungible_collection_id = create_test_collection(&CollectionMode::ReFungible, 3);
// check balance before
assert_eq!(TemplateModule::balance_count(nft_collection_id, 1), 0);
@@ -830,7 +806,7 @@
// check balance (collection with id = 1, user id = 1)
assert_eq!(TemplateModule::balance_count(nft_collection_id, 1), 1);
assert_eq!(TemplateModule::balance_count(fungible_collection_id, 1), 5);
- assert_eq!(TemplateModule::balance_count(re_fungible_collection_id, 1), 1000);
+ assert_eq!(TemplateModule::balance_count(re_fungible_collection_id, 1), 1023);
assert_eq!(TemplateModule::nft_item_id(nft_collection_id, 1).owner, 1);
assert_eq!(TemplateModule::fungible_item_id(fungible_collection_id, 1).value, 5);
assert_eq!(TemplateModule::refungible_item_id(re_fungible_collection_id, 1).owner[0].owner, 1);
@@ -1909,7 +1885,7 @@
new_test_ext().execute_with(|| {
default_limits();
- let collection_id = create_test_collection(&CollectionMode::ReFungible(3), 1);
+ let collection_id = create_test_collection(&CollectionMode::ReFungible, 1);
let origin1 = Origin::signed(1);
@@ -1986,7 +1962,7 @@
}));
- let collection_id = create_test_collection(&CollectionMode::ReFungible(3), 1);
+ let collection_id = create_test_collection(&CollectionMode::ReFungible, 1);
let origin1 = Origin::signed(1);
runtime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -1,27 +1,4 @@
{
- "Schedule": {
- "version": "u32",
- "put_code_per_byte_cost": "Gas",
- "grow_mem_cost": "Gas",
- "regular_op_cost": "Gas",
- "return_data_per_byte_cost": "Gas",
- "event_data_per_byte_cost": "Gas",
- "event_per_topic_cost": "Gas",
- "event_base_cost": "Gas",
- "call_base_cost": "Gas",
- "instantiate_base_cost": "Gas",
- "dispatch_base_cost": "Gas",
- "sandbox_data_read_cost": "Gas",
- "sandbox_data_write_cost": "Gas",
- "transfer_cost": "Gas",
- "instantiate_cost": "Gas",
- "max_event_topics": "u32",
- "max_stack_height": "u32",
- "max_memory_pages": "u32",
- "max_table_size": "u32",
- "enable_println": "bool",
- "max_subject_len": "u32"
- },
"AccessMode": {
"_enum": [
"Normal",
@@ -34,7 +11,7 @@
"Invalid": null,
"NFT": null,
"Fungible": "DecimalPoints",
- "ReFungible": "DecimalPoints"
+ "ReFungible": null
}
},
"Ownership": {
@@ -84,7 +61,8 @@
},
"CreateReFungibleData": {
"const_data": "Vec<u8>",
- "variable_data": "Vec<u8>"
+ "variable_data": "Vec<u8>",
+ "pieces": "u128"
},
"CreateItemData": {
"_enum": {
@@ -102,27 +80,21 @@
"CollectionId": "u32",
"TokenId": "u32",
"ChainLimits": {
- "collection_numbers_limit": "u32",
- "account_token_ownership_limit": "u32",
- "collections_admins_limit": "u64",
- "custom_data_limit": "u32",
- "nft_sponsor_timeout": "u32",
- "fungible_sponsor_timeout": "u32",
- "refungible_sponsor_timeout": "u32"
+ "CollectionNumbersLimit": "u32",
+ "AccountTokenOwnershipLimit": "u32",
+ "CollectionAdminsLimit": "u64",
+ "CustomDataLimit": "u32",
+ "NftSponsorTimeout": "u32",
+ "FungibleSponsorTimeout": "u32",
+ "RefungibleSponsorTimeout": "u32",
+ "OffchainSchemaLimit": "u32",
+ "VariableOnChainSchemaLimit": "u32",
+ "ConstOnChainSchemaLimit": "u32"
},
"CollectionLimits": {
"AccountTokenOwnershipLimit": "u32",
"SponsoredMintSize": "u32",
"TokenLimit": "u32",
"SponsorTimeout": "u32"
- },
- "AccountInfo": "AccountInfoWithProviders",
- "AccountInfoWithProviders": {
- "nonce": "Index",
- "consumers": "RefCount",
- "providers": "RefCount",
- "data": "AccountData"
}
-
- }
-
\ No newline at end of file
+}
\ No newline at end of file
tests/.vscode/settings.jsondiffbeforeafterboth--- /dev/null
+++ b/tests/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "mocha.enabled": true
+}
\ No newline at end of file
tests/README.mddiffbeforeafterboth--- a/tests/README.md
+++ b/tests/README.md
@@ -2,8 +2,8 @@
## How to run
-1. Run `npm install`.
+1. Run `yarn install`.
2. Setup a test node. You can do it using `docker-compose up -d` in parent directory.
3. Optional step - configure tests with env variables or by editing [configuration file](src/config.ts).
-4. Run `npm test`.
+4. Run `yarn test`.
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -22,10 +22,13 @@
"load": "mocha --timeout 9999999 -r ts-node/register ./**/*.load.ts",
"testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",
"testSetSchemaVersion": "mocha --timeout 9999999 -r ts-node/register ./**/setSchemaVersion.test.ts",
+ "testSetVariableMetaData": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetaData.test.ts",
"testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",
"testRemoveCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/removeCollectionAdmin.test.ts",
+ "testRemoveFromWhiteList": "mocha --timeout 9999999 -r ts-node/register ./**/removeFromWhiteList.test.ts",
"testConnection": "mocha --timeout 9999999 -r ts-node/register ./**/connection.test.ts",
"testCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",
+ "testCreateMultipleItems": "mocha --timeout 9999999 -r ts-node/register ./**/createMultipleItems.test.ts",
"testApprove": "mocha --timeout 9999999 -r ts-node/register ./**/approve.test.ts",
"testTransferFrom": "mocha --timeout 9999999 -r ts-node/register ./**/transferFrom.test.ts",
"testCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",
@@ -33,7 +36,10 @@
"testAddToContractWhiteList": "mocha --timeout 9999999 -r ts-node/register ./**/addToContractWhiteList.test.ts",
"testTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/transfer.test.ts",
"testBurnItem": "mocha --timeout 9999999 -r ts-node/register ./**/burnItem.test.ts",
- "testCreditFeesToTreasury": "mocha --timeout 9999999 -r ts-node/register ./**/creditFeesToTreasury.test.ts"
+ "testSetMintPermission": "mocha --timeout 9999999 -r ts-node/register ./**/setMintPermission.test.ts",
+ "testCreditFeesToTreasury": "mocha --timeout 9999999 -r ts-node/register ./**/creditFeesToTreasury.test.ts",
+ "testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts",
+ "testSetContractSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setContractSponsoringRateLimit.test.ts"
},
"author": "",
"license": "SEE LICENSE IN ../LICENSE",
tests/src/addCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -1,4 +1,9 @@
-import { ApiPromise } from '@polkadot/api';
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { ApiPromise } from '@polkadot/api';
import BN from 'bn.js';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
@@ -98,7 +103,7 @@
});
});
- it('Add an admin to a collection that has reached the maximum number of admins limit', async () => {
+ it.only('Add an admin to a collection that has reached the maximum number of admins limit', async () => {
await usingApi(async (api: ApiPromise) => {
const Alice = privateKey('//Alice');
const accounts = [
@@ -112,18 +117,18 @@
];
const collectionId = await createCollectionExpectSuccess();
- const chainLimit = await api.query.nft.chainLimit() as unknown as { collections_admins_limit: BN };
- const chainLimitNumber = chainLimit.collections_admins_limit.toNumber();
- expect(chainLimitNumber).to.be.equal(5);
+ const chainLimit = await api.query.nft.chainLimit() as unknown as { CollectionAdminsLimit: BN };
+ const chainAdminLimit = chainLimit.CollectionAdminsLimit.toNumber();
+ expect(chainAdminLimit).to.be.equal(5);
- for (let i = 0; i < chainLimitNumber; i++) {
+ for (let i = 0; i < chainAdminLimit; i++) {
const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, accounts[i]);
await submitTransactionAsync(Alice, changeAdminTx);
const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
expect(adminListAfterAddAdmin).to.be.contains(accounts[i]);
}
- const tx = api.tx.nft.addCollectionAdmin(collectionId, accounts[chainLimitNumber]);
+ const tx = api.tx.nft.addCollectionAdmin(collectionId, accounts[chainAdminLimit]);
await expect(submitTransactionExpectFailAsync(Alice, tx)).to.be.rejected;
});
});
tests/src/addToContractWhiteList.test.tsdiffbeforeafterboth--- a/tests/src/addToContractWhiteList.test.ts
+++ b/tests/src/addToContractWhiteList.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import chai from "chai";
import chaiAsPromised from 'chai-as-promised';
import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
tests/src/addToWhiteList.test.tsdiffbeforeafterboth--- a/tests/src/addToWhiteList.test.ts
+++ b/tests/src/addToWhiteList.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import { IKeyringPair } from '@polkadot/types/types';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
tests/src/approve.test.tsdiffbeforeafterboth--- a/tests/src/approve.test.ts
+++ b/tests/src/approve.test.ts
@@ -34,7 +34,7 @@
await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob);
// reFungible
const reFungibleCollectionId =
- await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob);
});
@@ -56,7 +56,7 @@
await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 0);
// reFungible
const reFungibleCollectionId =
- await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 1);
await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 0);
@@ -95,7 +95,7 @@
await approveExpectFail(fungibleCollectionId, 1, Alice, Bob);
// reFungible
const reFungibleCollectionId =
- await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
await destroyCollectionExpectSuccess(reFungibleCollectionId);
await approveExpectFail(reFungibleCollectionId, 1, Alice, Bob);
});
@@ -113,7 +113,7 @@
await approveExpectFail(fungibleCollectionId, 2, Alice, Bob);
// reFungible
const reFungibleCollectionId =
- await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
await approveExpectFail(reFungibleCollectionId, 2, Alice, Bob);
});
});
@@ -132,7 +132,7 @@
await approveExpectFail(fungibleCollectionId, newFungibleTokenId, Bob, Alice);
// reFungible
const reFungibleCollectionId =
- await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, Bob, Alice);
});
tests/src/burnItem.test.tsdiffbeforeafterboth--- a/tests/src/burnItem.test.ts
+++ b/tests/src/burnItem.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
import { Keyring } from "@polkadot/api";
import { IKeyringPair } from "@polkadot/types/types";
@@ -70,7 +75,7 @@
});
it('Burn item in ReFungible collection', async () => {
const createMode = 'ReFungible';
- const collectionId = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 2 }});
+ const collectionId = await createCollectionExpectSuccess({mode: {type: createMode }});
const tokenId = await createItemExpectSuccess(alice, collectionId, createMode);
await usingApi(async (api) => {
@@ -91,7 +96,7 @@
it('Burn owned portion of item in ReFungible collection', async () => {
const createMode = 'ReFungible';
- const collectionId = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 2 }});
+ const collectionId = await createCollectionExpectSuccess({mode: {type: createMode}});
const tokenId = await createItemExpectSuccess(alice, collectionId, createMode);
await usingApi(async (api) => {
@@ -107,7 +112,7 @@
const tx = api.tx.nft.burnItem(collectionId, tokenId, 0);
const events2 = await submitTransactionAsync(bob, tx);
const result2 = getGenericResult(events2);
-
+
// Get balances
const balance: any = (await api.query.nft.reFungibleItemList(collectionId, tokenId)).toJSON();
// console.log(balance);
tests/src/change-collection-owner.test.tsdiffbeforeafterboth--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -1,4 +1,9 @@
-import chai from 'chai';
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import privateKey from './substrate/privateKey';
import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
tests/src/confirmSponsorship.test.tsdiffbeforeafterboth--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -115,7 +115,7 @@
});
it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {
- const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0 }});
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
await setCollectionSponsorExpectSuccess(collectionId, bob.address);
await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
@@ -247,7 +247,7 @@
});
it('ReFungible: Sponsoring is rate limited', async () => {
- const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0 }});
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
await setCollectionSponsorExpectSuccess(collectionId, bob.address);
await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
tests/src/contracts.test.tsdiffbeforeafterboth--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import chai from "chai";
import chaiAsPromised from 'chai-as-promised';
import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
tests/src/createCollection.test.tsdiffbeforeafterboth--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -28,7 +28,7 @@
await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
});
it('Create new ReFungible collection', async () => {
- await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
});
});
tests/src/createItem.test.tsdiffbeforeafterboth--- a/tests/src/createItem.test.ts
+++ b/tests/src/createItem.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import { default as usingApi } from './substrate/substrate-api';
import { Keyring } from "@polkadot/api";
import { IKeyringPair } from "@polkadot/types/types";
@@ -28,7 +33,7 @@
});
it('Create new item in ReFungible collection', async () => {
const createMode = 'ReFungible';
- const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 0}});
+ const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}});
await createItemExpectSuccess(alice, newCollectionID, createMode);
});
});
tests/src/createMultipleItems.test.tsdiffbeforeafterboth--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -2,68 +2,177 @@
// This file is subject to the terms and conditions defined in
// file 'LICENSE', which is part of this source code package.
//
+import { ApiPromise } from '@polkadot/api';
+import BN from 'bn.js';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+ IReFungibleTokenDataType,
+} from './util/helpers';
-import { assert } from 'chai';
-import { alicesPublicKey } from './accounts';
-import privateKey from './substrate/privateKey';
-import usingApi from './substrate/substrate-api';
-import waitNewBlocks from './substrate/wait-new-blocks';
+chai.use(chaiAsPromised);
+const expect = chai.expect;
-const idCollection = 12;
+interface ITokenDataType {
+ Owner: number[];
+ ConstData: number[];
+ VariableData: number[];
+}
-describe.skip('integration test: ext. createMultipleItems():', () => {
- it('Create two NFT tokens in active NFT collection', async () => {
- await usingApi(async (api) => {
- const AitemListIndex = await api.query.nft.itemListIndex(idCollection);
- console.log(`itemListIndex count (before): ${AitemListIndex}`);
- const args = ['NFT', 'NFT'];
- const alicePrivateKey = privateKey('//Alice');
- const createMultipleItems = await api.tx.nft
- .createMultipleItems(idCollection, alicesPublicKey, args)
- .signAndSend(alicePrivateKey);
- // tslint:disable-next-line: no-unused-expression
- assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`');
- console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`);
- await waitNewBlocks(api);
- const BitemListIndex = await api.query.nft.itemListIndex(idCollection);
- console.log(`itemListIndex count (after): ${BitemListIndex}`);
- if (BitemListIndex === AitemListIndex) { assert.fail('Corret token not added in collection!'); }
+describe('Integration Test createMultipleItems(collection_id, owner, items_data):', () => {
+ it('Create 0x31, 0x32, 0x33 items in active NFT collection and verify tokens data in chain', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const itemsListIndexBefore = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
+ expect(itemsListIndexBefore.toNumber()).to.be.equal(0);
+ const Alice = privateKey('//Alice');
+ const args = [{ nft: ['0x31', '0x31'] }, { nft: ['0x32', '0x32'] }, { nft: ['0x33', '0x33'] }];
+ const createMultipleItemsTx = await api.tx.nft
+ .createMultipleItems(collectionId, Alice.address, args);
+ await submitTransactionAsync(Alice, createMultipleItemsTx);
+ const itemsListIndexAfter = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
+ expect(itemsListIndexAfter.toNumber()).to.be.equal(3);
+ const token1Data = await api.query.nft.nftItemList(collectionId, 1) as unknown as ITokenDataType;
+ const token2Data = await api.query.nft.nftItemList(collectionId, 2) as unknown as ITokenDataType;
+ const token3Data = await api.query.nft.nftItemList(collectionId, 3) as unknown as ITokenDataType;
+
+ expect(token1Data.Owner.toString()).to.be.equal(Alice.address);
+ expect(token2Data.Owner.toString()).to.be.equal(Alice.address);
+ expect(token3Data.Owner.toString()).to.be.equal(Alice.address);
+
+ expect(token1Data.ConstData.toString()).to.be.equal('0x31');
+ expect(token2Data.ConstData.toString()).to.be.equal('0x32');
+ expect(token3Data.ConstData.toString()).to.be.equal('0x33');
+
+ expect(token1Data.VariableData.toString()).to.be.equal('0x31');
+ expect(token2Data.VariableData.toString()).to.be.equal('0x32');
+ expect(token3Data.VariableData.toString()).to.be.equal('0x33');
});
});
- it('(!negative test!) Create two Fungible tokens in active NFT collection', async () => {
- await usingApi(async (api) => {
- const AitemListIndex = await api.query.nft.itemListIndex(idCollection);
- console.log(`itemListIndex count (before): ${AitemListIndex}`);
- const args = ['Fungible', 'Fungible'];
- const alicePrivateKey = privateKey('//Alice');
- const createMultipleItems = await api.tx.nft
- .createMultipleItems(idCollection, alicesPublicKey, args)
- .signAndSend(alicePrivateKey);
- // tslint:disable-next-line: no-unused-expression
- assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`');
- console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`);
- await waitNewBlocks(api);
- const BitemListIndex = await api.query.nft.itemListIndex(idCollection);
- console.log(`itemListIndex count (after): ${BitemListIndex}`);
- if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); }
+
+ it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+ const itemsListIndexBefore = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
+ expect(itemsListIndexBefore.toNumber()).to.be.equal(0);
+ const Alice = privateKey('//Alice');
+ const args = [
+ {refungible: {const_data: [0x31], variable_data: [0x31], pieces: 1}},
+ {refungible: {const_data: [0x32], variable_data: [0x32], pieces: 1}},
+ {refungible: {const_data: [0x33], variable_data: [0x33], pieces: 1}},
+ ];
+ const createMultipleItemsTx = await api.tx.nft
+ .createMultipleItems(collectionId, Alice.address, args);
+ await submitTransactionAsync(Alice, createMultipleItemsTx);
+ const itemsListIndexAfter = await api.query.nft.itemListIndex(collectionId) as unknown as BN;
+ expect(itemsListIndexAfter.toNumber()).to.be.equal(3);
+ const token1Data = await api.query.nft.reFungibleItemList(collectionId, 1) as unknown as IReFungibleTokenDataType;
+ const token2Data = await api.query.nft.reFungibleItemList(collectionId, 2) as unknown as IReFungibleTokenDataType;
+ const token3Data = await api.query.nft.reFungibleItemList(collectionId, 3) as unknown as IReFungibleTokenDataType;
+
+ expect(token1Data.Owner[0].Owner.toString()).to.be.equal(Alice.address);
+ expect(token1Data.Owner[0].Fraction.toNumber()).to.be.equal(1);
+
+ expect(token2Data.Owner[0].Owner.toString()).to.be.equal(Alice.address);
+ expect(token2Data.Owner[0].Fraction.toNumber()).to.be.equal(1);
+
+ expect(token3Data.Owner[0].Owner.toString()).to.be.equal(Alice.address);
+ expect(token3Data.Owner[0].Fraction.toNumber()).to.be.equal(1);
+
+ expect(token1Data.ConstData.toString()).to.be.equal('0x31');
+ expect(token2Data.ConstData.toString()).to.be.equal('0x32');
+ expect(token3Data.ConstData.toString()).to.be.equal('0x33');
+
+ expect(token1Data.VariableData.toString()).to.be.equal('0x31');
+ expect(token2Data.VariableData.toString()).to.be.equal('0x32');
+ expect(token3Data.VariableData.toString()).to.be.equal('0x33');
});
});
- it('(!negative test!) Create two ReFungible tokens in active NFT collection', async () => {
- await usingApi(async (api) => {
- const AitemListIndex = await api.query.nft.itemListIndex(idCollection);
- console.log(`itemListIndex count (before): ${AitemListIndex}`);
- const args = ['ReFungible', 'ReFungible'];
- const alicePrivateKey = privateKey('//Alice');
- const createMultipleItems = await api.tx.nft
- .createMultipleItems(idCollection, alicesPublicKey, args)
- .signAndSend(alicePrivateKey);
- // tslint:disable-next-line: no-unused-expression
- assert.exists(createMultipleItems, 'createMultipleItems is neither `null` or `undefined`');
- console.log(`Ext. createMultipleItems submitted with hash: ${createMultipleItems}`);
- await waitNewBlocks(api);
- const BitemListIndex = await api.query.nft.itemListIndex(idCollection);
- console.log(`itemListIndex count (after): ${BitemListIndex}`);
- if (BitemListIndex > AitemListIndex) { assert.fail('Incorrect token added in collection!'); }
+});
+
+describe('Negative Integration Test createMultipleItems(collection_id, owner, items_data):', () => {
+ it('Create token with not existing type', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const Alice = privateKey('//Alice');
+ try {
+ const args = [{ invalid: null }, { invalid: null }, { invalid: null }];
+ const createMultipleItemsTx = await api.tx.nft
+ .createMultipleItems(collectionId, Alice.address, args);
+ await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
+ } catch (e) {
+ // tslint:disable-next-line:no-unused-expression
+ expect(e).to.be.exist;
+ }
+ });
+ });
+
+ it('Create token in not existing collection', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ const Alice = privateKey('//Alice');
+ const createMultipleItemsTx = await api.tx.nft
+ .createMultipleItems(collectionId, Alice.address, ['NFT', 'NFT', 'NFT']);
+ await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
+ });
+ });
+
+ it('Create NFT and Re-fungible tokens that has reached the maximum data limit', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ // NFT
+ const collectionId = await createCollectionExpectSuccess();
+ const Alice = privateKey('//Alice');
+ const args = [
+ { nft: ['A'.repeat(2049), 'A'.repeat(2049)] },
+ { nft: ['B'.repeat(2049), 'B'.repeat(2049)] },
+ { nft: ['C'.repeat(2049), 'C'.repeat(2049)] },
+ ];
+ const createMultipleItemsTx = await api.tx.nft
+ .createMultipleItems(collectionId, Alice.address, args);
+ await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
+
+ // ReFungible
+ const collectionIdReFungible =
+ await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
+ const argsReFungible = [
+ { ReFungible: ['1'.repeat(2049), '1'.repeat(2049)] },
+ { ReFungible: ['2'.repeat(2049), '2'.repeat(2049)] },
+ { ReFungible: ['3'.repeat(2049), '3'.repeat(2049)] },
+ ];
+ const createMultipleItemsTxFungible = await api.tx.nft
+ .createMultipleItems(collectionIdReFungible, Alice.address, argsReFungible);
+ await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTxFungible)).to.be.rejected;
+ });
+ });
+
+ it('Create tokens with different types', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const Alice = privateKey('//Alice');
+ const createMultipleItemsTx = await api.tx.nft
+ .createMultipleItems(collectionId, Alice.address, ['NFT', 'Fungible', 'ReFungible']);
+ await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
+ // garbage collection :-D
+ await destroyCollectionExpectSuccess(collectionId);
+ });
+ });
+
+ it('Create tokens with different data limits <> maximum data limit', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const Alice = privateKey('//Alice');
+ const args = [
+ { nft: ['A', 'A'] },
+ { nft: ['B', 'B'.repeat(2049)] },
+ { nft: ['C'.repeat(2049), 'C'] },
+ ];
+ const createMultipleItemsTx = await api.tx.nft
+ .createMultipleItems(collectionId, Alice.address, args);
+ await expect(submitTransactionExpectFailAsync(Alice, createMultipleItemsTx)).to.be.rejected;
});
});
});
tests/src/destroyCollection.test.tsdiffbeforeafterboth--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,12 +1,14 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
-import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
-import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
-import privateKey from './substrate/privateKey';
+import { default as usingApi } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
chai.use(chaiAsPromised);
-const expect = chai.expect;
describe('integration test: ext. destroyCollection():', () => {
it('NFT collection can be destroyed', async () => {
@@ -18,7 +20,7 @@
await destroyCollectionExpectSuccess(collectionId);
});
it('ReFungible collection can be destroyed', async () => {
- const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
await destroyCollectionExpectSuccess(collectionId);
});
});
tests/src/enableContractSponsoring.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/enableContractSponsoring.test.ts
@@ -0,0 +1,89 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import { deployFlipper, getFlipValue, toggleFlipValueExpectSuccess } from './util/contracthelpers';
+import {
+ enableContractSponsoringExpectFailure,
+ enableContractSponsoringExpectSuccess,
+ findUnusedAddress,
+ setContractSponsoringRateLimitExpectSuccess,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Integration Test enableContractSponsoring', () => {
+ it('ensure tx fee is paid from endowment', async () => {
+ await usingApi(async (api) => {
+ const user = await findUnusedAddress(api);
+
+ const [flipper, deployer] = await deployFlipper(api);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 1);
+ await toggleFlipValueExpectSuccess(user, flipper);
+
+ expect(await getFlipValue(flipper, deployer)).to.be.false;
+ });
+ });
+
+ it('ensure it can be enabled twice', async () => {
+ await usingApi(async (api) => {
+ const [flipper, deployer] = await deployFlipper(api);
+
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ });
+ });
+
+ it('ensure it can be disabled twice', async () => {
+ await usingApi(async (api) => {
+ const [flipper, deployer] = await deployFlipper(api);
+
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, false);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, false);
+ });
+ });
+
+ it('ensure it can be re-enabled', async () => {
+ await usingApi(async (api) => {
+ const [flipper, deployer] = await deployFlipper(api);
+
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, false);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ });
+ });
+
+});
+
+describe('Negative Integration Test enableContractSponsoring', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ alice = privateKey('//Alice');
+ });
+
+ it('fails when called for non-contract address', async () => {
+ await usingApi(async (api) => {
+ const user = await findUnusedAddress(api);
+
+ await enableContractSponsoringExpectFailure(alice, user.address, true);
+ });
+ });
+
+ it('fails when called by non-owning user', async () => {
+ await usingApi(async (api) => {
+ const [flipper, _] = await deployFlipper(api);
+
+ await enableContractSponsoringExpectFailure(alice, flipper.address, true);
+ });
+ });
+});
tests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -1,5 +1,9 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import { ApiPromise } from '@polkadot/api';
-import BN from 'bn.js';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import privateKey from './substrate/privateKey';
tests/src/removeFromWhiteList.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/removeFromWhiteList.test.ts
@@ -0,0 +1,89 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi } from './substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+ enableWhiteListExpectSuccess,
+ addToWhiteListExpectSuccess,
+ removeFromWhiteListExpectSuccess,
+ isWhitelisted,
+ findNotExistingCollection,
+ removeFromWhiteListExpectFailure,
+ disableWhiteListExpectSuccess,
+} from './util/helpers';
+import { IKeyringPair } from '@polkadot/types/types';
+import privateKey from './substrate/privateKey';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Integration Test removeFromWhiteList', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async (api) => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+ });
+
+ it('ensure bob is not in whitelist after removal', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await enableWhiteListExpectSuccess(alice, collectionId);
+ await addToWhiteListExpectSuccess(alice, collectionId, bob.address);
+
+ await removeFromWhiteListExpectSuccess(alice, collectionId, bob.address);
+ expect(await isWhitelisted(collectionId, bob.address)).to.be.false;
+ });
+ });
+
+ it('allows removal from collection with unset whitelist status', async () => {
+ await usingApi(async () => {
+ const collectionWithoutWhitelistId = await createCollectionExpectSuccess();
+ await enableWhiteListExpectSuccess(alice, collectionWithoutWhitelistId);
+ await addToWhiteListExpectSuccess(alice, collectionWithoutWhitelistId, bob.address);
+ await disableWhiteListExpectSuccess(alice, collectionWithoutWhitelistId);
+
+ await removeFromWhiteListExpectSuccess(alice, collectionWithoutWhitelistId, bob.address);
+ });
+ });
+});
+
+describe('Negative Integration Test removeFromWhiteList', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async (api) => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+ });
+
+ it('fails on removal from not existing collection', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await findNotExistingCollection(api);
+
+ await removeFromWhiteListExpectFailure(alice, collectionId, bob.address);
+ });
+ });
+
+ it('fails on removal from removed collection', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await enableWhiteListExpectSuccess(alice, collectionId);
+ await addToWhiteListExpectSuccess(alice, collectionId, bob.address);
+ await destroyCollectionExpectSuccess(collectionId);
+
+ await removeFromWhiteListExpectFailure(alice, collectionId, bob.address);
+ });
+ });
+});
tests/src/setCollectionLimits.test.tsdiffbeforeafterboth--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
// https://unique-network.readthedocs.io/en/latest/jsapi.html#setchainlimits
import { ApiPromise, Keyring } from '@polkadot/api';
import { IKeyringPair } from '@polkadot/types/types';
tests/src/setCollectionSponsor.test.tsdiffbeforeafterboth--- a/tests/src/setCollectionSponsor.test.ts
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -9,7 +9,6 @@
import { createCollectionExpectSuccess, setCollectionSponsorExpectSuccess, destroyCollectionExpectSuccess, setCollectionSponsorExpectFailure } from "./util/helpers";
import { Keyring } from "@polkadot/api";
import { IKeyringPair } from "@polkadot/types/types";
-import type { AccountId } from '@polkadot/types/interfaces';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -34,7 +33,7 @@
await setCollectionSponsorExpectSuccess(collectionId, bob.address);
});
it('Set ReFungible collection sponsor', async () => {
- const collectionId = await createCollectionExpectSuccess({ mode: {type: 'ReFungible', decimalPoints: 0} });
+ const collectionId = await createCollectionExpectSuccess({ mode: {type: 'ReFungible'} });
await setCollectionSponsorExpectSuccess(collectionId, bob.address);
});
tests/src/setConstOnChainSchema.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setConstOnChainSchema.test.ts
@@ -0,0 +1,96 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { Keyring } from '@polkadot/api';
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let Alice: IKeyringPair;
+let Bob: IKeyringPair;
+let Shema: any;
+let largeShema: any;
+
+before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ Alice = keyring.addFromUri('//Alice');
+ Bob = keyring.addFromUri('//Bob');
+ Shema = '0x31';
+ largeShema = new Array(4097).fill(0xff);
+
+ });
+});
+describe('Integration Test ext. setConstOnChainSchema()', () => {
+
+ it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const collection: any = (await api.query.nft.collection(collectionId));
+ expect(collection.Owner.toString()).to.be.eq(Alice.address);
+ const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
+ await submitTransactionAsync(Alice, setShema);
+ });
+ });
+
+ it('Checking collection data using the ConstOnChainSchema parameter', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
+ await submitTransactionAsync(Alice, setShema);
+ const collection: any = (await api.query.nft.collection(collectionId));
+ expect(collection.ConstOnChainSchema.toString()).to.be.eq(Shema);
+
+ });
+ });
+});
+
+describe('Negative Integration Test ext. setConstOnChainSchema()', () => {
+
+ it('Set a non-existent collection', async () => {
+ await usingApi(async (api) => {
+ // tslint:disable-next-line: radix
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
+ await expect(submitTransactionExpectFailAsync(Alice, setShema)).to.be.rejected;
+ });
+ });
+
+ it('Set a previously deleted collection', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ await destroyCollectionExpectSuccess(collectionId);
+ const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
+ await expect(submitTransactionExpectFailAsync(Alice, setShema)).to.be.rejected;
+ });
+ });
+
+ it('Set invalid data in schema (size too large:> 1024b)', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const setShema = api.tx.nft.setConstOnChainSchema(collectionId, largeShema);
+ await expect(submitTransactionExpectFailAsync(Alice, setShema)).to.be.rejected;
+ });
+ });
+
+ it('Execute method not on behalf of the collection owner', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const collection: any = (await api.query.nft.collection(collectionId));
+ expect(collection.Owner.toString()).to.be.eq(Alice.address);
+ const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
+ await expect(submitTransactionExpectFailAsync(Bob, setShema)).to.be.rejected;
+ });
+ });
+
+});
tests/src/setContractSponsoringRateLimit.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setContractSponsoringRateLimit.test.ts
@@ -0,0 +1,67 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import waitNewBlocks from './substrate/wait-new-blocks';
+import { deployFlipper, toggleFlipValueExpectFailure, toggleFlipValueExpectSuccess } from './util/contracthelpers';
+import {
+ enableContractSponsoringExpectSuccess,
+ findUnusedAddress,
+ setContractSponsoringRateLimitExpectFailure,
+ setContractSponsoringRateLimitExpectSuccess,
+} from './util/helpers';
+
+describe('Integration Test setContractSponsoringRateLimit', () => {
+ it('ensure sponsored contract can\'t be called twice without pause for free', async () => {
+ await usingApi(async (api) => {
+ const user = await findUnusedAddress(api);
+
+ const [flipper, deployer] = await deployFlipper(api);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 10);
+ await toggleFlipValueExpectSuccess(user, flipper);
+ await toggleFlipValueExpectFailure(user, flipper);
+ });
+ });
+
+ it('ensure sponsored contract can be called twice with pause for free', async () => {
+ await usingApi(async (api) => {
+ const user = await findUnusedAddress(api);
+
+ const [flipper, deployer] = await deployFlipper(api);
+ await enableContractSponsoringExpectSuccess(deployer, flipper.address, true);
+ await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 1);
+ await toggleFlipValueExpectSuccess(user, flipper);
+ await waitNewBlocks(api, 1);
+ await toggleFlipValueExpectSuccess(user, flipper);
+ });
+ });
+});
+
+describe('Negative Integration Test setContractSponsoringRateLimit', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ alice = privateKey('//Alice');
+ });
+
+ it('fails when called for non-contract address', async () => {
+ await usingApi(async (api) => {
+ const user = await findUnusedAddress(api);
+
+ await setContractSponsoringRateLimitExpectFailure(alice, user.address, 1);
+ });
+ });
+
+ it('fails when called by non-owning user', async () => {
+ await usingApi(async (api) => {
+ const [flipper, _] = await deployFlipper(api);
+
+ await setContractSponsoringRateLimitExpectFailure(alice, flipper.address, 1);
+ });
+ });
+});
tests/src/setMintPermission.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setMintPermission.test.ts
@@ -0,0 +1,103 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+ addToWhiteListExpectSuccess,
+ createCollectionExpectSuccess,
+ createItemExpectFailure,
+ createItemExpectSuccess,
+ destroyCollectionExpectSuccess,
+ enableWhiteListExpectSuccess,
+ findNotExistingCollection,
+ setMintPermissionExpectFailure,
+ setMintPermissionExpectSuccess,
+} from './util/helpers';
+
+describe('Integration Test setMintPermission', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+ });
+
+ it('ensure white-listed non-privileged address can mint tokens', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await enableWhiteListExpectSuccess(alice, collectionId);
+ await setMintPermissionExpectSuccess(alice, collectionId, true);
+ await addToWhiteListExpectSuccess(alice, collectionId, bob.address);
+
+ await createItemExpectSuccess(bob, collectionId, 'NFT');
+ });
+ });
+
+ it('can be enabled twice', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setMintPermissionExpectSuccess(alice, collectionId, true);
+ await setMintPermissionExpectSuccess(alice, collectionId, true);
+ });
+ });
+
+ it('can be disabled twice', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await setMintPermissionExpectSuccess(alice, collectionId, true);
+ await setMintPermissionExpectSuccess(alice, collectionId, false);
+ await setMintPermissionExpectSuccess(alice, collectionId, false);
+ });
+ });
+});
+
+describe('Negative Integration Test setMintPermission', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+ });
+ });
+
+ it('fails on not existing collection', async () => {
+ await usingApi(async (api) => {
+ const nonExistingCollection = await findNotExistingCollection(api);
+ await setMintPermissionExpectFailure(alice, nonExistingCollection, true);
+ });
+ });
+
+ it('fails on removed collection', async () => {
+ await usingApi(async () => {
+ const removedCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await destroyCollectionExpectSuccess(removedCollectionId);
+
+ await setMintPermissionExpectFailure(alice, removedCollectionId, true);
+ });
+ });
+
+ it('fails when not collection owner tries to set mint status', async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await enableWhiteListExpectSuccess(alice, collectionId);
+ await setMintPermissionExpectFailure(bob, collectionId, true);
+ });
+
+ it('ensure non-white-listed non-privileged address can\'t mint tokens', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ await enableWhiteListExpectSuccess(alice, collectionId);
+ await setMintPermissionExpectSuccess(alice, collectionId, true);
+
+ await createItemExpectFailure(bob, collectionId, 'NFT');
+ });
+ });
+});
tests/src/setPublicAccessMode.test.tsdiffbeforeafterboth--- a/tests/src/setPublicAccessMode.test.ts
+++ b/tests/src/setPublicAccessMode.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
// https://unique-network.readthedocs.io/en/latest/jsapi.html#setschemaversion
import { ApiPromise, Keyring } from '@polkadot/api';
import { IKeyringPair } from '@polkadot/types/types';
tests/src/setSchemaVersion.test.tsdiffbeforeafterboth--- a/tests/src/setSchemaVersion.test.ts
+++ b/tests/src/setSchemaVersion.test.ts
@@ -1,7 +1,11 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
// https://unique-network.readthedocs.io/en/latest/jsapi.html#setschemaversion
import { ApiPromise, Keyring } from '@polkadot/api';
import { IKeyringPair } from '@polkadot/types/types';
-import BN from 'bn.js';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import usingApi, {submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api';
@@ -98,12 +102,16 @@
it('execute setSchemaVersion with not correct schema version', async () => {
await usingApi(async (api: ApiPromise) => {
+ const consoleError = console.error;
+ console.error = (message: string) => {};
try {
tx = api.tx.nft.setSchemaVersion(collectionIdForTesting, 'Test');
await submitTransactionAsync(alice, tx);
} catch (e) {
// tslint:disable-next-line:no-unused-expression
expect(e).to.be.exist;
+ } finally {
+ console.error = consoleError;
}
});
});
tests/src/setVariableMetaData.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setVariableMetaData.test.ts
@@ -0,0 +1,109 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+ burnItemExpectSuccess,
+ createCollectionExpectSuccess,
+ createItemExpectSuccess,
+ destroyCollectionExpectSuccess,
+ findNotExistingCollection,
+ setVariableMetaDataExpectFailure,
+ setVariableMetaDataExpectSuccess,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Integration Test setVariableMetaData', () => {
+ const data = [1, 2, 254, 255];
+
+ let alice: IKeyringPair;
+ let collectionId: number;
+ let tokenId: number;
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ collectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');
+ });
+ });
+
+ it('execute setVariableMetaData', async () => {
+ await setVariableMetaDataExpectSuccess(alice, collectionId, tokenId, data);
+ });
+
+ it('verify data was set', async () => {
+ await usingApi(async api => {
+ const item: any = await api.query.nft.nftItemList(collectionId, tokenId);
+
+ expect(Array.from(item.VariableData)).to.deep.equal(Array.from(data));
+ });
+ });
+});
+
+describe('Negative Integration Test setVariableMetaData', () => {
+ let data = [1];
+
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+
+ let validCollectionId: number;
+ let validTokenId: number;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ bob = privateKey('//Bob');
+
+ validCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ validTokenId = await createItemExpectSuccess(alice, validCollectionId, 'NFT');
+ });
+ });
+
+ it('fails on not existing collection id', async () => {
+ await usingApi(async api => {
+ let nonExistingCollectionId = await findNotExistingCollection(api);
+ await setVariableMetaDataExpectFailure(alice, nonExistingCollectionId, 1, data);
+ });
+ });
+ it('fails on removed collection id', async () => {
+ const removedCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ const removedCollectionTokenId = await createItemExpectSuccess(alice, removedCollectionId, 'NFT');
+
+ await destroyCollectionExpectSuccess(removedCollectionId);
+ await setVariableMetaDataExpectFailure(alice, removedCollectionId, removedCollectionTokenId, data);
+ });
+ it('fails on removed token', async () => {
+ const removedTokenCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+ const removedTokenId = await createItemExpectSuccess(alice, removedTokenCollectionId, 'NFT');
+ await burnItemExpectSuccess(alice, removedTokenCollectionId, removedTokenId);
+
+ await setVariableMetaDataExpectFailure(alice, removedTokenCollectionId, removedTokenId, data);
+ });
+ it('fails on not existing token', async () => {
+ const nonExistingTokenId = validTokenId + 1;
+
+ await setVariableMetaDataExpectFailure(alice, validCollectionId, nonExistingTokenId, data);
+ });
+ it('fails on too long data', async () => {
+ const tooLongData = new Array(4097).fill(0xff);
+
+ await setVariableMetaDataExpectFailure(alice, validCollectionId, validTokenId, tooLongData);
+ });
+ it('fails on fungible token', async () => {
+ const fungibleCollectionId = await createCollectionExpectSuccess({ mode: { type: 'Fungible', decimalPoints: 0 } });
+ const fungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible');
+
+ await setVariableMetaDataExpectFailure(alice, fungibleCollectionId, fungibleTokenId, data);
+ });
+ it('fails on bad sender', async () => {
+ await setVariableMetaDataExpectFailure(bob, validCollectionId, validTokenId, data);
+ });
+});
tests/src/setVariableOnChainSchema.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setVariableOnChainSchema.test.ts
@@ -0,0 +1,96 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { Keyring } from '@polkadot/api';
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let Alice: IKeyringPair;
+let Bob: IKeyringPair;
+let Schema: any;
+let largeSchema: any;
+
+before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ Alice = keyring.addFromUri('//Alice');
+ Bob = keyring.addFromUri('//Bob');
+ Schema = '0x31';
+ largeSchema = new Array(4097).fill(0xff);
+
+ });
+});
+describe('Integration Test ext. setVariableOnChainSchema()', () => {
+
+ it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const collection: any = (await api.query.nft.collection(collectionId));
+ expect(collection.Owner.toString()).to.be.eq(Alice.address);
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await submitTransactionAsync(Alice, setSchema);
+ });
+ });
+
+ it('Checking collection data using the setVariableOnChainSchema parameter', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await submitTransactionAsync(Alice, setSchema);
+ const collection: any = (await api.query.nft.collection(collectionId));
+ expect(collection.VariableOnChainSchema.toString()).to.be.eq(Schema);
+
+ });
+ });
+});
+
+describe('Negative Integration Test ext. setVariableOnChainSchema()', () => {
+
+ it('Set a non-existent collection', async () => {
+ await usingApi(async (api) => {
+ // tslint:disable-next-line: radix
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
+ });
+ });
+
+ it('Set a previously deleted collection', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ await destroyCollectionExpectSuccess(collectionId);
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
+ });
+ });
+
+ it('Set invalid data in schema (size too large:> 1024b)', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, largeSchema);
+ await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
+ });
+ });
+
+ it('Execute method not on behalf of the collection owner', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const collection: any = (await api.query.nft.collection(collectionId));
+ expect(collection.Owner.toString()).to.be.eq(Alice.address);
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await expect(submitTransactionExpectFailAsync(Bob, setSchema)).to.be.rejected;
+ });
+ });
+
+});
tests/src/substrate/substrate-api.tsdiffbeforeafterboth--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -24,12 +24,17 @@
// TODO: Remove, this is temporary: Filter unneeded API output
// (Jaco promised it will be removed in the next version)
- // const consoleLog = console.log;
- // console.log = (message: string) => {
- // if (!(message.includes("API/INIT: Capabilities detected") || message.includes("2021-"))) {
- // consoleLog(message);
- // }
- // };
+ const consoleLog = console.log;
+ console.log = (message: string) => {
+ if (message.includes("API/INIT: Capabilities detected") || message.includes("2021-")) {}
+ else if (message.includes("StorageChangeSet:: WebSocket is not connected") || message.includes("2021-")) {}
+ else consoleLog(message);
+ };
+ const consoleErr = console.error;
+ console.error = (message: string) => {
+ if (message.includes("StorageChangeSet:: WebSocket is not connected") || message.includes("2021-")) {}
+ else consoleErr(message);
+ };
try {
await promisifySubstrate(api, async () => {
@@ -40,7 +45,8 @@
})();
} finally {
await api.disconnect();
- // console.log = consoleLog;
+ console.log = consoleLog;
+ console.error = consoleErr;
}
}
@@ -115,11 +121,11 @@
await transaction.signAndSend(sender, ({ events = [], status }) => {
const transactionStatus = getTransactionStatus(events, status);
- console.log('transactionStatus', transactionStatus, 'events', events);
+ // console.log('transactionStatus', transactionStatus, 'events', events);
- if (transactionStatus == TransactionStatus.Success) {
+ if (transactionStatus === TransactionStatus.Success) {
resolve(events);
- } else if (transactionStatus == TransactionStatus.Fail) {
+ } else if (transactionStatus === TransactionStatus.Fail) {
reject(events);
}
});
tests/src/toggleContractWhiteList.test.tsdiffbeforeafterboth--- a/tests/src/toggleContractWhiteList.test.ts
+++ b/tests/src/toggleContractWhiteList.test.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import chai from "chai";
import chaiAsPromised from 'chai-as-promised';
import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
tests/src/transfer.test.tsdiffbeforeafterboth--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -63,7 +63,7 @@
});
});
- it('Create collection, balance transfers and check balance', async () => {
+ it('User can transfer owned token', async () => {
await usingApi(async (api) => {
const Alice = privateKey('//Alice');
const Bob = privateKey('//Bob');
@@ -77,10 +77,10 @@
await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 1, 'Fungible');
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await transferExpectSuccess(reFungibleCollectionId,
- newReFungibleTokenId, Alice, Bob, 1, 'ReFungible');
+ newReFungibleTokenId, Alice, Bob, 100, 'ReFungible');
});
});
});
@@ -119,7 +119,7 @@
await transferExpectFail(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 1, 'Fungible');
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await destroyCollectionExpectSuccess(reFungibleCollectionId);
await transferExpectFail(reFungibleCollectionId,
@@ -134,7 +134,7 @@
await transferExpectFail(fungibleCollectionId, 2, Alice, Bob, 1, 'Fungible');
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
await transferExpectFail(reFungibleCollectionId,
2, Alice, Bob, 1, 'ReFungible');
});
@@ -151,7 +151,7 @@
await transferExpectFail(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 1, 'Fungible');
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await burnItemExpectSuccess(Alice, reFungibleCollectionId, newReFungibleTokenId, 1);
await transferExpectFail(reFungibleCollectionId,
@@ -168,7 +168,7 @@
await transferExpectFail(fungibleCollectionId, newFungibleTokenId, Charlie, Bob, 1, 'Fungible');
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await transferExpectFail(reFungibleCollectionId,
newReFungibleTokenId, Charlie, Bob, 1, 'ReFungible');
tests/src/transferFrom.test.tsdiffbeforeafterboth--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -41,11 +41,11 @@
await transferFromExpectSuccess(fungibleCollectionId, newFungibleTokenId, Bob, Alice, Charlie, 1, 'Fungible');
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
- await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob);
+ await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 100);
await transferFromExpectSuccess(reFungibleCollectionId,
- newReFungibleTokenId, Bob, Alice, Charlie, 1, 'ReFungible');
+ newReFungibleTokenId, Bob, Alice, Charlie, 100, 'ReFungible');
});
});
});
@@ -110,7 +110,7 @@
await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, Bob, Alice, Charlie, 1);
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await transferFromExpectFail(reFungibleCollectionId,
newReFungibleTokenId, Bob, Alice, Charlie, 1);
@@ -136,7 +136,7 @@
await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, Bob, Alice, Charlie, 2);
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob);
await transferFromExpectFail(reFungibleCollectionId,
@@ -149,7 +149,7 @@
const Alice = privateKey('//Alice');
const Bob = privateKey('//Bob');
const Charlie = privateKey('//Charlie');
- const Dave = privateKey('//DAVE');
+ const Dave = privateKey('//Dave');
// nft
const nftCollectionId = await createCollectionExpectSuccess();
const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
@@ -175,7 +175,7 @@
}
// reFungible
const reFungibleCollectionId = await
- createCollectionExpectSuccess({mode: {type: 'ReFungible', decimalPoints: 0}});
+ createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible');
try {
await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, Dave, Bob);
tests/src/types.tsdiffbeforeafterboth--- a/tests/src/types.ts
+++ b/tests/src/types.ts
@@ -1,3 +1,8 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
import BN from 'bn.js';
export interface ICollectionInterface {
tests/src/util/contracthelpers.tsdiffbeforeafterboth--- a/tests/src/util/contracthelpers.ts
+++ b/tests/src/util/contracthelpers.ts
@@ -5,7 +5,7 @@
import chai from "chai";
import chaiAsPromised from 'chai-as-promised';
-import { submitTransactionAsync } from "../substrate/substrate-api";
+import { submitTransactionAsync, submitTransactionExpectFailAsync } from "../substrate/substrate-api";
import fs from "fs";
import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";
import { IKeyringPair } from "@polkadot/types/types";
@@ -14,7 +14,7 @@
chai.use(chaiAsPromised);
const expect = chai.expect;
import { BigNumber } from 'bignumber.js';
-import { findUnusedAddress } from '../util/helpers';
+import { findUnusedAddress, getGenericResult } from '../util/helpers';
const value = 0;
const gasLimit = '200000000000';
@@ -92,6 +92,19 @@
return (result.result.asOk.data[0] == 0x00) ? false : true;
}
+export async function toggleFlipValueExpectSuccess(sender: IKeyringPair, contract: Contract) {
+ const tx = contract.tx.flip(value, gasLimit);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.true;
+}
+
+export async function toggleFlipValueExpectFailure(sender: IKeyringPair, contract: Contract) {
+ const tx = contract.tx.flip(value, gasLimit);
+ await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+}
+
function instantiateTransferContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {
return new Promise<any>(async (resolve, reject) => {
const unsub = await blueprint.tx
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -51,7 +51,7 @@
Value: BN;
}
-interface IReFungibleTokenDataType {
+export interface IReFungibleTokenDataType {
Owner: IReFungibleOwner[];
ConstData: number[];
VariableData: number[];
@@ -124,21 +124,6 @@
interface ReFungible {
type: 'ReFungible';
- decimalPoints: number;
-}
-
-interface Nft {
- type: 'NFT'
-}
-
-interface Fungible {
- type: 'Fungible',
- decimalPoints: number
-}
-
-interface ReFungible {
- type: 'ReFungible',
- decimalPoints: number
}
type CollectionMode = Nft | Fungible | ReFungible | Invalid;
@@ -174,7 +159,7 @@
} else if (mode.type === 'Fungible') {
modeprm = {fungible: mode.decimalPoints};
} else if (mode.type === 'ReFungible') {
- modeprm = {refungible: mode.decimalPoints};
+ modeprm = {refungible: null};
} else if (mode.type === 'Invalid') {
modeprm = {invalid: null};
}
@@ -216,7 +201,7 @@
} else if (mode.type === 'Fungible') {
modeprm = {fungible: mode.decimalPoints};
} else if (mode.type === 'ReFungible') {
- modeprm = {refungible: mode.decimalPoints};
+ modeprm = {refungible: null};
} else if (mode.type === 'Invalid') {
modeprm = {invalid: null};
}
@@ -253,6 +238,12 @@
return unused;
}
+export async function findNotExistingCollection(api: ApiPromise): Promise<number> {
+ const totalNumber = parseInt((await api.query.nft.createdCollectionCount()).toString(), 10) as unknown as number;
+ const newCollection: number = totalNumber + 1;
+ return newCollection;
+}
+
function getDestroyResult(events: EventRecord[]): boolean {
let success: boolean = false;
events.forEach(({ phase, event: { data, method, section } }) => {
@@ -379,6 +370,63 @@
});
}
+export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) {
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.enableContractSponsoring(contractAddress, enable);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.true;
+ });
+}
+
+export async function enableContractSponsoringExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) {
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.enableContractSponsoring(contractAddress, enable);
+ const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.false;
+ });
+}
+
+export async function setContractSponsoringRateLimitExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) {
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.setContractSponsoringRateLimit(contractAddress, rateLimit);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.true;
+ });
+}
+
+export async function setContractSponsoringRateLimitExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) {
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.setContractSponsoringRateLimit(contractAddress, rateLimit);
+ const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.false;
+ });
+}
+
+export async function setVariableMetaDataExpectSuccess(sender: IKeyringPair, collectionId: number, itemId: number, data: number[]) {
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.setVariableMetaData(collectionId, itemId, '0x' + Buffer.from(data).toString('hex'));
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ expect(result.success).to.be.true;
+ });
+}
+
+export async function setVariableMetaDataExpectFailure(sender: IKeyringPair, collectionId: number, itemId: number, data: number[]) {
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.setVariableMetaData(collectionId, itemId, '0x' + Buffer.from(data).toString('hex'));
+ await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ });
+}
+
export interface CreateFungibleData extends Struct {
readonly value: u128;
}
@@ -440,7 +488,7 @@
}
const transferFromTx = await api.tx.nft.transferFrom(
accountFrom.address, accountTo.address, collectionId, tokenId, value);
- const events = await submitTransactionAsync(accountFrom, transferFromTx);
+ const events = await submitTransactionAsync(accountApproved, transferFromTx);
const result = getCreateItemResult(events);
// tslint:disable-next-line:no-unused-expression
expect(result.success).to.be.true;
@@ -558,6 +606,9 @@
if (createMode === 'Fungible') {
const createData = {fungible: {value: 10}};
tx = api.tx.nft.createItem(collectionId, owner, createData);
+ } else if (createMode === 'ReFungible') {
+ const createData = {refungible: {const_data: [], variable_data: [], pieces: 100}};
+ tx = api.tx.nft.createItem(collectionId, owner, createData);
} else {
tx = api.tx.nft.createItem(collectionId, owner, createMode);
}
@@ -583,11 +634,25 @@
return newItemId;
}
-export async function enableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) {
+export async function createItemExpectFailure(
+ sender: IKeyringPair, collectionId: number, createMode: string, owner: string = sender.address) {
+ await usingApi(async (api) => {
+ const tx = api.tx.nft.createItem(collectionId, owner, createMode);
+ const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ const result = getCreateItemResult(events);
+
+ expect(result.success).to.be.false;
+ });
+}
+
+export async function setPublicAccessModeExpectSuccess(
+ sender: IKeyringPair, collectionId: number,
+ accessMode: 'Normal' | 'WhiteList',
+) {
await usingApi(async (api) => {
// Run the transaction
- const tx = api.tx.nft.setPublicAccessMode(collectionId, 'WhiteList');
+ const tx = api.tx.nft.setPublicAccessMode(collectionId, accessMode);
const events = await submitTransactionAsync(sender, tx);
const result = getGenericResult(events);
@@ -597,15 +662,23 @@
// What to expect
// tslint:disable-next-line:no-unused-expression
expect(result.success).to.be.true;
- expect(collection.Access).to.be.equal('WhiteList');
+ expect(collection.Access).to.be.equal(accessMode);
});
}
-export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) {
+export async function enableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) {
+ await setPublicAccessModeExpectSuccess(sender, collectionId, 'WhiteList');
+}
+
+export async function disableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) {
+ await setPublicAccessModeExpectSuccess(sender, collectionId, 'Normal');
+}
+
+export async function setMintPermissionExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) {
await usingApi(async (api) => {
// Run the transaction
- const tx = api.tx.nft.setMintPermission(collectionId, true);
+ const tx = api.tx.nft.setMintPermission(collectionId, enabled);
const events = await submitTransactionAsync(sender, tx);
const result = getGenericResult(events);
@@ -613,9 +686,33 @@
const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
// What to expect
+ // tslint:disable-next-line:no-unused-expression
expect(result.success).to.be.true;
- expect(collection.MintMode).to.be.equal(true);
+ expect(collection.MintMode).to.be.equal(enabled);
+ });
+}
+
+export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) {
+ await setMintPermissionExpectSuccess(sender, collectionId, true);
+}
+
+export async function setMintPermissionExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) {
+ await usingApi(async (api) => {
+ // Run the transaction
+ const tx = api.tx.nft.setMintPermission(collectionId, enabled);
+ const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ const result = getCreateCollectionResult(events);
+ // tslint:disable-next-line:no-unused-expression
+ expect(result.success).to.be.false;
+ });
+}
+
+export async function isWhitelisted(collectionId: number, address: string) {
+ let whitelisted: boolean = false;
+ await usingApi(async (api) => {
+ whitelisted = (await api.query.nft.whiteList(collectionId, address)).toJSON() as unknown as boolean;
});
+ return whitelisted;
}
export async function addToWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) {
@@ -640,6 +737,32 @@
});
}
+export async function removeFromWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) {
+ await usingApi(async (api) => {
+ // Run the transaction
+ const tx = api.tx.nft.removeFromWhiteList(collectionId, address);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ // What to expect
+ // tslint:disable-next-line:no-unused-expression
+ expect(result.success).to.be.true;
+ });
+}
+
+export async function removeFromWhiteListExpectFailure(sender: IKeyringPair, collectionId: number, address: string) {
+ await usingApi(async (api) => {
+ // Run the transaction
+ const tx = api.tx.nft.removeFromWhiteList(collectionId, address);
+ const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ const result = getGenericResult(events);
+
+ // What to expect
+ // tslint:disable-next-line:no-unused-expression
+ expect(result.success).to.be.false;
+ });
+}
+
export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number)
: Promise<ICollectionInterface | null> => {
return await api.query.nft.collection(collectionId) as unknown as ICollectionInterface;