difftreelog
Merge branch 'develop' into feature/contract-sponsoring-modes
in: master
34 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -382,9 +382,9 @@
[[package]]
name = "autocfg"
-version = "1.0.1"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
@@ -634,9 +634,9 @@
[[package]]
name = "block-buffer"
-version = "0.10.1"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b"
+checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array 0.14.5",
]
@@ -1097,9 +1097,9 @@
[[package]]
name = "core-foundation"
-version = "0.9.2"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
@@ -1229,9 +1229,9 @@
[[package]]
name = "crc32fast"
-version = "1.3.1"
+version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if 1.0.0",
]
@@ -1288,9 +1288,9 @@
[[package]]
name = "crypto-common"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0"
+checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
dependencies = [
"generic-array 0.14.5",
]
@@ -1898,13 +1898,12 @@
[[package]]
name = "digest"
-version = "0.10.1"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b"
+checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
dependencies = [
- "block-buffer 0.10.1",
+ "block-buffer 0.10.2",
"crypto-common",
- "generic-array 0.14.5",
]
[[package]]
@@ -3241,9 +3240,9 @@
[[package]]
name = "httparse"
-version = "1.5.1"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
[[package]]
name = "httpdate"
@@ -4664,9 +4663,9 @@
[[package]]
name = "memmap2"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d"
+checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
dependencies = [
"libc",
]
@@ -8805,7 +8804,7 @@
source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.16#4aeb95f7f38fcd519e2628f32f79044a8fef99d5"
dependencies = [
"impl-trait-for-tuples",
- "memmap2 0.5.2",
+ "memmap2 0.5.3",
"parity-scale-codec",
"sc-chain-spec-derive",
"sc-network",
@@ -9912,7 +9911,7 @@
dependencies = [
"cfg-if 1.0.0",
"cpufeatures 0.2.1",
- "digest 0.10.1",
+ "digest 0.10.2",
]
[[package]]
@@ -11644,7 +11643,7 @@
[[package]]
name = "unique-node"
-version = "0.9.14"
+version = "0.9.16"
dependencies = [
"cumulus-client-cli",
"cumulus-client-collator",
Dockerfile-parachaindiffbeforeafterboth--- a/Dockerfile-parachain
+++ b/Dockerfile-parachain
@@ -90,7 +90,7 @@
nvm install v15.5.0 && \
nvm use v15.5.0
-RUN git clone https://github.com/PureStake/polkadot-launch -b jlm-use-para-id-in-specs
+RUN git clone https://github.com/paritytech/polkadot-launch
RUN export NVM_DIR="$HOME/.nvm" && \
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
README.mddiffbeforeafterboth--- a/README.md
+++ b/README.md
@@ -29,26 +29,11 @@
Please see our [walk-thorugh instructions](doc/hackusama_walk_through.md) to try everything out!
-## Hackusama Update
-
-During the Kusama Hackaphon the following changes were made:
-
-- Enabled Smart Contracts Pallet
-- Enabled integration between Smart Contracts and Unique Pallet (required special edition of RC4 Substrate version)
-- Fixed misc. bugs in Unique Pallet
-- Deployed Unique TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
-- New Features:
- - Re-Fungible Token Mode
- - Off-Chain Schema to store token image URLs
- - Alternative economic model
- - Allow Lists and Public Mint Permission
-- Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and Unique Testnet
- Blockchain.
-
## Application Development
If you are building an application that operates NFT tokens, use [this document](doc/application_development.md).
+
## Building
Building Unique chain requires special versions of Rust and toolchain. We don't use the most recent versions of everything
@@ -57,8 +42,8 @@
1. Install Rust:
```bash
+sudo apt-get install git curl libssl-dev llvm pkg-config libclang-dev clang
curl https://sh.rustup.rs -sSf | sh
-sudo apt-get install libssl-dev pkg-config libclang-dev clang
```
2. Remove all installed toolchains with `rustup toolchain list` and `rustup toolchain uninstall <toolchain>`.
@@ -86,111 +71,52 @@
cargo build --release
```
-## Run
-
-You can start a development chain with:
-
-```bash
-cargo run -- --dev
-```
-
-Detailed logs may be shown by running the node with the following environment variables set:
-`RUST_LOG=debug RUST_BACKTRACE=1 cargo run -- --dev`.
+## Building as Parachain locally
-If you want to see the multi-node consensus algorithm in action locally, then you can create a local testnet with two
-validator nodes for Alice and Bob, who are the initial authorities of the genesis chain that have been endowed with
-testnet units. Give each node a name and expose them so they are listed on the Polkadot
-[telemetry site](https://telemetry.polkadot.io/#/Local%20Testnet). You'll need two terminal windows open.
+Note: checkout this project and all related projects (see below) in the sibling folders (both under the same folder)
-We'll start Alice's substrate node first on default TCP port 30333 with her chain database stored locally at
-`/tmp/alice`. The bootnode ID of her node is `QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN`, which is generated from
-the `--node-key` value that we specify below:
+### Polkadot launch utility
-```bash
-cargo run -- \
- --base-path /tmp/alice \
- --chain=local \
- --alice \
- --node-key 0000000000000000000000000000000000000000000000000000000000000001 \
- --telemetry-url ws://telemetry.polkadot.io:1024 \
- --validator
```
-
-In the second terminal, we'll start Bob's substrate node on a different TCP port of 30334, and with his chain database
-stored locally at `/tmp/bob`. We'll specify a value for the `--bootnodes` option that will connect his node to Alice's
-bootnode ID on TCP port 30333:
-
-```bash
-cargo run -- \
- --base-path /tmp/bob \
- --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/QmQZ8TjTqeDj3ciwr93EJ95hxfDsb9pEYDizUAbWpigtQN \
- --chain=local \
- --bob \
- --port 30334 \
- --telemetry-url ws://telemetry.polkadot.io:1024 \
- --validator
+git clone https://github.com/paritytech/polkadot-launch
```
-
-Additional CLI usage options are available and may be shown by running `cargo run -- --help`.
-
-## Building and Running as Parachain locally
-
-Rust toolchain: nightly-2021-11-11
-Note: checkout this project and polkadot project (see below) in the sibling folders (both under the same folder)
### Build relay
```
git clone https://github.com/paritytech/polkadot.git
cd polkadot
-git checkout release-v0.9.9
+git checkout release-v0.9.16
cargo build --release
```
-### Build parachain
+### Build Unique parachain
Run in the root of this project:
```
-cargo --build
+cargo build --release
```
-### Run 4-node Relay
-
-1. Download `rococo-custom-4.json` chain spec here: https://substrate.dev/cumulus-workshop/shared/chainspecs/rococo-custom-4.json
-2. Use these instructions to launch 4 nodes: https://substrate.dev/cumulus-workshop/#/en/2-relay-chain/1-launch
+### Build Acala parachain (optional, full config only)
-Example (Run in polkadot folder. Replace `12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on` with output from Alice node):
```
-./target/release/polkadot --alice --validator --base-path ./cumulus_relay0 --chain rococo-custom-4.json --port 50555 --ws-port 9944
-./target/release/polkadot --bob --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50556 --ws-port 9945
-./target/release/polkadot --charlie --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50557 --ws-port 9946
-./target/release/polkadot --dave --validator --base-path ./cumulus_relay1 --chain rococo-custom-4.json --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWNLAmKcyee3oqSgTMthaQVXaAcXeo8RrGCzMfMVA3B5on --port 50558 --ws-port 9947
-
+git clone https://github.com/AcalaNetwork/Acala
+cd Acala
+git checkout 54db3acd409a0b787f116f20e163a3b24101ce38
+make build-release
```
-3. Export genesis state and runtime wasm from Unique parachain:
+## Running as Parachain locally
-Run from this project root:
```
-./target/release/unique-collator export-genesis-state --parachain-id 2000 > ./resources/para-2000-genesis
-./target/release/unique-collator export-genesis-wasm > ./resources/para-2000-wasm
+./launch-testnet.sh
```
-4. Run two parachain nodes:
-
-Replace `12D3KooWN1ah2bFQSysEFnwZqcmcVpDDR8UedXyo6xfzV1zDNMNg` with Alice or Bob relay ID
-
-Run from this project root:
+Optional, full setup with Acala and Statemint
```
-./target/release/unique-collator --alice --collator --force-authoring --base-path ./tmp/parachain-alice --parachain-id 2000 --port 40333 --ws-port 9844 -- --execution wasm --chain ../polkadot/rococo-custom-4.json --port 30343 --ws-port 9977
-./target/release/unique-collator --bob --collator --force-authoring --parachain-id 2000 --base-path ./tmp/parachain/bob --port 40334 --ws-port 9845 -- --execution wasm --chain ../polkadot/rococo-custom-4.json --port 30344 --ws-port 9978 --bootnodes /ip4/127.0.0.1/tcp/50556/p2p/12D3KooWN1ah2bFQSysEFnwZqcmcVpDDR8UedXyo6xfzV1zDNMNg
+./launch-testnet-full.sh
```
-
-4. Reserve parachain ID as described here: https://substrate.dev/cumulus-workshop/#/en/2-relay-chain/2-reserve
-5. Register parachain in relay as described here: https://substrate.dev/cumulus-workshop/#/en/3-parachains/2-register
-
-
## Run Integration Tests
1. Install all needed dependecies
@@ -202,40 +128,11 @@
2. Run tests
```
yarn test
-```
-
-## Benchmarks
-
-First of all, add rust toolchain and make it default.
-```bash
-rustup target add wasm32-unknown-unknown --toolchain nightly-2021-11-11
-```
-
-Then in "/node/src" run build command below
-```bash
-cargo +nightly-2021-11-11 build --release --features runtime-benchmarks
-```
-
-Run benchmark
-```bash
-target/release/unique-collator benchmark --chain dev --pallet "pallet_unique" --extrinsic "*" --repeat 1
```
-
-## UI custom types
-
-Moved to [runtime_types.json](./runtime_types.json).
-
-## Running Integration Tests
-See [tests/README.md](./tests/README.md).
## Code Formatting
-### Get formatter and linter settings into your branch (if you forked before they were introduced)
-```bash
-git cherry-pick -n 8ff77c21b0d30b2a4648fa35dbf61dfa9d3948a7
-```
-
### Apply formatting and clippy fixes
```bash
cargo clippy
@@ -252,84 +149,57 @@
cd tests && yarn eslint --ext .ts,.js src/
```
-## Re-Enabling Ink! Contracts
-Uncomment following lies:
-1. In node/rpc/Cargo.toml
-```
-# pallet-contracts-rpc = { version = "3.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.9' }
-```
+## Karura token transfer
-2. In node/rpc/src/lib.rs
-```
-// C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber, Hash>,
-...
-// use pallet_contracts_rpc::{Contracts, ContractsApi};
-...
-// io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
+To get started, you need to open inbound and outbound hrmp channels.
+### Next, we need to register our asset at Karura.
```
+assetRegistry -> registerForeignAsset(location, metadata)
+location:
+ V0(X2(Parent, Parachain(PARA_ID)))
+metadata:
+ name OPL
+ symbol OPL
+ decimals 18
+minimalBalance 1
+```
-3. In runtime/Cargo.toml
+### Next, we can send tokens from Opal to Karura:
```
- # 'pallet-contracts/std',
- # 'pallet-contracts-primitives/std',
- # 'pallet-contracts-rpc-runtime-api/std',
- # 'pallet-contract-helpers/std',
-...
- # [dependencies.pallet-contracts]
- # git = 'https://github.com/paritytech/substrate.git'
- # default-features = false
- # branch = 'polkadot-v0.9.9'
- # version = '3.0.0'
+polkadotXcm -> reserveTransferAssets
+dest:
+ V0(X2(Parent, Parachain(<KARURA_PARA_ID>)))
+beneficiary:
+ X1(AccountId(Any, <ACCOUNT>))
+assets:
+ V1(Concrete(0,Here), Fungible(<AMOUNT>))
+feeAssetItem:
+ 0
+weightLimit:
+ <LIMIT>
+```
- # [dependencies.pallet-contracts-primitives]
- # git = 'https://github.com/paritytech/substrate.git'
- # default-features = false
- # branch = 'polkadot-v0.9.9'
- # version = '3.0.0'
+The result will be displayed in ChainState
+tokens -> accounts
- # [dependencies.pallet-contracts-rpc-runtime-api]
- # git = 'https://github.com/paritytech/substrate.git'
- # default-features = false
- # branch = 'polkadot-v0.9.9'
- # version = '3.0.0'
-...
- # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
+### To send tokens from Karura to Opal:
```
+xtokens -> transfer
-4. runtime/src/lib.rs
-```
-// use pallet_contracts::weights::WeightInfo;
-...
-// pub use pallet_timestamp::Call as TimestampCall;
-...
-// mod chain_extension;
-// use crate::chain_extension::{NFTExtension, Imbalance};
-...
-/*
-parameter_types! {
- pub TombstoneDeposit: Balance = deposit(
- ...
-}
-*/
-...
-//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
-...
-// impl pallet_contract_helpers::Config for Runtime {}
-...
-// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
-...
-// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage},
-...
-//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
-...
-/*
- impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
- for Runtime
- {
- ...
- }
-*/
+currencyId:
+ ForeingAsset
+ <TOKEN_ID>
-```
+amount:
+ <AMOUNT>
+dest:
+ V1
+ (
+ Parents:1,
+ X2(Parachain(<KARURA_PARA_ID>), AccountId(Any, <ACCOUNT>)
+ )
+destWeight:
+ <WEIGHT>
+```
\ No newline at end of file
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -4,7 +4,7 @@
use jsonrpc_core::{Error as RpcError, ErrorCode, Result};
use jsonrpc_derive::rpc;
use up_data_structs::{Collection, CollectionId, CollectionStats, TokenId};
-use sp_api::{BlockId, BlockT, ProvideRuntimeApi};
+use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};
use sp_blockchain::HeaderBackend;
use up_rpc::UniqueApi as UniqueRuntimeApi;
@@ -132,7 +132,10 @@
}
macro_rules! pass_method {
- ($method_name:ident($($name:ident: $ty:ty),* $(,)?) -> $result:ty $(=> $mapper:expr)?) => {
+ (
+ $method_name:ident($($name:ident: $ty:ty),* $(,)?) -> $result:ty $(=> $mapper:expr)?
+ $(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*
+ ) => {
fn $method_name(
&self,
$(
@@ -142,8 +145,25 @@
) -> Result<$result> {
let api = self.client.runtime_api();
let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));
+ let _api_version = if let Ok(Some(api_version)) =
+ api.api_version::<dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>>(&at)
+ {
+ api_version
+ } else {
+ // unreachable for our runtime
+ return Err(RpcError {
+ code: ErrorCode::InvalidParams,
+ message: "Api is not available".into(),
+ data: None,
+ })
+ };
- let result = api.$method_name(&at, $($name),*).map_err(|e| RpcError {
+ let result = $(if _api_version < $ver {
+ api.$changed_method_name(&at, $($changed_name),*).map(|r| r.map($fixer))
+ } else)*
+ { api.$method_name(&at, $($name),*) };
+
+ let result = result.map_err(|e| RpcError {
code: ErrorCode::ServerError(Error::RuntimeError.into()),
message: "Unable to query".into(),
data: Some(format!("{:?}", e).into()),
@@ -168,7 +188,10 @@
{
pass_method!(account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>);
pass_method!(token_exists(collection: CollectionId, token: TokenId) -> bool);
- pass_method!(token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>);
+ pass_method!(
+ token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>;
+ changed_in 2, token_owner_before_version_2(collection, token) => |u| Some(u)
+ );
pass_method!(const_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
pass_method!(variable_metadata(collection: CollectionId, token: TokenId) -> Vec<u8>);
pass_method!(collection_tokens(collection: CollectionId) -> u32);
doc/hackusama_update.mddiffbeforeafterboth--- /dev/null
+++ b/doc/hackusama_update.md
@@ -0,0 +1,15 @@
+## Hackusama Update
+
+During the Kusama Hackaphon the following changes were made:
+
+- Enabled Smart Contracts Pallet
+- Enabled integration between Smart Contracts and Unique Pallet (required special edition of RC4 Substrate version)
+- Fixed misc. bugs in Unique Pallet
+- Deployed Unique TestNet. Public node available at wss://unique.usetech.com, custom UI types - see below in this README.
+- New Features:
+ - Re-Fungible Token Mode
+ - Off-Chain Schema to store token image URLs
+ - Alternative economic model
+ - Allow Lists and Public Mint Permission
+- Use example: [SubstraPunks Game](https://github.com/usetech-llc/substrapunks), fully hosted on IPFS and Unique Testnet
+ Blockchain.
\ No newline at end of file
doc/re-enable_ink_contracts.mddiffbeforeafterboth--- /dev/null
+++ b/doc/re-enable_ink_contracts.md
@@ -0,0 +1,81 @@
+## Re-Enabling Ink! Contracts
+
+Uncomment following lies:
+1. In node/rpc/Cargo.toml
+```
+# pallet-contracts-rpc = { version = "3.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.9' }
+```
+
+2. In node/rpc/src/lib.rs
+```
+// C::Api: pallet_contracts_rpc::ContractsRuntimeApi<Block, AccountId, Balance, BlockNumber, Hash>,
+...
+// use pallet_contracts_rpc::{Contracts, ContractsApi};
+...
+// io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone())));
+
+```
+
+3. In runtime/Cargo.toml
+```
+ # 'pallet-contracts/std',
+ # 'pallet-contracts-primitives/std',
+ # 'pallet-contracts-rpc-runtime-api/std',
+ # 'pallet-contract-helpers/std',
+...
+ # [dependencies.pallet-contracts]
+ # git = 'https://github.com/paritytech/substrate.git'
+ # default-features = false
+ # branch = 'polkadot-v0.9.9'
+ # version = '3.0.0'
+
+ # [dependencies.pallet-contracts-primitives]
+ # git = 'https://github.com/paritytech/substrate.git'
+ # default-features = false
+ # branch = 'polkadot-v0.9.9'
+ # version = '3.0.0'
+
+ # [dependencies.pallet-contracts-rpc-runtime-api]
+ # git = 'https://github.com/paritytech/substrate.git'
+ # default-features = false
+ # branch = 'polkadot-v0.9.9'
+ # version = '3.0.0'
+...
+ # pallet-contract-helpers = { path = '../pallets/contract-helpers', default-features = false, version = '0.1.0' }
+```
+
+4. runtime/src/lib.rs
+```
+// use pallet_contracts::weights::WeightInfo;
+...
+// pub use pallet_timestamp::Call as TimestampCall;
+...
+// mod chain_extension;
+// use crate::chain_extension::{NFTExtension, Imbalance};
+...
+/*
+parameter_types! {
+ pub TombstoneDeposit: Balance = deposit(
+ ...
+}
+*/
+...
+//pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
+...
+// impl pallet_contract_helpers::Config for Runtime {}
+...
+// Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
+...
+// ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage},
+...
+//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
+...
+/*
+ impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
+ for Runtime
+ {
+ ...
+ }
+*/
+
+```
\ No newline at end of file
launch-config-full.jsondiffbeforeafterboth--- /dev/null
+++ b/launch-config-full.json
@@ -0,0 +1,135 @@
+{
+ "relaychain": {
+ "bin": "../polkadot/target/release/polkadot",
+ "chain": "rococo-local",
+ "nodes": [
+ {
+ "name": "alice",
+ "wsPort": 9844,
+ "rpcPort": 9843,
+ "port": 30444,
+ "flags": [
+ "-lparachain::candidate_validation=debug",
+ "-lxcm=trace"
+ ]
+ },
+ {
+ "name": "bob",
+ "wsPort": 9855,
+ "rpcPort": 9854,
+ "port": 30555,
+ "flags": [
+ "-lparachain::candidate_validation=debug",
+ "-lxcm=trace"
+ ]
+ },
+ {
+ "name": "charlie",
+ "wsPort": 9866,
+ "rpcPort": 9865,
+ "port": 30666,
+ "flags": [
+ "-lparachain::candidate_validation=debug"
+ ]
+ },
+ {
+ "name": "dave",
+ "wsPort": 9877,
+ "rpcPort": 9876,
+ "port": 30777,
+ "flags": [
+ "-lparachain::candidate_validation=debug"
+ ]
+ },
+ {
+ "name": "eve",
+ "wsPort": 9888,
+ "rpcPort": 9887,
+ "port": 30888,
+ "flags": [
+ "-lparachain::candidate_validation=debug"
+ ]
+ }
+ ],
+ "genesis": {
+ "runtime": {
+ "runtime_genesis_config": {
+ "parachainsConfiguration": {
+ "config": {
+ "validation_upgrade_frequency": 1,
+ "validation_upgrade_delay": 1
+ }
+ }
+ }
+ }
+ }
+ },
+ "parachains": [
+ {
+ "bin": "../unique-chain/target/release/unique-collator",
+ "id": "1000",
+ "balance": "1000000000000000000000000",
+ "nodes": [
+ {
+ "port": 31200,
+ "wsPort": 9944,
+ "rpcPort": 9933,
+ "name": "alice",
+ "flags": [
+ "--rpc-cors=all",
+ "--unsafe-rpc-external",
+ "--unsafe-ws-external",
+ "-lxcm=trace"
+ ]
+ },
+ {
+ "port": 31201,
+ "wsPort": 9945,
+ "rpcPort": 9934,
+ "name": "bob",
+ "flags": [
+ "--rpc-cors=all",
+ "--unsafe-rpc-external",
+ "--unsafe-ws-external",
+ "-lxcm=trace"
+ ]
+ }
+ ]
+ },
+ {
+ "bin": "../Acala/target/release/acala",
+ "id": "2000",
+ "chain": "karura-dev",
+ "balance": "1000000000000000000000",
+ "nodes": [
+ {
+ "wsPort": 9946,
+ "port": 31202,
+ "name": "alice",
+ "flags": [
+ "--rpc-cors=all",
+ "--unsafe-rpc-external",
+ "--unsafe-ws-external",
+ "-lxcm=trace"
+ ]
+ }
+ ]
+ }
+ ],
+ "simpleParachains": [],
+ "hrmpChannels": [
+ {
+ "sender": 2000,
+ "recipient": 1000,
+ "maxCapacity": 8,
+ "maxMessageSize": 512
+ },
+ {
+ "sender": 1000,
+ "recipient": 2000,
+ "maxCapacity": 8,
+ "maxMessageSize": 512
+ }
+ ],
+ "finalization": false
+}
launch-config-westend.jsondiffbeforeafterboth--- a/launch-config-westend.json
+++ /dev/null
@@ -1,141 +0,0 @@
-{
- "relaychain": {
- "bin": "../polkadot/target/release/polkadot",
- "chain": "westend-local",
- "nodes": [
- {
- "name": "alice",
- "wsPort": 9844,
- "rpcPort": 9843,
- "port": 30444,
- "flags": [
- "-lparachain::candidate_validation=debug"
- ]
- },
- {
- "name": "bob",
- "wsPort": 9855,
- "rpcPort": 9854,
- "port": 30555,
- "flags": [
- "-lparachain::candidate_validation=debug"
- ]
- },
- {
- "name": "charlie",
- "wsPort": 9866,
- "rpcPort": 9865,
- "port": 30666,
- "flags": [
- "-lparachain::candidate_validation=debug"
- ]
- },
- {
- "name": "dave",
- "wsPort": 9877,
- "rpcPort": 9876,
- "port": 30777,
- "flags": [
- "-lparachain::candidate_validation=debug"
- ]
- },
- {
- "name": "eve",
- "wsPort": 9888,
- "rpcPort": 9886,
- "port": 30888,
- "flags": [
- "-lparachain::candidate_validation=debug"
- ]
- },
- {
- "name": "ferdie",
- "wsPort": 9897,
- "rpcPort": 9896,
- "port": 30999,
- "flags": [
- "-lparachain::candidate_validation=debug"
- ]
- }
- ],
- "genesis": {
- "runtime": {
- "runtime_genesis_config": {
- "parachainsConfiguration": {
- "config": {
- "validation_upgrade_frequency": 1,
- "validation_upgrade_delay": 1
- }
- }
- }
- }
- }
- },
- "parachains": [
- {
- "bin": "../unique-chain/target/release/unique-collator",
- "chain": "westend-local",
- "balance": "1000000000000000000000000",
- "nodes": [
- {
- "port": 31200,
- "wsPort": 9944,
- "rpcPort": 9933,
- "name": "alice",
- "flags": [
- "--rpc-cors=all",
- "--unsafe-rpc-external",
- "--unsafe-ws-external"
- ]
- },
- {
- "port": 31201,
- "wsPort": 9945,
- "rpcPort": 9934,
- "name": "bob",
- "flags": [
- "--rpc-cors=all",
- "--unsafe-rpc-external",
- "--unsafe-ws-external"
- ]
- },
- {
- "port": 31202,
- "wsPort": 9946,
- "rpcPort": 9935,
- "name": "charlie",
- "flags": [
- "--rpc-cors=all",
- "--unsafe-rpc-external",
- "--unsafe-ws-external"
- ]
- },
- {
- "port": 31203,
- "wsPort": 9947,
- "rpcPort": 9936,
- "name": "dave",
- "flags": [
- "--rpc-cors=all",
- "--unsafe-rpc-external",
- "--unsafe-ws-external"
- ]
- },
- {
- "port": 31204,
- "wsPort": 9948,
- "rpcPort": 9937,
- "name": "eve",
- "flags": [
- "--rpc-cors=all",
- "--unsafe-rpc-external",
- "--unsafe-ws-external"
- ]
- }
- ]
- }
- ],
- "simpleParachains": [],
- "hrmpChannels": [],
- "finalization": false
-}
launch-config.jsondiffbeforeafterboth--- a/launch-config.json
+++ b/launch-config.json
@@ -9,7 +9,8 @@
"rpcPort": 9843,
"port": 30444,
"flags": [
- "-lparachain::candidate_validation=debug"
+ "-lparachain::candidate_validation=debug",
+ "-lxcm=trace"
]
},
{
@@ -18,7 +19,8 @@
"rpcPort": 9854,
"port": 30555,
"flags": [
- "-lparachain::candidate_validation=debug"
+ "-lparachain::candidate_validation=debug",
+ "-lxcm=trace"
]
},
{
@@ -38,6 +40,15 @@
"flags": [
"-lparachain::candidate_validation=debug"
]
+ },
+ {
+ "name": "eve",
+ "wsPort": 9888,
+ "rpcPort": 9887,
+ "port": 30888,
+ "flags": [
+ "-lparachain::candidate_validation=debug"
+ ]
}
],
"genesis": {
@@ -67,7 +78,8 @@
"flags": [
"--rpc-cors=all",
"--unsafe-rpc-external",
- "--unsafe-ws-external"
+ "--unsafe-ws-external",
+ "-lxcm=trace"
]
},
{
@@ -78,7 +90,8 @@
"flags": [
"--rpc-cors=all",
"--unsafe-rpc-external",
- "--unsafe-ws-external"
+ "--unsafe-ws-external",
+ "-lxcm=trace"
]
}
]
launch-test-env.shdiffbeforeafterboth--- a/launch-test-env.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-cp launch-config.json ../polkadot-launch/launch-config.json
-
-cd ../polkadot-launch
-yarn install
-yarn build
-yarn start launch-config.json
launch-testnet-full.shdiffbeforeafterboth--- /dev/null
+++ b/launch-testnet-full.sh
@@ -0,0 +1,6 @@
+cp launch-config-full.json ../polkadot-launch/launch-config-full.json
+
+cd ../polkadot-launch
+yarn install
+yarn build
+yarn start launch-config-full.json
launch-testnet.shdiffbeforeafterboth--- /dev/null
+++ b/launch-testnet.sh
@@ -0,0 +1,6 @@
+cp launch-config.json ../polkadot-launch/launch-config.json
+
+cd ../polkadot-launch
+yarn install
+yarn build
+yarn start launch-config.json
node/cli/Cargo.tomldiffbeforeafterboth--- a/node/cli/Cargo.toml
+++ b/node/cli/Cargo.toml
@@ -257,7 +257,7 @@
license = 'All Rights Reserved'
name = 'unique-node'
repository = 'https://github.com/UniqueNetwork/unique-chain'
-version = '0.9.14'
+version = '0.9.16'
[[bin]]
name = 'unique-collator'
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -14,7 +14,10 @@
use up_data_structs::{
COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,
- TokenId, Weight, WithdrawReasons, CollectionStats, CustomDataLimit,
+ TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,
+ NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,
+ CustomDataLimit, CreateCollectionData, SponsorshipState,
};
pub use pallet::*;
use sp_core::H160;
@@ -285,6 +288,10 @@
TokenVariableDataLimitExceeded,
/// Exceeded max admin count
CollectionAdminCountExceeded,
+ /// Collection limit bounds per collection exceeded
+ CollectionLimitBoundsExceeded,
+ /// Tried to enable permissions which are only permitted to be disabled
+ OwnerPermissionsCantBeReverted,
/// Collection settings not allowing items transferring
TransferNotAllowed,
@@ -300,7 +307,7 @@
/// Item balance not enough.
TokenValueTooLow,
/// Requested value more than approved.
- TokenValueNotEnough,
+ ApprovedValueTooLow,
/// Tried to approve more than owned
CantApproveMoreThanOwned,
@@ -395,7 +402,10 @@
}
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
{
ensure!(
data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,
@@ -418,6 +428,29 @@
// =========
+ let collection = Collection {
+ owner: owner.clone(),
+ name: data.name,
+ mode: data.mode.clone(),
+ mint_mode: false,
+ access: data.access.unwrap_or_default(),
+ description: data.description,
+ token_prefix: data.token_prefix,
+ offchain_schema: data.offchain_schema,
+ schema_version: data.schema_version.unwrap_or_default(),
+ sponsorship: data
+ .pending_sponsor
+ .map(SponsorshipState::Unconfirmed)
+ .unwrap_or_default(),
+ variable_on_chain_schema: data.variable_on_chain_schema,
+ const_on_chain_schema: data.const_on_chain_schema,
+ limits: data
+ .limits
+ .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))
+ .unwrap_or_else(|| Ok(CollectionLimits::default()))?,
+ meta_update_permission: data.meta_update_permission.unwrap_or_default(),
+ };
+
// Take a (non-refundable) deposit of collection creation
{
let mut imbalance =
@@ -429,7 +462,7 @@
),
);
<T as Config>::Currency::settle(
- &data.owner,
+ &owner,
imbalance,
WithdrawReasons::TRANSFER,
ExistenceRequirement::KeepAlive,
@@ -438,12 +471,8 @@
}
<CreatedCollectionCount<T>>::put(created_count);
- <Pallet<T>>::deposit_event(Event::CollectionCreated(
- id,
- data.mode.id(),
- data.owner.clone(),
- ));
- <CollectionById<T>>::insert(id, data);
+ <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));
+ <CollectionById<T>>::insert(id, collection);
Ok(id)
}
@@ -527,6 +556,61 @@
Ok(())
}
+
+ pub fn clamp_limits(
+ mode: CollectionMode,
+ old_limit: &CollectionLimits,
+ mut new_limit: CollectionLimits,
+ ) -> Result<CollectionLimits, DispatchError> {
+ macro_rules! limit_default {
+ ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
+ $(
+ if let Some($new) = $new.$field {
+ let $old = $old.$field($($arg)?);
+ let _ = $new;
+ let _ = $old;
+ $check
+ } else {
+ $new.$field = $old.$field
+ }
+ )*
+ }};
+ }
+
+ limit_default!(old_limit, new_limit,
+ account_token_ownership_limit => ensure!(
+ new_limit <= MAX_TOKEN_OWNERSHIP,
+ <Error<T>>::CollectionLimitBoundsExceeded,
+ ),
+ sponsor_transfer_timeout(match mode {
+ CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ }) => ensure!(
+ new_limit <= MAX_SPONSOR_TIMEOUT,
+ <Error<T>>::CollectionLimitBoundsExceeded,
+ ),
+ sponsored_data_size => ensure!(
+ new_limit <= CUSTOM_DATA_LIMIT,
+ <Error<T>>::CollectionLimitBoundsExceeded,
+ ),
+ token_limit => ensure!(
+ old_limit >= new_limit && new_limit > 0,
+ <Error<T>>::CollectionTokenLimitExceeded
+ ),
+ owner_can_transfer => ensure!(
+ old_limit || !new_limit,
+ <Error<T>>::OwnerPermissionsCantBeReverted,
+ ),
+ owner_can_destroy => ensure!(
+ old_limit || !new_limit,
+ <Error<T>>::OwnerPermissionsCantBeReverted,
+ ),
+ sponsored_data_rate_limit => {},
+ transfers_enabled => {},
+ );
+ Ok(new_limit)
+ }
}
#[macro_export]
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -2,7 +2,7 @@
use core::ops::Deref;
use frame_support::{ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, TokenId};
+use up_data_structs::{AccessMode, CollectionId, TokenId, CreateCollectionData};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
};
@@ -100,8 +100,11 @@
}
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: FungibleHandle<T>,
@@ -343,7 +346,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
@@ -374,7 +377,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -2,7 +2,7 @@
use erc::ERC721Events;
use frame_support::{BoundedVec, ensure};
-use up_data_structs::{AccessMode, Collection, CollectionId, CustomDataLimit, TokenId};
+use up_data_structs::{AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData};
use pallet_common::{
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
};
@@ -140,8 +140,11 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: NonfungibleHandle<T>,
@@ -492,7 +495,7 @@
if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
@@ -520,7 +523,7 @@
if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -2,7 +2,7 @@
use frame_support::{ensure, BoundedVec};
use up_data_structs::{
- AccessMode, Collection, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,
+ AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId, CreateCollectionData,
};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
@@ -155,8 +155,11 @@
// unchecked calls skips any permission checks
impl<T: Config> Pallet<T> {
- pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(data)
+ pub fn init_collection(
+ owner: T::AccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ <PalletCommon<T>>::init_collection(owner, data)
}
pub fn destroy_collection(
collection: RefungibleHandle<T>,
@@ -528,7 +531,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
@@ -561,7 +564,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
pallets/unique/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error, decl_event,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},33 BoundedVec,34};35use scale_info::TypeInfo;36use frame_system::{self as system, ensure_signed};37use sp_runtime::{sp_std::prelude::Vec};38use up_data_structs::{39 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,40 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,41 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,42 NFT_SPONSOR_TRANSFER_TIMEOUT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,43 MAX_TOKEN_PREFIX_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits,44 CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,45 CustomDataLimit,46};47use pallet_common::{48 account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,49 CommonWeightInfo,50};51use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};52use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};53use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5455#[cfg(test)]56mod mock;5758#[cfg(test)]59mod tests;6061mod eth;62mod sponsorship;63pub use sponsorship::UniqueSponsorshipHandler;64pub use eth::sponsoring::UniqueEthSponsorshipHandler;6566pub use eth::UniqueErcSupport;6768pub mod common;69use common::CommonWeights;70pub mod dispatch;71use dispatch::dispatch_call;7273#[cfg(feature = "runtime-benchmarks")]74mod benchmarking;75pub mod weights;76use weights::WeightInfo;7778decl_error! {79 /// Error for non-fungible-token module.80 pub enum Error for Module<T: Config> {81 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.82 CollectionDecimalPointLimitExceeded,83 /// This address is not set as sponsor, use setCollectionSponsor first.84 ConfirmUnsetSponsorFail,85 /// Length of items properties must be greater than 0.86 EmptyArgument,87 /// Collection limit bounds per collection exceeded88 CollectionLimitBoundsExceeded,89 /// Tried to enable permissions which are only permitted to be disabled90 OwnerPermissionsCantBeReverted,91 }92}9394pub trait Config:95 system::Config96 + pallet_evm_coder_substrate::Config97 + pallet_common::Config98 + pallet_nonfungible::Config99 + pallet_refungible::Config100 + pallet_fungible::Config101 + Sized102 + TypeInfo103{104 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;105106 /// Weight information for extrinsics in this pallet.107 type WeightInfo: WeightInfo;108}109110decl_event! {111 pub enum Event<T>112 where113 <T as frame_system::Config>::AccountId,114 <T as pallet_common::Config>::CrossAccountId,115 {116 /// Collection sponsor was removed117 ///118 /// # Arguments119 ///120 /// * collection_id: Globally unique collection identifier.121 CollectionSponsorRemoved(CollectionId),122123 /// Collection admin was added124 ///125 /// # Arguments126 ///127 /// * collection_id: Globally unique collection identifier.128 ///129 /// * admin: Admin address.130 CollectionAdminAdded(CollectionId, CrossAccountId),131132 /// Collection owned was change133 ///134 /// # Arguments135 ///136 /// * collection_id: Globally unique collection identifier.137 ///138 /// * owner: New owner address.139 CollectionOwnedChanged(CollectionId, AccountId),140141 /// Collection sponsor was set142 ///143 /// # Arguments144 ///145 /// * collection_id: Globally unique collection identifier.146 ///147 /// * owner: New sponsor address.148 CollectionSponsorSet(CollectionId, AccountId),149150 /// const on chain schema was set151 ///152 /// # Arguments153 ///154 /// * collection_id: Globally unique collection identifier.155 ConstOnChainSchemaSet(CollectionId),156157 /// New sponsor was confirm158 ///159 /// # Arguments160 ///161 /// * collection_id: Globally unique collection identifier.162 ///163 /// * sponsor: New sponsor address.164 SponsorshipConfirmed(CollectionId, AccountId),165166 /// Collection admin was removed167 ///168 /// # Arguments169 ///170 /// * collection_id: Globally unique collection identifier.171 ///172 /// * admin: Admin address.173 CollectionAdminRemoved(CollectionId, CrossAccountId),174175 /// Address was remove from allow list176 ///177 /// # Arguments178 ///179 /// * collection_id: Globally unique collection identifier.180 ///181 /// * user: Address.182 AllowListAddressRemoved(CollectionId, CrossAccountId),183184 /// Address was add to allow list185 ///186 /// # Arguments187 ///188 /// * collection_id: Globally unique collection identifier.189 ///190 /// * user: Address.191 AllowListAddressAdded(CollectionId, CrossAccountId),192193 /// Collection limits was set194 ///195 /// # Arguments196 ///197 /// * collection_id: Globally unique collection identifier.198 CollectionLimitSet(CollectionId),199200 /// Mint permission was set201 ///202 /// # Arguments203 ///204 /// * collection_id: Globally unique collection identifier.205 MintPermissionSet(CollectionId),206207 /// Offchain schema was set208 ///209 /// # Arguments210 ///211 /// * collection_id: Globally unique collection identifier.212 OffchainSchemaSet(CollectionId),213214 /// Public access mode was set215 ///216 /// # Arguments217 ///218 /// * collection_id: Globally unique collection identifier.219 ///220 /// * mode: New access state.221 PublicAccessModeSet(CollectionId, AccessMode),222223 /// Schema version was set224 ///225 /// # Arguments226 ///227 /// * collection_id: Globally unique collection identifier.228 SchemaVersionSet(CollectionId),229230 /// Variable on chain schema was set231 ///232 /// # Arguments233 ///234 /// * collection_id: Globally unique collection identifier.235 VariableOnChainSchemaSet(CollectionId),236 }237}238239type SelfWeightOf<T> = <T as Config>::WeightInfo;240241// # Used definitions242//243// ## User control levels244//245// chain-controlled - key is uncontrolled by user246// i.e autoincrementing index247// can use non-cryptographic hash248// real - key is controlled by user249// but it is hard to generate enough colliding values, i.e owner of signed txs250// can use non-cryptographic hash251// controlled - key is completly controlled by users252// i.e maps with mutable keys253// should use cryptographic hash254//255// ## User control level downgrade reasons256//257// ?1 - chain-controlled -> controlled258// collections/tokens can be destroyed, resulting in massive holes259// ?2 - chain-controlled -> controlled260// same as ?1, but can be only added, resulting in easier exploitation261// ?3 - real -> controlled262// no confirmation required, so addresses can be easily generated263decl_storage! {264 trait Store for Module<T: Config> as Unique {265266 //#region Private members267 /// Used for migrations268 ChainVersion: u64;269 //#endregion270271 //#region Tokens transfer rate limit baskets272 /// (Collection id (controlled?2), who created (real))273 /// TODO: Off chain worker should remove from this map when collection gets removed274 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;275 /// Collection id (controlled?2), token id (controlled?2)276 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;277 /// Collection id (controlled?2), owning user (real)278 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;279 /// Collection id (controlled?2), token id (controlled?2)280 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;281 //#endregion282283 /// Variable metadata sponsoring284 /// Collection id (controlled?2), token id (controlled?2)285 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;286 /// Approval sponsoring287 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;288 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;289 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;290 }291}292293decl_module! {294 pub struct Module<T: Config> for enum Call295 where296 origin: T::Origin297 {298 type Error = Error<T>;299300 fn deposit_event() = default;301302 fn on_initialize(_now: T::BlockNumber) -> Weight {303 0304 }305306 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.307 ///308 /// # Permissions309 ///310 /// * Anyone.311 ///312 /// # Arguments313 ///314 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.315 ///316 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.317 ///318 /// * token_prefix: UTF-8 string with token prefix.319 ///320 /// * mode: [CollectionMode] collection type and type dependent data.321 // returns collection ID322 #[weight = <SelfWeightOf<T>>::create_collection()]323 #[transactional]324 pub fn create_collection(origin,325 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,326 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,327 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,328 mode: CollectionMode) -> DispatchResult {329330 // Anyone can create a collection331 let who = ensure_signed(origin)?;332333 // Create new collection334 let new_collection = Collection {335 owner: who,336 name: collection_name,337 mode: mode.clone(),338 mint_mode: false,339 access: AccessMode::Normal,340 description: collection_description,341 token_prefix,342 offchain_schema: BoundedVec::default(),343 schema_version: SchemaVersion::ImageURL,344 sponsorship: SponsorshipState::Disabled,345 variable_on_chain_schema: BoundedVec::default(),346 const_on_chain_schema: BoundedVec::default(),347 limits: Default::default(),348 meta_update_permission: Default::default(),349 };350351 let _id = match mode {352 CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},353 CollectionMode::Fungible(decimal_points) => {354 // check params355 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);356 <PalletFungible<T>>::init_collection(new_collection)?357 }358 CollectionMode::ReFungible => {359 <PalletRefungible<T>>::init_collection(new_collection)?360 }361 };362363 Ok(())364 }365366 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.367 ///368 /// # Permissions369 ///370 /// * Collection Owner.371 ///372 /// # Arguments373 ///374 /// * collection_id: collection to destroy.375 #[weight = <SelfWeightOf<T>>::destroy_collection()]376 #[transactional]377 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {378 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);379380 let collection = <CollectionHandle<T>>::try_get(collection_id)?;381 collection.check_is_owner(&sender)?;382383 // =========384385 match collection.mode {386 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,387 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,388 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,389 }390391 <NftTransferBasket<T>>::remove_prefix(collection_id, None);392 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);393 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);394395 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);396 <NftApproveBasket<T>>::remove_prefix(collection_id, None);397 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);398 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);399400 Ok(())401 }402403 /// Add an address to allow list.404 ///405 /// # Permissions406 ///407 /// * Collection Owner408 /// * Collection Admin409 ///410 /// # Arguments411 ///412 /// * collection_id.413 ///414 /// * address.415 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]416 #[transactional]417 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{418419 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);420 let collection = <CollectionHandle<T>>::try_get(collection_id)?;421422 <PalletCommon<T>>::toggle_allowlist(423 &collection,424 &sender,425 &address,426 true,427 )?;428429 Self::deposit_event(Event::<T>::AllowListAddressAdded(430 collection_id,431 address432 ));433434 Ok(())435 }436437 /// Remove an address from allow list.438 ///439 /// # Permissions440 ///441 /// * Collection Owner442 /// * Collection Admin443 ///444 /// # Arguments445 ///446 /// * collection_id.447 ///448 /// * address.449 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]450 #[transactional]451 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{452453 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454 let collection = <CollectionHandle<T>>::try_get(collection_id)?;455456 <PalletCommon<T>>::toggle_allowlist(457 &collection,458 &sender,459 &address,460 false,461 )?;462463 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(464 collection_id,465 address466 ));467468 Ok(())469 }470471 /// Toggle between normal and allow list access for the methods with access for `Anyone`.472 ///473 /// # Permissions474 ///475 /// * Collection Owner.476 ///477 /// # Arguments478 ///479 /// * collection_id.480 ///481 /// * mode: [AccessMode]482 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]483 #[transactional]484 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult485 {486 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);487488 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;489 target_collection.check_is_owner(&sender)?;490491 target_collection.access = mode.clone();492493 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(494 collection_id,495 mode496 ));497498 target_collection.save()499 }500501 /// Allows Anyone to create tokens if:502 /// * Allow List is enabled, and503 /// * Address is added to allow list, and504 /// * This method was called with True parameter505 ///506 /// # Permissions507 /// * Collection Owner508 ///509 /// # Arguments510 ///511 /// * collection_id.512 ///513 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.514 #[weight = <SelfWeightOf<T>>::set_mint_permission()]515 #[transactional]516 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult517 {518 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);519520 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;521 target_collection.check_is_owner(&sender)?;522523 target_collection.mint_mode = mint_permission;524525 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(526 collection_id527 ));528529 target_collection.save()530 }531532 /// Change the owner of the collection.533 ///534 /// # Permissions535 ///536 /// * Collection Owner.537 ///538 /// # Arguments539 ///540 /// * collection_id.541 ///542 /// * new_owner.543 #[weight = <SelfWeightOf<T>>::change_collection_owner()]544 #[transactional]545 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {546547 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);548549 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;550 target_collection.check_is_owner(&sender)?;551552 target_collection.owner = new_owner.clone();553 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(554 collection_id,555 new_owner556 ));557558 target_collection.save()559 }560561 /// Adds an admin of the Collection.562 /// 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.563 ///564 /// # Permissions565 ///566 /// * Collection Owner.567 /// * Collection Admin.568 ///569 /// # Arguments570 ///571 /// * collection_id: ID of the Collection to add admin for.572 ///573 /// * new_admin_id: Address of new admin to add.574 #[weight = <SelfWeightOf<T>>::add_collection_admin()]575 #[transactional]576 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {577 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);578 let collection = <CollectionHandle<T>>::try_get(collection_id)?;579580 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(581 collection_id,582 new_admin_id.clone()583 ));584585 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)586 }587588 /// 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.589 ///590 /// # Permissions591 ///592 /// * Collection Owner.593 /// * Collection Admin.594 ///595 /// # Arguments596 ///597 /// * collection_id: ID of the Collection to remove admin for.598 ///599 /// * account_id: Address of admin to remove.600 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]601 #[transactional]602 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {603 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604 let collection = <CollectionHandle<T>>::try_get(collection_id)?;605606 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(607 collection_id,608 account_id.clone()609 ));610611 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)612 }613614 /// # Permissions615 ///616 /// * Collection Owner617 ///618 /// # Arguments619 ///620 /// * collection_id.621 ///622 /// * new_sponsor.623 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]624 #[transactional]625 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {626 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);627628 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;629 target_collection.check_is_owner(&sender)?;630631 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());632633 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(634 collection_id,635 new_sponsor636 ));637638 target_collection.save()639 }640641 /// # Permissions642 ///643 /// * Sponsor.644 ///645 /// # Arguments646 ///647 /// * collection_id.648 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]649 #[transactional]650 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {651 let sender = ensure_signed(origin)?;652653 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;654 ensure!(655 target_collection.sponsorship.pending_sponsor() == Some(&sender),656 Error::<T>::ConfirmUnsetSponsorFail657 );658659 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());660661 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(662 collection_id,663 sender664 ));665666 target_collection.save()667 }668669 /// Switch back to pay-per-own-transaction model.670 ///671 /// # Permissions672 ///673 /// * Collection owner.674 ///675 /// # Arguments676 ///677 /// * collection_id.678 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]679 #[transactional]680 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {681 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);682683 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;684 target_collection.check_is_owner(&sender)?;685686 target_collection.sponsorship = SponsorshipState::Disabled;687688 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(689 collection_id690 ));691 target_collection.save()692 }693694 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.695 ///696 /// # Permissions697 ///698 /// * Collection Owner.699 /// * Collection Admin.700 /// * Anyone if701 /// * Allow List is enabled, and702 /// * Address is added to allow list, and703 /// * MintPermission is enabled (see SetMintPermission method)704 ///705 /// # Arguments706 ///707 /// * collection_id: ID of the collection.708 ///709 /// * owner: Address, initial owner of the NFT.710 ///711 /// * data: Token data to store on chain.712 #[weight = <CommonWeights<T>>::create_item()]713 #[transactional]714 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {715 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);716717 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))718 }719720 /// This method creates multiple items in a collection created with CreateCollection method.721 ///722 /// # Permissions723 ///724 /// * Collection Owner.725 /// * Collection Admin.726 /// * Anyone if727 /// * Allow List is enabled, and728 /// * Address is added to allow list, and729 /// * MintPermission is enabled (see SetMintPermission method)730 ///731 /// # Arguments732 ///733 /// * collection_id: ID of the collection.734 ///735 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].736 ///737 /// * owner: Address, initial owner of the NFT.738 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]739 #[transactional]740 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {741 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);742 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);743744 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))745 }746747 // TODO! transaction weight748749 /// Set transfers_enabled value for particular collection750 ///751 /// # Permissions752 ///753 /// * Collection Owner.754 ///755 /// # Arguments756 ///757 /// * collection_id: ID of the collection.758 ///759 /// * value: New flag value.760 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]761 #[transactional]762 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {763 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);764 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;765 target_collection.check_is_owner(&sender)?;766767 // =========768769 target_collection.limits.transfers_enabled = Some(value);770 target_collection.save()771 }772773 /// Destroys a concrete instance of NFT.774 ///775 /// # Permissions776 ///777 /// * Collection Owner.778 /// * Collection Admin.779 /// * Current NFT Owner.780 ///781 /// # Arguments782 ///783 /// * collection_id: ID of the collection.784 ///785 /// * item_id: ID of NFT to burn.786 #[weight = <CommonWeights<T>>::burn_item()]787 #[transactional]788 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {789 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);790791 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;792 if value == 1 {793 <NftTransferBasket<T>>::remove(collection_id, item_id);794 <NftApproveBasket<T>>::remove(collection_id, item_id);795 }796 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?797 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());798 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));799 Ok(post_info)800 }801802 /// Destroys a concrete instance of NFT on behalf of the owner803 /// See also: [`approve`]804 ///805 /// # Permissions806 ///807 /// * Collection Owner.808 /// * Collection Admin.809 /// * Current NFT Owner.810 ///811 /// # Arguments812 ///813 /// * collection_id: ID of the collection.814 ///815 /// * item_id: ID of NFT to burn.816 ///817 /// * from: owner of item818 #[weight = <CommonWeights<T>>::burn_from()]819 #[transactional]820 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {821 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);822823 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))824 }825826 /// Change ownership of the token.827 ///828 /// # Permissions829 ///830 /// * Collection Owner831 /// * Collection Admin832 /// * Current NFT owner833 ///834 /// # Arguments835 ///836 /// * recipient: Address of token recipient.837 ///838 /// * collection_id.839 ///840 /// * item_id: ID of the item841 /// * Non-Fungible Mode: Required.842 /// * Fungible Mode: Ignored.843 /// * Re-Fungible Mode: Required.844 ///845 /// * value: Amount to transfer.846 /// * Non-Fungible Mode: Ignored847 /// * Fungible Mode: Must specify transferred amount848 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)849 #[weight = <CommonWeights<T>>::transfer()]850 #[transactional]851 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {852 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);853854 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))855 }856857 /// Set, change, or remove approved address to transfer the ownership of the NFT.858 ///859 /// # Permissions860 ///861 /// * Collection Owner862 /// * Collection Admin863 /// * Current NFT owner864 ///865 /// # Arguments866 ///867 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).868 ///869 /// * collection_id.870 ///871 /// * item_id: ID of the item.872 #[weight = <CommonWeights<T>>::approve()]873 #[transactional]874 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {875 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);876877 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))878 }879880 /// 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.881 ///882 /// # Permissions883 /// * Collection Owner884 /// * Collection Admin885 /// * Current NFT owner886 /// * Address approved by current NFT owner887 ///888 /// # Arguments889 ///890 /// * from: Address that owns token.891 ///892 /// * recipient: Address of token recipient.893 ///894 /// * collection_id.895 ///896 /// * item_id: ID of the item.897 ///898 /// * value: Amount to transfer.899 #[weight = <CommonWeights<T>>::transfer_from()]900 #[transactional]901 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {902 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);903904 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))905 }906907 /// Set off-chain data schema.908 ///909 /// # Permissions910 ///911 /// * Collection Owner912 /// * Collection Admin913 ///914 /// # Arguments915 ///916 /// * collection_id.917 ///918 /// * schema: String representing the offchain data schema.919 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]920 #[transactional]921 pub fn set_variable_meta_data (922 origin,923 collection_id: CollectionId,924 item_id: TokenId,925 data: BoundedVec<u8, CustomDataLimit>,926 ) -> DispatchResultWithPostInfo {927 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);928929 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))930 }931932 /// Set meta_update_permission value for particular collection933 ///934 /// # Permissions935 ///936 /// * Collection Owner.937 ///938 /// # Arguments939 ///940 /// * collection_id: ID of the collection.941 ///942 /// * value: New flag value.943 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]944 #[transactional]945 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {946 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);947 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;948949 ensure!(950 target_collection.meta_update_permission != MetaUpdatePermission::None,951 <CommonError<T>>::MetadataFlagFrozen,952 );953 target_collection.check_is_owner(&sender)?;954955 target_collection.meta_update_permission = value;956957 target_collection.save()958 }959960 /// Set schema standard961 /// ImageURL962 /// Unique963 ///964 /// # Permissions965 ///966 /// * Collection Owner967 /// * Collection Admin968 ///969 /// # Arguments970 ///971 /// * collection_id.972 ///973 /// * schema: SchemaVersion: enum974 #[weight = <SelfWeightOf<T>>::set_schema_version()]975 #[transactional]976 pub fn set_schema_version(977 origin,978 collection_id: CollectionId,979 version: SchemaVersion980 ) -> DispatchResult {981 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);982 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;983 target_collection.check_is_owner_or_admin(&sender)?;984 target_collection.schema_version = version;985986 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(987 collection_id988 ));989990 target_collection.save()991 }992993 /// Set off-chain data schema.994 ///995 /// # Permissions996 ///997 /// * Collection Owner998 /// * Collection Admin999 ///1000 /// # Arguments1001 ///1002 /// * collection_id.1003 ///1004 /// * schema: String representing the offchain data schema.1005 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]1006 #[transactional]1007 pub fn set_offchain_schema(1008 origin,1009 collection_id: CollectionId,1010 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1011 ) -> DispatchResult {1012 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1013 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1014 target_collection.check_is_owner_or_admin(&sender)?;10151016 target_collection.offchain_schema = schema;10171018 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1019 collection_id1020 ));10211022 target_collection.save()1023 }10241025 /// Set const on-chain data schema.1026 ///1027 /// # Permissions1028 ///1029 /// * Collection Owner1030 /// * Collection Admin1031 ///1032 /// # Arguments1033 ///1034 /// * collection_id.1035 ///1036 /// * schema: String representing the const on-chain data schema.1037 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1038 #[transactional]1039 pub fn set_const_on_chain_schema (1040 origin,1041 collection_id: CollectionId,1042 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1043 ) -> DispatchResult {1044 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1046 target_collection.check_is_owner_or_admin(&sender)?;10471048 target_collection.const_on_chain_schema = schema;10491050 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1051 collection_id1052 ));10531054 target_collection.save()1055 }10561057 /// Set variable on-chain data schema.1058 ///1059 /// # Permissions1060 ///1061 /// * Collection Owner1062 /// * Collection Admin1063 ///1064 /// # Arguments1065 ///1066 /// * collection_id.1067 ///1068 /// * schema: String representing the variable on-chain data schema.1069 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1070 #[transactional]1071 pub fn set_variable_on_chain_schema (1072 origin,1073 collection_id: CollectionId,1074 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1075 ) -> DispatchResult {1076 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1077 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1078 target_collection.check_is_owner_or_admin(&sender)?;10791080 target_collection.variable_on_chain_schema = schema;10811082 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1083 collection_id1084 ));10851086 target_collection.save()1087 }10881089 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1090 #[transactional]1091 pub fn set_collection_limits(1092 origin,1093 collection_id: CollectionId,1094 new_limit: CollectionLimits,1095 ) -> DispatchResult {1096 let mut new_limit = new_limit;1097 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1098 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1099 target_collection.check_is_owner(&sender)?;1100 let old_limit = &target_collection.limits;11011102 macro_rules! limit_default {1103 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1104 $(1105 if let Some($new) = $new.$field {1106 let $old = $old.$field($($arg)?);1107 let _ = $new;1108 let _ = $old;1109 $check1110 } else {1111 $new.$field = $old.$field1112 }1113 )*1114 }};1115 }11161117 limit_default!(old_limit, new_limit,1118 account_token_ownership_limit => ensure!(1119 new_limit <= MAX_TOKEN_OWNERSHIP,1120 <Error<T>>::CollectionLimitBoundsExceeded,1121 ),1122 sponsor_transfer_timeout(match target_collection.mode {1123 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1124 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1125 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1126 }) => ensure!(1127 new_limit <= MAX_SPONSOR_TIMEOUT,1128 <Error<T>>::CollectionLimitBoundsExceeded,1129 ),1130 sponsored_data_size => ensure!(1131 new_limit <= CUSTOM_DATA_LIMIT,1132 <Error<T>>::CollectionLimitBoundsExceeded,1133 ),1134 token_limit => ensure!(1135 old_limit >= new_limit && new_limit > 0,1136 <CommonError<T>>::CollectionTokenLimitExceeded1137 ),1138 owner_can_transfer => ensure!(1139 old_limit || !new_limit,1140 <Error<T>>::OwnerPermissionsCantBeReverted,1141 ),1142 owner_can_destroy => ensure!(1143 old_limit || !new_limit,1144 <Error<T>>::OwnerPermissionsCantBeReverted,1145 ),1146 sponsored_data_rate_limit => {},1147 transfers_enabled => {},1148 );11491150 target_collection.limits = new_limit;11511152 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1153 collection_id1154 ));11551156 target_collection.save()1157 }1158 }1159}1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error, decl_event,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::{DispatchResultWithPostInfo, ConstU32},33 BoundedVec,34};35use scale_info::TypeInfo;36use frame_system::{self as system, ensure_signed};37use sp_runtime::{sp_std::prelude::Vec};38use up_data_structs::{39 MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,40 OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH,41 MAX_TOKEN_PREFIX_LENGTH, AccessMode, CreateItemData, CollectionLimits, CollectionId,42 CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43 CreateCollectionData, CustomDataLimit,44};45use pallet_common::{46 account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,47 CommonWeightInfo,48};49use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};50use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};51use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5253#[cfg(test)]54mod mock;5556#[cfg(test)]57mod tests;5859mod eth;60mod sponsorship;61pub use sponsorship::UniqueSponsorshipHandler;62pub use eth::sponsoring::UniqueEthSponsorshipHandler;6364pub use eth::UniqueErcSupport;6566pub mod common;67use common::CommonWeights;68pub mod dispatch;69use dispatch::dispatch_call;7071#[cfg(feature = "runtime-benchmarks")]72mod benchmarking;73pub mod weights;74use weights::WeightInfo;7576decl_error! {77 /// Error for non-fungible-token module.78 pub enum Error for Module<T: Config> {79 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.80 CollectionDecimalPointLimitExceeded,81 /// This address is not set as sponsor, use setCollectionSponsor first.82 ConfirmUnsetSponsorFail,83 /// Length of items properties must be greater than 0.84 EmptyArgument,85 }86}8788pub trait Config:89 system::Config90 + pallet_evm_coder_substrate::Config91 + pallet_common::Config92 + pallet_nonfungible::Config93 + pallet_refungible::Config94 + pallet_fungible::Config95 + Sized96 + TypeInfo97{98 type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;99100 /// Weight information for extrinsics in this pallet.101 type WeightInfo: WeightInfo;102}103104decl_event! {105 pub enum Event<T>106 where107 <T as frame_system::Config>::AccountId,108 <T as pallet_common::Config>::CrossAccountId,109 {110 /// Collection sponsor was removed111 ///112 /// # Arguments113 ///114 /// * collection_id: Globally unique collection identifier.115 CollectionSponsorRemoved(CollectionId),116117 /// Collection admin was added118 ///119 /// # Arguments120 ///121 /// * collection_id: Globally unique collection identifier.122 ///123 /// * admin: Admin address.124 CollectionAdminAdded(CollectionId, CrossAccountId),125126 /// Collection owned was change127 ///128 /// # Arguments129 ///130 /// * collection_id: Globally unique collection identifier.131 ///132 /// * owner: New owner address.133 CollectionOwnedChanged(CollectionId, AccountId),134135 /// Collection sponsor was set136 ///137 /// # Arguments138 ///139 /// * collection_id: Globally unique collection identifier.140 ///141 /// * owner: New sponsor address.142 CollectionSponsorSet(CollectionId, AccountId),143144 /// const on chain schema was set145 ///146 /// # Arguments147 ///148 /// * collection_id: Globally unique collection identifier.149 ConstOnChainSchemaSet(CollectionId),150151 /// New sponsor was confirm152 ///153 /// # Arguments154 ///155 /// * collection_id: Globally unique collection identifier.156 ///157 /// * sponsor: New sponsor address.158 SponsorshipConfirmed(CollectionId, AccountId),159160 /// Collection admin was removed161 ///162 /// # Arguments163 ///164 /// * collection_id: Globally unique collection identifier.165 ///166 /// * admin: Admin address.167 CollectionAdminRemoved(CollectionId, CrossAccountId),168169 /// Address was remove from allow list170 ///171 /// # Arguments172 ///173 /// * collection_id: Globally unique collection identifier.174 ///175 /// * user: Address.176 AllowListAddressRemoved(CollectionId, CrossAccountId),177178 /// Address was add to allow list179 ///180 /// # Arguments181 ///182 /// * collection_id: Globally unique collection identifier.183 ///184 /// * user: Address.185 AllowListAddressAdded(CollectionId, CrossAccountId),186187 /// Collection limits was set188 ///189 /// # Arguments190 ///191 /// * collection_id: Globally unique collection identifier.192 CollectionLimitSet(CollectionId),193194 /// Mint permission was set195 ///196 /// # Arguments197 ///198 /// * collection_id: Globally unique collection identifier.199 MintPermissionSet(CollectionId),200201 /// Offchain schema was set202 ///203 /// # Arguments204 ///205 /// * collection_id: Globally unique collection identifier.206 OffchainSchemaSet(CollectionId),207208 /// Public access mode was set209 ///210 /// # Arguments211 ///212 /// * collection_id: Globally unique collection identifier.213 ///214 /// * mode: New access state.215 PublicAccessModeSet(CollectionId, AccessMode),216217 /// Schema version was set218 ///219 /// # Arguments220 ///221 /// * collection_id: Globally unique collection identifier.222 SchemaVersionSet(CollectionId),223224 /// Variable on chain schema was set225 ///226 /// # Arguments227 ///228 /// * collection_id: Globally unique collection identifier.229 VariableOnChainSchemaSet(CollectionId),230 }231}232233type SelfWeightOf<T> = <T as Config>::WeightInfo;234235// # Used definitions236//237// ## User control levels238//239// chain-controlled - key is uncontrolled by user240// i.e autoincrementing index241// can use non-cryptographic hash242// real - key is controlled by user243// but it is hard to generate enough colliding values, i.e owner of signed txs244// can use non-cryptographic hash245// controlled - key is completly controlled by users246// i.e maps with mutable keys247// should use cryptographic hash248//249// ## User control level downgrade reasons250//251// ?1 - chain-controlled -> controlled252// collections/tokens can be destroyed, resulting in massive holes253// ?2 - chain-controlled -> controlled254// same as ?1, but can be only added, resulting in easier exploitation255// ?3 - real -> controlled256// no confirmation required, so addresses can be easily generated257decl_storage! {258 trait Store for Module<T: Config> as Unique {259260 //#region Private members261 /// Used for migrations262 ChainVersion: u64;263 //#endregion264265 //#region Tokens transfer rate limit baskets266 /// (Collection id (controlled?2), who created (real))267 /// TODO: Off chain worker should remove from this map when collection gets removed268 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;269 /// Collection id (controlled?2), token id (controlled?2)270 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;271 /// Collection id (controlled?2), owning user (real)272 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;273 /// Collection id (controlled?2), token id (controlled?2)274 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;275 //#endregion276277 /// Variable metadata sponsoring278 /// Collection id (controlled?2), token id (controlled?2)279 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;280 /// Approval sponsoring281 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;282 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;283 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;284 }285}286287decl_module! {288 pub struct Module<T: Config> for enum Call289 where290 origin: T::Origin291 {292 type Error = Error<T>;293294 fn deposit_event() = default;295296 fn on_initialize(_now: T::BlockNumber) -> Weight {297 0298 }299300 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner of the collection is set to the address that signed the transaction and can be changed later.301 ///302 /// # Permissions303 ///304 /// * Anyone.305 ///306 /// # Arguments307 ///308 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.309 ///310 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.311 ///312 /// * token_prefix: UTF-8 string with token prefix.313 ///314 /// * mode: [CollectionMode] collection type and type dependent data.315 // returns collection ID316 #[weight = <SelfWeightOf<T>>::create_collection()]317 #[transactional]318 #[deprecated]319 pub fn create_collection(origin,320 collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,321 collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,322 token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,323 mode: CollectionMode) -> DispatchResult {324 let data: CreateCollectionData<T::AccountId> = CreateCollectionData {325 name: collection_name,326 description: collection_description,327 token_prefix,328 mode,329 ..Default::default()330 };331 Self::create_collection_ex(origin, data)332 }333334 /// This method creates a collection335 ///336 /// Prefer it to deprecated [`created_collection`] method337 #[weight = <SelfWeightOf<T>>::create_collection()]338 #[transactional]339 pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {340 let owner = ensure_signed(origin)?;341342 let _id = match data.mode {343 CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},344 CollectionMode::Fungible(decimal_points) => {345 // check params346 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);347 <PalletFungible<T>>::init_collection(owner, data)?348 }349 CollectionMode::ReFungible => {350 <PalletRefungible<T>>::init_collection(owner, data)?351 }352 };353354 Ok(())355 }356357 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.358 ///359 /// # Permissions360 ///361 /// * Collection Owner.362 ///363 /// # Arguments364 ///365 /// * collection_id: collection to destroy.366 #[weight = <SelfWeightOf<T>>::destroy_collection()]367 #[transactional]368 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {369 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);370371 let collection = <CollectionHandle<T>>::try_get(collection_id)?;372 collection.check_is_owner(&sender)?;373374 // =========375376 match collection.mode {377 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,378 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,379 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,380 }381382 <NftTransferBasket<T>>::remove_prefix(collection_id, None);383 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);384 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);385386 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);387 <NftApproveBasket<T>>::remove_prefix(collection_id, None);388 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);389 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);390391 Ok(())392 }393394 /// Add an address to allow list.395 ///396 /// # Permissions397 ///398 /// * Collection Owner399 /// * Collection Admin400 ///401 /// # Arguments402 ///403 /// * collection_id.404 ///405 /// * address.406 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]407 #[transactional]408 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{409410 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);411 let collection = <CollectionHandle<T>>::try_get(collection_id)?;412413 <PalletCommon<T>>::toggle_allowlist(414 &collection,415 &sender,416 &address,417 true,418 )?;419420 Self::deposit_event(Event::<T>::AllowListAddressAdded(421 collection_id,422 address423 ));424425 Ok(())426 }427428 /// Remove an address from allow list.429 ///430 /// # Permissions431 ///432 /// * Collection Owner433 /// * Collection Admin434 ///435 /// # Arguments436 ///437 /// * collection_id.438 ///439 /// * address.440 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]441 #[transactional]442 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{443444 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);445 let collection = <CollectionHandle<T>>::try_get(collection_id)?;446447 <PalletCommon<T>>::toggle_allowlist(448 &collection,449 &sender,450 &address,451 false,452 )?;453454 <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(455 collection_id,456 address457 ));458459 Ok(())460 }461462 /// Toggle between normal and allow list access for the methods with access for `Anyone`.463 ///464 /// # Permissions465 ///466 /// * Collection Owner.467 ///468 /// # Arguments469 ///470 /// * collection_id.471 ///472 /// * mode: [AccessMode]473 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]474 #[transactional]475 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult476 {477 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);478479 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;480 target_collection.check_is_owner(&sender)?;481482 target_collection.access = mode.clone();483484 <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(485 collection_id,486 mode487 ));488489 target_collection.save()490 }491492 /// Allows Anyone to create tokens if:493 /// * Allow List is enabled, and494 /// * Address is added to allow list, and495 /// * This method was called with True parameter496 ///497 /// # Permissions498 /// * Collection Owner499 ///500 /// # Arguments501 ///502 /// * collection_id.503 ///504 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.505 #[weight = <SelfWeightOf<T>>::set_mint_permission()]506 #[transactional]507 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult508 {509 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);510511 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;512 target_collection.check_is_owner(&sender)?;513514 target_collection.mint_mode = mint_permission;515516 <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(517 collection_id518 ));519520 target_collection.save()521 }522523 /// Change the owner of the collection.524 ///525 /// # Permissions526 ///527 /// * Collection Owner.528 ///529 /// # Arguments530 ///531 /// * collection_id.532 ///533 /// * new_owner.534 #[weight = <SelfWeightOf<T>>::change_collection_owner()]535 #[transactional]536 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {537538 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);539540 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;541 target_collection.check_is_owner(&sender)?;542543 target_collection.owner = new_owner.clone();544 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(545 collection_id,546 new_owner547 ));548549 target_collection.save()550 }551552 /// Adds an admin of the Collection.553 /// 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.554 ///555 /// # Permissions556 ///557 /// * Collection Owner.558 /// * Collection Admin.559 ///560 /// # Arguments561 ///562 /// * collection_id: ID of the Collection to add admin for.563 ///564 /// * new_admin_id: Address of new admin to add.565 #[weight = <SelfWeightOf<T>>::add_collection_admin()]566 #[transactional]567 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {568 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);569 let collection = <CollectionHandle<T>>::try_get(collection_id)?;570571 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(572 collection_id,573 new_admin_id.clone()574 ));575576 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)577 }578579 /// 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.580 ///581 /// # Permissions582 ///583 /// * Collection Owner.584 /// * Collection Admin.585 ///586 /// # Arguments587 ///588 /// * collection_id: ID of the Collection to remove admin for.589 ///590 /// * account_id: Address of admin to remove.591 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]592 #[transactional]593 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {594 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);595 let collection = <CollectionHandle<T>>::try_get(collection_id)?;596597 <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(598 collection_id,599 account_id.clone()600 ));601602 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)603 }604605 /// # Permissions606 ///607 /// * Collection Owner608 ///609 /// # Arguments610 ///611 /// * collection_id.612 ///613 /// * new_sponsor.614 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]615 #[transactional]616 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {617 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);618619 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;620 target_collection.check_is_owner(&sender)?;621622 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());623624 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(625 collection_id,626 new_sponsor627 ));628629 target_collection.save()630 }631632 /// # Permissions633 ///634 /// * Sponsor.635 ///636 /// # Arguments637 ///638 /// * collection_id.639 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]640 #[transactional]641 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {642 let sender = ensure_signed(origin)?;643644 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;645 ensure!(646 target_collection.sponsorship.pending_sponsor() == Some(&sender),647 Error::<T>::ConfirmUnsetSponsorFail648 );649650 target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());651652 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(653 collection_id,654 sender655 ));656657 target_collection.save()658 }659660 /// Switch back to pay-per-own-transaction model.661 ///662 /// # Permissions663 ///664 /// * Collection owner.665 ///666 /// # Arguments667 ///668 /// * collection_id.669 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]670 #[transactional]671 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {672 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);673674 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;675 target_collection.check_is_owner(&sender)?;676677 target_collection.sponsorship = SponsorshipState::Disabled;678679 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(680 collection_id681 ));682 target_collection.save()683 }684685 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.686 ///687 /// # Permissions688 ///689 /// * Collection Owner.690 /// * Collection Admin.691 /// * Anyone if692 /// * Allow List is enabled, and693 /// * Address is added to allow list, and694 /// * MintPermission is enabled (see SetMintPermission method)695 ///696 /// # Arguments697 ///698 /// * collection_id: ID of the collection.699 ///700 /// * owner: Address, initial owner of the NFT.701 ///702 /// * data: Token data to store on chain.703 #[weight = <CommonWeights<T>>::create_item()]704 #[transactional]705 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {706 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);707708 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))709 }710711 /// This method creates multiple items in a collection created with CreateCollection method.712 ///713 /// # Permissions714 ///715 /// * Collection Owner.716 /// * Collection Admin.717 /// * Anyone if718 /// * Allow List is enabled, and719 /// * Address is added to allow list, and720 /// * MintPermission is enabled (see SetMintPermission method)721 ///722 /// # Arguments723 ///724 /// * collection_id: ID of the collection.725 ///726 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].727 ///728 /// * owner: Address, initial owner of the NFT.729 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]730 #[transactional]731 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {732 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);733 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);734735 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))736 }737738 // TODO! transaction weight739740 /// Set transfers_enabled value for particular collection741 ///742 /// # Permissions743 ///744 /// * Collection Owner.745 ///746 /// # Arguments747 ///748 /// * collection_id: ID of the collection.749 ///750 /// * value: New flag value.751 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]752 #[transactional]753 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {754 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);755 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;756 target_collection.check_is_owner(&sender)?;757758 // =========759760 target_collection.limits.transfers_enabled = Some(value);761 target_collection.save()762 }763764 /// Destroys a concrete instance of NFT.765 ///766 /// # Permissions767 ///768 /// * Collection Owner.769 /// * Collection Admin.770 /// * Current NFT Owner.771 ///772 /// # Arguments773 ///774 /// * collection_id: ID of the collection.775 ///776 /// * item_id: ID of NFT to burn.777 #[weight = <CommonWeights<T>>::burn_item()]778 #[transactional]779 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {780 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);781782 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;783 if value == 1 {784 <NftTransferBasket<T>>::remove(collection_id, item_id);785 <NftApproveBasket<T>>::remove(collection_id, item_id);786 }787 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?788 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());789 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));790 Ok(post_info)791 }792793 /// Destroys a concrete instance of NFT on behalf of the owner794 /// See also: [`approve`]795 ///796 /// # Permissions797 ///798 /// * Collection Owner.799 /// * Collection Admin.800 /// * Current NFT Owner.801 ///802 /// # Arguments803 ///804 /// * collection_id: ID of the collection.805 ///806 /// * item_id: ID of NFT to burn.807 ///808 /// * from: owner of item809 #[weight = <CommonWeights<T>>::burn_from()]810 #[transactional]811 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {812 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);813814 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))815 }816817 /// Change ownership of the token.818 ///819 /// # Permissions820 ///821 /// * Collection Owner822 /// * Collection Admin823 /// * Current NFT owner824 ///825 /// # Arguments826 ///827 /// * recipient: Address of token recipient.828 ///829 /// * collection_id.830 ///831 /// * item_id: ID of the item832 /// * Non-Fungible Mode: Required.833 /// * Fungible Mode: Ignored.834 /// * Re-Fungible Mode: Required.835 ///836 /// * value: Amount to transfer.837 /// * Non-Fungible Mode: Ignored838 /// * Fungible Mode: Must specify transferred amount839 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)840 #[weight = <CommonWeights<T>>::transfer()]841 #[transactional]842 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {843 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);844845 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))846 }847848 /// Set, change, or remove approved address to transfer the ownership of the NFT.849 ///850 /// # Permissions851 ///852 /// * Collection Owner853 /// * Collection Admin854 /// * Current NFT owner855 ///856 /// # Arguments857 ///858 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).859 ///860 /// * collection_id.861 ///862 /// * item_id: ID of the item.863 #[weight = <CommonWeights<T>>::approve()]864 #[transactional]865 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {866 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);867868 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))869 }870871 /// 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.872 ///873 /// # Permissions874 /// * Collection Owner875 /// * Collection Admin876 /// * Current NFT owner877 /// * Address approved by current NFT owner878 ///879 /// # Arguments880 ///881 /// * from: Address that owns token.882 ///883 /// * recipient: Address of token recipient.884 ///885 /// * collection_id.886 ///887 /// * item_id: ID of the item.888 ///889 /// * value: Amount to transfer.890 #[weight = <CommonWeights<T>>::transfer_from()]891 #[transactional]892 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {893 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);894895 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))896 }897898 /// Set off-chain data schema.899 ///900 /// # Permissions901 ///902 /// * Collection Owner903 /// * Collection Admin904 ///905 /// # Arguments906 ///907 /// * collection_id.908 ///909 /// * schema: String representing the offchain data schema.910 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]911 #[transactional]912 pub fn set_variable_meta_data (913 origin,914 collection_id: CollectionId,915 item_id: TokenId,916 data: BoundedVec<u8, CustomDataLimit>,917 ) -> DispatchResultWithPostInfo {918 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);919920 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))921 }922923 /// Set meta_update_permission value for particular collection924 ///925 /// # Permissions926 ///927 /// * Collection Owner.928 ///929 /// # Arguments930 ///931 /// * collection_id: ID of the collection.932 ///933 /// * value: New flag value.934 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]935 #[transactional]936 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {937 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);938 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;939940 ensure!(941 target_collection.meta_update_permission != MetaUpdatePermission::None,942 <CommonError<T>>::MetadataFlagFrozen,943 );944 target_collection.check_is_owner(&sender)?;945946 target_collection.meta_update_permission = value;947948 target_collection.save()949 }950951 /// Set schema standard952 /// ImageURL953 /// Unique954 ///955 /// # Permissions956 ///957 /// * Collection Owner958 /// * Collection Admin959 ///960 /// # Arguments961 ///962 /// * collection_id.963 ///964 /// * schema: SchemaVersion: enum965 #[weight = <SelfWeightOf<T>>::set_schema_version()]966 #[transactional]967 pub fn set_schema_version(968 origin,969 collection_id: CollectionId,970 version: SchemaVersion971 ) -> DispatchResult {972 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);973 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;974 target_collection.check_is_owner_or_admin(&sender)?;975 target_collection.schema_version = version;976977 <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(978 collection_id979 ));980981 target_collection.save()982 }983984 /// Set off-chain data schema.985 ///986 /// # Permissions987 ///988 /// * Collection Owner989 /// * Collection Admin990 ///991 /// # Arguments992 ///993 /// * collection_id.994 ///995 /// * schema: String representing the offchain data schema.996 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]997 #[transactional]998 pub fn set_offchain_schema(999 origin,1000 collection_id: CollectionId,1001 schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,1002 ) -> DispatchResult {1003 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1004 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1005 target_collection.check_is_owner_or_admin(&sender)?;10061007 target_collection.offchain_schema = schema;10081009 <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(1010 collection_id1011 ));10121013 target_collection.save()1014 }10151016 /// Set const on-chain data schema.1017 ///1018 /// # Permissions1019 ///1020 /// * Collection Owner1021 /// * Collection Admin1022 ///1023 /// # Arguments1024 ///1025 /// * collection_id.1026 ///1027 /// * schema: String representing the const on-chain data schema.1028 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1029 #[transactional]1030 pub fn set_const_on_chain_schema (1031 origin,1032 collection_id: CollectionId,1033 schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>1034 ) -> DispatchResult {1035 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1036 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1037 target_collection.check_is_owner_or_admin(&sender)?;10381039 target_collection.const_on_chain_schema = schema;10401041 <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(1042 collection_id1043 ));10441045 target_collection.save()1046 }10471048 /// Set variable on-chain data schema.1049 ///1050 /// # Permissions1051 ///1052 /// * Collection Owner1053 /// * Collection Admin1054 ///1055 /// # Arguments1056 ///1057 /// * collection_id.1058 ///1059 /// * schema: String representing the variable on-chain data schema.1060 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]1061 #[transactional]1062 pub fn set_variable_on_chain_schema (1063 origin,1064 collection_id: CollectionId,1065 schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>1066 ) -> DispatchResult {1067 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1068 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1069 target_collection.check_is_owner_or_admin(&sender)?;10701071 target_collection.variable_on_chain_schema = schema;10721073 <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(1074 collection_id1075 ));10761077 target_collection.save()1078 }10791080 #[weight = <SelfWeightOf<T>>::set_collection_limits()]1081 #[transactional]1082 pub fn set_collection_limits(1083 origin,1084 collection_id: CollectionId,1085 new_limit: CollectionLimits,1086 ) -> DispatchResult {1087 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1088 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1089 target_collection.check_is_owner(&sender)?;1090 let old_limit = &target_collection.limits;10911092 target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10931094 <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1095 collection_id1096 ));10971098 target_collection.save()1099 }1100 }1101}pallets/unique/src/tests.rsdiffbeforeafterboth--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -5,7 +5,7 @@
use up_data_structs::{
COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,
CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,
- TokenId,
+ TokenId, MAX_TOKEN_OWNERSHIP,
};
use frame_support::{assert_noop, assert_ok};
use sp_std::convert::TryInto;
@@ -601,7 +601,7 @@
1
)
.map_err(|e| e.error),
- CommonError::<Test>::TokenValueNotEnough
+ CommonError::<Test>::ApprovedValueTooLow
);
// do approve
@@ -916,7 +916,7 @@
4
)
.map_err(|e| e.error),
- CommonError::<Test>::TokenValueNotEnough
+ CommonError::<Test>::ApprovedValueTooLow
);
});
}
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -263,6 +263,31 @@
pub meta_update_permission: MetaUpdatePermission,
}
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative, MaxEncodedLen)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Default(bound = ""))]
+pub struct CreateCollectionData<AccountId> {
+ #[derivative(Default(value = "CollectionMode::NFT"))]
+ pub mode: CollectionMode,
+ pub access: Option<AccessMode>,
+ #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ pub name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,
+ #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ pub description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,
+ #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ pub token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,
+ #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ pub offchain_schema: BoundedVec<u8, ConstU32<OFFCHAIN_SCHEMA_LIMIT>>,
+ pub schema_version: Option<SchemaVersion>,
+ pub pending_sponsor: Option<AccountId>,
+ pub limits: Option<CollectionLimits>,
+ #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
+ #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))]
+ pub const_on_chain_schema: BoundedVec<u8, ConstU32<CONST_ON_CHAIN_SCHEMA_LIMIT>>,
+ pub meta_update_permission: Option<MetaUpdatePermission>,
+}
+
#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct NftItemType<AccountId> {
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -9,10 +9,14 @@
type Result<T> = core::result::Result<T, DispatchError>;
sp_api::decl_runtime_apis! {
+ #[api_version(2)]
pub trait UniqueApi<CrossAccountId, AccountId> where
AccountId: Decode,
CrossAccountId: pallet_common::account::CrossAccountId<AccountId>,
{
+ #[changed_in(2)]
+ fn token_owner(collection: CollectionId, token: TokenId) -> Result<CrossAccountId>;
+
fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>>;
fn token_exists(collection: CollectionId, token: TokenId) -> Result<bool>;
runtime/src/lib.rsdiffbeforeafterboth--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -23,7 +23,8 @@
use sp_runtime::{
Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
traits::{
- AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify, AccountIdConversion,
+ AccountIdLookup, BlakeTwo256, Block as BlockT, IdentifyAccount, Verify,
+ AccountIdConversion, Zero,
},
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult, MultiSignature, RuntimeAppPublic,
@@ -45,8 +46,9 @@
dispatch::DispatchResult,
PalletId, parameter_types, StorageValue, ConsensusEngineId,
traits::{
- Everything, Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem,
- LockIdentifier, OnUnbalanced, Randomness, FindAuthor,
+ tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything,
+ Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier,
+ OnUnbalanced, Randomness, FindAuthor,
},
weights::{
constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
@@ -69,8 +71,9 @@
use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall};
use fp_rpc::TransactionStatus;
use sp_runtime::{
- traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf},
+ traits::{BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating},
transaction_validity::TransactionValidityError,
+ SaturatedConversion,
};
// pub use pallet_timestamp::Call as TimestampCall;
@@ -82,13 +85,24 @@
use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*};
use xcm_builder::{
AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
- EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocationInverter, NativeAsset,
- ParentAsSuperuser, ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative,
- SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
- SovereignSignedViaLocation, TakeWeightCredit, UsingComponents,
+ EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser,
+ ParentIsDefault, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
+ SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
};
-use xcm_executor::{Config, XcmExecutor};
+use xcm_executor::{Config, XcmExecutor, Assets};
+use sp_std::{marker::PhantomData};
+use xcm::latest::{
+ // Xcm,
+ AssetId::{Concrete},
+ Fungibility::Fungible as XcmFungible,
+ MultiAsset,
+ Error as XcmError,
+};
+use xcm_executor::traits::{MatchesFungible, WeightTrader};
+//use xcm_executor::traits::MatchesFungible;
+use sp_runtime::traits::CheckedConversion;
+
// mod chain_extension;
// use crate::chain_extension::{NFTExtension, Imbalance};
@@ -146,10 +160,10 @@
spec_name: create_runtime_str!("opal"),
impl_name: create_runtime_str!("opal"),
authoring_version: 1,
- spec_version: 914000,
- impl_version: 1,
+ spec_version: 916001,
+ impl_version: 0,
apis: RUNTIME_API_VERSIONS,
- transaction_version: 0,
+ transaction_version: 1,
state_version: 0,
};
@@ -616,12 +630,22 @@
AccountId32Aliases<RelayNetwork, AccountId>,
);
+pub struct OnlySelfCurrency;
+impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
+ fn matches_fungible(a: &MultiAsset) -> Option<B> {
+ match (&a.id, &a.fun) {
+ (Concrete(_), XcmFungible(ref amount)) => CheckedConversion::checked_from(*amount),
+ _ => None,
+ }
+ }
+}
+
/// Means for transacting assets on this chain.
pub type LocalAssetTransactor = CurrencyAdapter<
// Use this currency:
Balances,
// Use this currency when it is a fungible asset matching the given location or name:
- IsConcrete<RelayLocation>,
+ OnlySelfCurrency,
// Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID:
LocationToAccountId,
// Our chain's account ID type (we can't get away without mentioning it explicitly):
@@ -677,6 +701,88 @@
// ^^^ Parent & its unit plurality gets free execution
);
+pub struct UsingOnlySelfCurrencyComponents<
+ WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+ AssetId: Get<MultiLocation>,
+ AccountId,
+ Currency: CurrencyT<AccountId>,
+ OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+>(
+ Weight,
+ Currency::Balance,
+ PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
+);
+impl<
+ WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+ AssetId: Get<MultiLocation>,
+ AccountId,
+ Currency: CurrencyT<AccountId>,
+ OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+ > WeightTrader
+ for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+{
+ fn new() -> Self {
+ Self(0, Zero::zero(), PhantomData)
+ }
+
+ fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
+ let amount = WeightToFee::calc(&weight);
+ let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
+
+ // location to this parachain through relay chain
+ let option1: xcm::v1::AssetId = Concrete(MultiLocation {
+ parents: 1,
+ interior: X1(Parachain(ParachainInfo::parachain_id().into())),
+ });
+ // direct location
+ let option2: xcm::v1::AssetId = Concrete(MultiLocation {
+ parents: 0,
+ interior: Here,
+ });
+
+ let required = if payment.fungible.contains_key(&option1) {
+ (option1, u128_amount).into()
+ } else if payment.fungible.contains_key(&option2) {
+ (option2, u128_amount).into()
+ } else {
+ (Concrete(MultiLocation::default()), u128_amount).into()
+ };
+
+ let unused = payment
+ .checked_sub(required)
+ .map_err(|_| XcmError::TooExpensive)?;
+ self.0 = self.0.saturating_add(weight);
+ self.1 = self.1.saturating_add(amount);
+ Ok(unused)
+ }
+
+ fn refund_weight(&mut self, weight: Weight) -> Option<MultiAsset> {
+ let weight = weight.min(self.0);
+ let amount = WeightToFee::calc(&weight);
+ self.0 -= weight;
+ self.1 = self.1.saturating_sub(amount);
+ let amount: u128 = amount.saturated_into();
+ if amount > 0 {
+ Some((AssetId::get(), amount).into())
+ } else {
+ None
+ }
+ }
+}
+impl<
+ WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+ AssetId: Get<MultiLocation>,
+ AccountId,
+ Currency: CurrencyT<AccountId>,
+ OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+ > Drop
+ for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+{
+ fn drop(&mut self) {
+ OnUnbalanced::on_unbalanced(Currency::issue(self.1));
+ }
+}
+
pub struct XcmConfig;
impl Config for XcmConfig {
type Call = Call;
@@ -689,7 +795,13 @@
type LocationInverter = LocationInverter<Ancestry>;
type Barrier = Barrier;
type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
- type Trader = UsingComponents<IdentityFee<Balance>, RelayLocation, AccountId, Balances, ()>;
+ type Trader = UsingOnlySelfCurrencyComponents<
+ IdentityFee<Balance>,
+ RelayLocation,
+ AccountId,
+ Balances,
+ (),
+ >;
type ResponseHandler = (); // Don't handle responses for now.
type SubscriptionService = PolkadotXcm;
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -64,12 +64,14 @@
"testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",
"testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",
"testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",
+ "testXcmTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/xcmTransfer.test.ts",
"testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",
"testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
"testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts",
+ "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
"polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --input src/interfaces/ --package .",
- "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint ws://localhost:9944 --output src/interfaces/ --package .",
- "polkadot-types": "yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
+ "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
+ "polkadot-types": "yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain"
},
"author": "",
"license": "SEE LICENSE IN ../LICENSE",
tests/src/check-event/createCollectionEvent.test.tsdiffbeforeafterboth--- a/tests/src/check-event/createCollectionEvent.test.ts
+++ b/tests/src/check-event/createCollectionEvent.test.ts
@@ -27,7 +27,7 @@
});
it('Check event from createCollection(): ', async () => {
await usingApi(async (api: ApiPromise) => {
- const tx = api.tx.unique.createCollection([0x31], [0x32], '0x33', 'NFT');
+ const tx = api.tx.unique.createCollectionEx({name: [0x31], description: [0x32], tokenPrefix: '0x33', mode: 'NFT'});
const events = await submitTransactionAsync(alice, tx);
const msg = JSON.stringify(uniqueEventMessage(events));
expect(msg).to.be.contain(checkSection);
tests/src/createCollection.test.tsdiffbeforeafterboth--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -3,12 +3,11 @@
// file 'LICENSE', which is part of this source code package.
//
-import chai from 'chai';
-import chaiAsPromised from 'chai-as-promised';
-import {createCollectionExpectFailure, createCollectionExpectSuccess} from './util/helpers';
+import {expect} from 'chai';
+import privateKey from './substrate/privateKey';
+import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api';
+import {createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo} from './util/helpers';
-chai.use(chaiAsPromised);
-
describe('integration test: ext. createCollection():', () => {
it('Create new NFT collection', async () => {
await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});
@@ -28,6 +27,45 @@
it('Create new ReFungible collection', async () => {
await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});
});
+ it('Create new collection with extra fields', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+ const tx = api.tx.unique.createCollectionEx({
+ mode: {Fungible: 8},
+ access: 'AllowList',
+ name: [1],
+ description: [2],
+ tokenPrefix: '0x000000',
+ offchainSchema: '0x111111',
+ schemaVersion: 'Unique',
+ pendingSponsor: bob.address,
+ limits: {
+ accountTokenOwnershipLimit: 3,
+ },
+ variableOnChainSchema: '0x222222',
+ constOnChainSchema: '0x333333',
+ metaUpdatePermission: 'Admin',
+ });
+ const events = await submitTransactionAsync(alice, tx);
+ const result = getCreateCollectionResult(events);
+
+ const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;
+ expect(collection.owner.toString()).to.equal(alice.address);
+ expect(collection.mode.asFungible.toNumber()).to.equal(8);
+ expect(collection.access.isAllowList).to.be.true;
+ expect(collection.name.map(v => v.toNumber())).to.deep.equal([1]);
+ expect(collection.description.map(v => v.toNumber())).to.deep.equal([2]);
+ expect(collection.tokenPrefix.toString()).to.equal('0x000000');
+ expect(collection.offchainSchema.toString()).to.equal('0x111111');
+ expect(collection.schemaVersion.isUnique).to.be.true;
+ expect(collection.sponsorship.asUnconfirmed.toString()).to.equal(bob.address);
+ expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3);
+ expect(collection.variableOnChainSchema.toString()).to.equal('0x222222');
+ expect(collection.constOnChainSchema.toString()).to.equal('0x333333');
+ expect(collection.metaUpdatePermission.isAdmin).to.be.true;
+ });
+ });
});
describe('(!negative test!) integration test: ext. createCollection():', () => {
@@ -40,4 +78,11 @@
it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {
await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17), mode: {type: 'NFT'}});
});
+ it('fails when bad limits are set', async () => {
+ await usingApi(async api => {
+ const alice = privateKey('//Alice');
+ const tx = api.tx.unique.createCollectionEx({mode: 'NFT', limits: {tokenLimit: 0}});
+ await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/);
+ });
+ });
});
tests/src/interfaces/.gitignorediffbeforeafterboth--- /dev/null
+++ b/tests/src/interfaces/.gitignore
@@ -0,0 +1 @@
+metadata.json
tests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -69,6 +69,10 @@
**/
CollectionDescriptionLimitExceeded: AugmentedError<ApiType>;
/**
+ * Collection limit bounds per collection exceeded
+ **/
+ CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
+ /**
* Collection name can not be longer than 63 char.
**/
CollectionNameLimitExceeded: AugmentedError<ApiType>;
@@ -97,6 +101,10 @@
**/
NoPermission: AugmentedError<ApiType>;
/**
+ * Tried to enable permissions which are only permitted to be disabled
+ **/
+ OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
+ /**
* Collection is not in mint mode.
**/
PublicMintingNotAllowed: AugmentedError<ApiType>;
@@ -107,7 +115,7 @@
/**
* Requested value more than approved.
**/
- TokenValueNotEnough: AugmentedError<ApiType>;
+ ApprovedValueTooLow: AugmentedError<ApiType>;
/**
* Item balance not enough.
**/
@@ -436,10 +444,6 @@
* Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.
**/
CollectionDecimalPointLimitExceeded: AugmentedError<ApiType>;
- /**
- * Collection limit bounds per collection exceeded
- **/
- CollectionLimitBoundsExceeded: AugmentedError<ApiType>;
/**
* This address is not set as sponsor, use setCollectionSponsor first.
**/
@@ -448,10 +452,6 @@
* Length of items properties must be greater than 0.
**/
EmptyArgument: AugmentedError<ApiType>;
- /**
- * Tried to enable permissions which are only permitted to be disabled
- **/
- OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;
/**
* Generic error
**/
tests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -2,7 +2,7 @@
/* eslint-disable */
import type { CumulusPrimitivesParachainInherentParachainInherentData } from './polkadot';
-import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique';
import type { ApiTypes, SubmittableExtrinsic } from '@polkadot/api/types';
import type { Bytes, Compact, Option, U256, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types';
import type { Extrinsic } from '@polkadot/types/interfaces/extrinsics';
@@ -671,6 +671,12 @@
**/
createCollection: AugmentedSubmittable<(collectionName: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], collectionDescription: Vec<u16> | (u16 | AnyNumber | Uint8Array)[], tokenPrefix: Bytes | string | Uint8Array, mode: UpDataStructsCollectionMode | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [Vec<u16>, Vec<u16>, Bytes, UpDataStructsCollectionMode]>;
/**
+ * This method creates a collection
+ *
+ * Prefer it to deprecated [`created_collection`] method
+ **/
+ createCollectionEx: AugmentedSubmittable<(data: UpDataStructsCreateCollectionData | { mode?: any; access?: any; name?: any; description?: any; tokenPrefix?: any; offchainSchema?: any; schemaVersion?: any; pendingSponsor?: any; limits?: any; variableOnChainSchema?: any; constOnChainSchema?: any; metaUpdatePermission?: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [UpDataStructsCreateCollectionData]>;
+ /**
* This method creates a concrete instance of NFT Collection created with CreateCollection method.
*
* # Permissions
tests/src/interfaces/augment-types.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-types.ts
+++ b/tests/src/interfaces/augment-types.ts
@@ -3,7 +3,7 @@
import type { EthereumBlock, EthereumLog, EthereumReceipt, EthereumTransactionLegacyTransaction, EvmCoreErrorExitReason, FpRpcTransactionStatus } from './ethereum';
import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundStatus, CumulusPalletXcmpQueueOutboundStatus, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1PersistedValidationData } from './polkadot';
-import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
+import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique';
import type { BitVec, Bool, Bytes, Data, I128, I16, I256, I32, I64, I8, Json, Null, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types';
import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets';
import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations';
@@ -1021,6 +1021,7 @@
UpDataStructsCollectionLimits: UpDataStructsCollectionLimits;
UpDataStructsCollectionMode: UpDataStructsCollectionMode;
UpDataStructsCollectionStats: UpDataStructsCollectionStats;
+ UpDataStructsCreateCollectionData: UpDataStructsCreateCollectionData;
UpDataStructsCreateItemData: UpDataStructsCreateItemData;
UpDataStructsMetaUpdatePermission: UpDataStructsMetaUpdatePermission;
UpDataStructsSchemaVersion: UpDataStructsSchemaVersion;
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -67,6 +67,20 @@
constOnChainSchema: 'Vec<u8>',
metaUpdatePermission: 'UpDataStructsMetaUpdatePermission',
},
+ UpDataStructsCreateCollectionData: {
+ mode: 'UpDataStructsCollectionMode',
+ access: 'Option<UpDataStructsAccessMode>',
+ name: 'Vec<u16>',
+ description: 'Vec<u16>',
+ tokenPrefix: 'Vec<u8>',
+ offchainSchema: 'Vec<u8>',
+ schemaVersion: 'Option<UpDataStructsSchemaVersion>',
+ pendingSponsor: 'Option<AccountId>',
+ limits: 'Option<UpDataStructsCollectionLimits>',
+ variableOnChainSchema: 'Vec<u8>',
+ constOnChainSchema: 'Vec<u8>',
+ metaUpdatePermission: 'Option<UpDataStructsMetaUpdatePermission>',
+ },
UpDataStructsCollectionStats: {
created: 'u32',
destroyed: 'u32',
@@ -76,7 +90,13 @@
UpDataStructsTokenId: 'u32',
PalletNonfungibleItemData: mkDummy('NftItemData'),
PalletRefungibleItemData: mkDummy('RftItemData'),
- UpDataStructsCollectionMode: mkDummy('CollectionMode'),
+ UpDataStructsCollectionMode: {
+ _enum: {
+ NFT: null,
+ Fungible: 'u32',
+ ReFungible: null,
+ },
+ },
UpDataStructsCreateItemData: mkDummy('CreateItemData'),
UpDataStructsCollectionLimits: {
accountTokenOwnershipLimit: 'Option<u32>',
@@ -101,7 +121,9 @@
UpDataStructsAccessMode: {
_enum: ['Normal', 'AllowList'],
},
- UpDataStructsSchemaVersion: mkDummy('SchemaVersion'),
+ UpDataStructsSchemaVersion: {
+ _enum: ['ImageURL', 'Unique'],
+ },
PalletUnqSchedulerScheduledV2: mkDummy('ScheduledV2'),
PalletUnqSchedulerCallSpec: mkDummy('CallSpec'),
tests/src/interfaces/unique/types.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/types.ts
+++ b/tests/src/interfaces/unique/types.ts
@@ -77,8 +77,11 @@
}
/** @name UpDataStructsCollectionMode */
-export interface UpDataStructsCollectionMode extends Struct {
- readonly dummyCollectionMode: u32;
+export interface UpDataStructsCollectionMode extends Enum {
+ readonly isNft: boolean;
+ readonly isFungible: boolean;
+ readonly asFungible: u32;
+ readonly isReFungible: boolean;
}
/** @name UpDataStructsCollectionStats */
@@ -88,6 +91,22 @@
readonly alive: u32;
}
+/** @name UpDataStructsCreateCollectionData */
+export interface UpDataStructsCreateCollectionData extends Struct {
+ readonly mode: UpDataStructsCollectionMode;
+ readonly access: Option<UpDataStructsAccessMode>;
+ readonly name: Vec<u16>;
+ readonly description: Vec<u16>;
+ readonly tokenPrefix: Bytes;
+ readonly offchainSchema: Bytes;
+ readonly schemaVersion: Option<UpDataStructsSchemaVersion>;
+ readonly pendingSponsor: Option<AccountId>;
+ readonly limits: Option<UpDataStructsCollectionLimits>;
+ readonly variableOnChainSchema: Bytes;
+ readonly constOnChainSchema: Bytes;
+ readonly metaUpdatePermission: Option<UpDataStructsMetaUpdatePermission>;
+}
+
/** @name UpDataStructsCreateItemData */
export interface UpDataStructsCreateItemData extends Struct {
readonly dummyCreateItemData: u32;
@@ -101,8 +120,9 @@
}
/** @name UpDataStructsSchemaVersion */
-export interface UpDataStructsSchemaVersion extends Struct {
- readonly dummySchemaVersion: u32;
+export interface UpDataStructsSchemaVersion extends Enum {
+ readonly isImageUrl: boolean;
+ readonly isUnique: boolean;
}
/** @name UpDataStructsSponsorshipState */
tests/src/substrate/substrate-api.tsdiffbeforeafterboth--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -95,6 +95,32 @@
return TransactionStatus.Fail;
}
+export function executeTransaction(api: ApiPromise, sender: IKeyringPair, transaction: SubmittableExtrinsic<'promise'>): Promise<EventRecord[]> {
+ return new Promise(async (res, rej) => {
+ try {
+ await transaction.signAndSend(sender, ({events, status}) => {
+ if (!status.isInBlock && !status.isFinalized) return;
+ for (const {event} of events) {
+ if (api.events.system.ExtrinsicSuccess.is(event)) {
+ res(events);
+ } else if (api.events.system.ExtrinsicFailed.is(event)) {
+ const {data: [error]} = event;
+ if (error.isModule) {
+ const decoded = api.registry.findMetaError(error.asModule);
+ const {method, section} = decoded;
+ rej(new Error(`${section}.${method}`));
+ } else {
+ rej(new Error(error.toString()));
+ }
+ }
+ }
+ });
+ } catch (e) {
+ rej(e);
+ }
+ });
+}
+
export function
submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
/* eslint no-async-promise-executor: "off" */
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -49,7 +49,7 @@
};
} else if ('Substrate' in input) {
return input;
- }else if ('substrate' in input) {
+ } else if ('substrate' in input) {
return {
Substrate: (input as any).substrate,
};
@@ -116,15 +116,15 @@
export interface IChainLimits {
collectionNumbersLimit: number;
- accountTokenOwnershipLimit: number;
- collectionsAdminsLimit: number;
- customDataLimit: number;
- nftSponsorTransferTimeout: number;
- fungibleSponsorTransferTimeout: number;
- refungibleSponsorTransferTimeout: number;
- offchainSchemaLimit: number;
- variableOnChainSchemaLimit: number;
- constOnChainSchemaLimit: number;
+ accountTokenOwnershipLimit: number;
+ collectionsAdminsLimit: number;
+ customDataLimit: number;
+ nftSponsorTransferTimeout: number;
+ fungibleSponsorTransferTimeout: number;
+ refungibleSponsorTransferTimeout: number;
+ offchainSchemaLimit: number;
+ variableOnChainSchemaLimit: number;
+ constOnChainSchemaLimit: number;
}
export interface IReFungibleTokenDataType {
@@ -283,7 +283,7 @@
modeprm = {refungible: null};
}
- const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+ const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
const events = await submitTransactionAsync(alicePrivateKey, tx);
const result = getCreateCollectionResult(events);
@@ -329,7 +329,7 @@
// Run the CreateCollection transaction
const alicePrivateKey = privateKey('//Alice');
- const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any);
+ const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any});
const events = await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
const result = getCreateCollectionResult(events);
@@ -557,7 +557,7 @@
await usingApi(async (api) => {
- const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+ const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
const events = await submitTransactionAsync(sender, tx);
const result = getGenericResult(events);
@@ -569,7 +569,7 @@
await usingApi(async (api) => {
- const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled);
+ const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled);
const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
const result = getGenericResult(events);
@@ -811,8 +811,7 @@
}
export async function
-getFreeBalance(account: IKeyringPair) : Promise<bigint>
-{
+getFreeBalance(account: IKeyringPair): Promise<bigint> {
let balance = 0n;
await usingApi(async (api) => {
balance = BigInt((await api.query.system.account(account.address)).data.free.toString());
tests/src/xcmTransfer.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/xcmTransfer.test.ts
@@ -0,0 +1,178 @@
+//
+// 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 {WsProvider} from '@polkadot/api';
+import {ApiOptions} from '@polkadot/api/types';
+import {IKeyringPair} from '@polkadot/types/types';
+import privateKey from './substrate/privateKey';
+import usingApi, {submitTransactionAsync} from './substrate/substrate-api';
+import {getGenericResult} from './util/helpers';
+import waitNewBlocks from './substrate/wait-new-blocks';
+import getBalance from './substrate/get-balance';
+import {alicesPublicKey} from './accounts';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+const UNIQUE_CHAIN = 1000;
+const KARURA_CHAIN = 2000;
+const KARURA_PORT = '9946';
+
+describe('Integration test: Exchanging OPL with Karura', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ });
+
+ const karuraApiOptions: ApiOptions = {
+ provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT),
+ };
+
+ await usingApi(async (api) => {
+ const destination = {
+ V0: {
+ X2: [
+ 'Parent',
+ {
+ Parachain: UNIQUE_CHAIN,
+ },
+ ],
+ },
+ };
+
+ const metadata =
+ {
+ name: 'OPL',
+ symbol: 'OPL',
+ decimals: 18,
+ minimalBalance: 1,
+ };
+
+ const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata);
+ const sudoTx = api.tx.sudo.sudo(tx as any);
+ const events = await submitTransactionAsync(alice, sudoTx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+ }, karuraApiOptions);
+ });
+
+ it('Should connect and send OPL to Karura', async () => {
+ let balanceOnKaruraBefore: bigint;
+
+ await usingApi(async (api) => {
+ const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAsset: 0})).toJSON() as any;
+ balanceOnKaruraBefore = free;
+ }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+
+ await usingApi(async (api) => {
+ const destination = {
+ V0: {
+ X2: [
+ 'Parent',
+ {
+ Parachain: KARURA_CHAIN,
+ },
+ ],
+ },
+ };
+
+ const beneficiary = {
+ V0: {
+ X1: {
+ AccountId32: {
+ network: 'Any',
+ id: alice.addressRaw,
+ },
+ },
+ },
+ };
+
+ const assets = {
+ V1: [
+ {
+ id: {
+ Concrete: {
+ parents: 0,
+ interior: 'Here',
+ },
+ },
+ fun: {
+ Fungible: 5000000000,
+ },
+ },
+ ],
+ };
+
+ const feeAssetItem = 0;
+
+ const weightLimit = {
+ Limited: 5000000000,
+ };
+
+ const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit);
+ const events = await submitTransactionAsync(alice, tx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+ });
+
+ await usingApi(async (api) => {
+ // todo do something about instant sealing, where there might not be any new blocks
+ await waitNewBlocks(api, 3);
+ const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAsset: 0})).toJSON() as any;
+ expect(free > balanceOnKaruraBefore).to.be.true;
+ }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+ });
+
+ it('Should connect to Karura and send OPL back', async () => {
+ let balanceBefore: bigint;
+
+ await usingApi(async (api) => {
+ [balanceBefore] = await getBalance(api, [alicesPublicKey]);
+ });
+
+ await usingApi(async (api) => {
+ const destination = {
+ V0: {
+ X3: [
+ 'Parent',
+ {
+ Parachain: UNIQUE_CHAIN,
+ },
+ {
+ AccountId32: {
+ network: 'Any',
+ id: alice.addressRaw,
+ },
+ },
+ ],
+ },
+ };
+
+ const id = {
+ ForeignAsset: 0,
+ };
+
+ const amount = 5000000000;
+ const destWeight = 50000000;
+
+ const tx = api.tx.xTokens.transfer(id, amount, destination, destWeight);
+ const events = await submitTransactionAsync(alice, tx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+ }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)});
+
+ await usingApi(async (api) => {
+ // todo do something about instant sealing, where there might not be any new blocks
+ await waitNewBlocks(api, 3);
+ const [balanceAfter] = await getBalance(api, [alicesPublicKey]);
+ expect(balanceAfter > balanceBefore).to.be.true;
+ });
+ });
+});
\ No newline at end of file