From be539d744c7bb977c7fead4d5c3de02fae6e36bf Mon Sep 17 00:00:00 2001 From: Alex <12645087+lzadjsf@users.noreply.github.com> Date: Fri, 12 Aug 2022 11:13:09 +0000 Subject: [PATCH] Merge branch 'develop' into CI-41-forckless-update-parachain-relay-nodata --- --- a/.docker/Dockerfile-parachain +++ b/.docker/Dockerfile-parachain @@ -9,7 +9,6 @@ ENV PATH="/cargo-home/bin:$PATH" - RUN apt-get update && \ apt-get install -y curl cmake pkg-config libssl-dev git clang && \ apt-get clean && \ @@ -23,11 +22,11 @@ rustup target list --installed && \ rustup show RUN rustup target add wasm32-unknown-unknown --toolchain $RUST_TOOLCHAIN - RUN mkdir /unique_parachain WORKDIR /unique_parachain + # ===== BUILD ====== FROM rust-builder as builder-unique @@ -42,6 +41,7 @@ RUN git clone $REPO_URL -b $BRANCH && \ cargo build --features=$FEATURE --$PROFILE + # ===== BUILD POLKADOT ===== FROM rust-builder as builder-polkadot @@ -60,7 +60,6 @@ FROM ubuntu:20.04 RUN apt-get -y update && \ - apt-get -y upgrade && \ apt-get -y install curl git && \ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash && \ export NVM_DIR="$HOME/.nvm" && \ --- /dev/null +++ b/.docker/docker-compose-master.yaml @@ -0,0 +1,19 @@ +version: "3.5" + +services: + blockchain_nodes: + build: + context: ../ + dockerfile: .docker/Dockerfile-parachain + image: blockchain_nodes + container_name: blockchain_nodes + expose: + - 9944 + - 9933 + ports: + - 127.0.0.1:9944:9944 + - 127.0.0.1:9933:9933 + logging: + options: + max-size: "1m" + max-file: "3" --- /dev/null +++ b/.docker/docker-compose.tmp-master.j2 @@ -0,0 +1,11 @@ +version: "3.5" + +services: + blockchain_nodes: + build: + args: + - "RUST_TOOLCHAIN={{ RUST_TOOLCHAIN }}" + - "BRANCH={{ BRANCH }}" + - "REPO_URL={{ REPO_URL }}" + - "FEATURE={{ FEATURE }}" + - "POLKADOT_BUILD_BRANCH={{ POLKADOT_BUILD_BRANCH }}" --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ * text=auto *.sh text eol=lf +*.ts linguist-detectable=false \ No newline at end of file --- /dev/null +++ b/.github/workflows/build-test-master.yml @@ -0,0 +1,122 @@ +name: MASTER - Build & Test + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + pull_request: + branches: + - master + types: + - opened + - reopened + - synchronize #commit(s) pushed to the pull request + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +#Define Workflow variables +env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + build: + # The type of runner that the job will run on + runs-on: self-hosted-ci + + + name: Build Container, Spin it Up an test + + continue-on-error: true #Do not stop testing of matrix runs failed. + + strategy: + matrix: + include: + - network: "Opal" + features: " " + - network: "Quartz" + features: "--features=quartz-runtime" + - network: "Unique" + features: "--features=unique-runtime" + + steps: + - name: Skip if pull request is in Draft + # `if: github.event.pull_request.draft == true` should be kept here, at + # the step level, rather than at the job level. The latter is not + # recommended because when the PR is moved from "Draft" to "Ready to + # review" the workflow will immediately be passing (since it was skipped), + # even though it hasn't actually ran, since it takes a few seconds for + # the workflow to start. This is also disclosed in: + # https://github.community/t/dont-run-actions-on-draft-pull-requests/16817/17 + # That scenario would open an opportunity for the check to be bypassed: + # 1. Get your PR approved + # 2. Move it to Draft + # 3. Push whatever commits you want + # 4. Move it to "Ready for review"; now the workflow is passing (it was + # skipped) and "Check reviews" is also passing (it won't be updated + # until the workflow is finished) + if: github.event.pull_request.draft == true + run: exit 1 + + - name: Clean Workspace + uses: AutoModality/action-clean@v1.1.0 + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + + - name: Read .env file + uses: xom9ikk/dotenv@v1.0.2 + + - name: Generate ENV related extend file for docker-compose + uses: cuchi/jinja2-action@v1.2.0 + with: + template: .docker/docker-compose.tmp-master.j2 + output_file: .docker/docker-compose.${{ matrix.network }}.yml + variables: | + REPO_URL=${{ github.server_url }}/${{ github.repository }}.git + RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} + POLKADOT_BUILD_BRANCH=${{ env.POLKADOT_BUILD_BRANCH }} + FEATURE=${{ matrix.features }} + BRANCH=${{ github.head_ref }} + + - name: Show build configuration + run: cat .docker/docker-compose.${{ matrix.network }}.yml + + - name: Build the stack + run: docker-compose -f ".docker/docker-compose-master.yml" -f ".docker/docker-compose.${{ matrix.network }}.yml" up -d --build + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Run tests + run: | + cd tests + yarn install + yarn add mochawesome + echo "Ready to start tests" + node scripts/readyness.js + NOW=$(date +%s) && yarn test --reporter mochawesome --reporter-options reportFilename=test-${NOW} + env: + RPC_URL: http://127.0.0.1:9933/ + + - name: Test Report + uses: phoenix-actions/test-reporting@v8 + id: test-report + if: success() || failure() # run this step even if previous step failed + with: + name: Tests ${{ matrix.network }} # Name of the check run which will be created + path: tests/mochawesome-report/test-*.json # Path to test results + reporter: mochawesome-json + fail-on-error: 'false' + + - name: Read output variables + run: | + echo "url is ${{ steps.test-report.outputs.runHtmlUrl }}" + + - name: Stop running containers + if: always() # run this step always + run: docker-compose -f ".docker/docker-compose-master.yml" -f ".docker/docker-compose.${{ matrix.network }}.yml" down --- a/.github/workflows/node_build_test.yml +++ b/.github/workflows/node_build_test.yml @@ -32,7 +32,7 @@ matrix: include: - network: "Opal" - features: "--features=opal-runtime" + features: " " - network: "Quartz" features: "--features=quartz-runtime" - network: "Unique" @@ -57,6 +57,9 @@ if: github.event.pull_request.draft == true run: exit 1 + - name: Clean Workspace + uses: AutoModality/action-clean@v1.1.0 + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 with: @@ -74,7 +77,7 @@ REPO_URL=${{ github.server_url }}/${{ github.repository }}.git RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} POLKADOT_BUILD_BRANCH=${{ env.POLKADOT_BUILD_BRANCH }} - FEATURE='${{ matrix.features }}' + FEATURE=${{ matrix.features }} BRANCH=${{ github.head_ref }} - name: Show build configuration @@ -100,6 +103,7 @@ - name: Test Report uses: phoenix-actions/test-reporting@v8 + id: test-report if: success() || failure() # run this step even if previous step failed with: name: Tests ${{ matrix.network }} # Name of the check run which will be created --- a/Cargo.lock +++ b/Cargo.lock @@ -5291,6 +5291,7 @@ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "derivative", + "evm-coder", "fp-evm-mapping", "fp-rpc", "fp-self-contained", @@ -5352,9 +5353,10 @@ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "unique-runtime-common", + "up-common", "up-data-structs", "up-rpc", + "up-sponsorship", "xcm", "xcm-builder", "xcm-executor", @@ -8605,6 +8607,7 @@ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "derivative", + "evm-coder", "fp-evm-mapping", "fp-rpc", "fp-self-contained", @@ -8666,9 +8669,10 @@ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "unique-runtime-common", + "up-common", "up-data-structs", "up-rpc", + "up-sponsorship", "xcm", "xcm-builder", "xcm-executor", @@ -11881,6 +11885,7 @@ name = "tests" version = "0.1.0" dependencies = [ + "evm-coder", "fp-evm-mapping", "frame-support", "frame-system", @@ -11902,8 +11907,8 @@ "sp-io", "sp-runtime", "sp-std", - "unique-runtime-common", "up-data-structs", + "up-sponsorship", ] [[package]] @@ -12387,7 +12392,6 @@ "sp-core", "sp-rpc", "sp-runtime", - "unique-runtime-common", "up-data-structs", "up-rpc", ] @@ -12541,7 +12545,7 @@ "try-runtime-cli", "unique-rpc", "unique-runtime", - "unique-runtime-common", + "up-common", "up-data-structs", "up-rpc", ] @@ -12590,7 +12594,7 @@ "substrate-frame-rpc-system", "tokio 0.2.25", "uc-rpc", - "unique-runtime-common", + "up-common", "up-data-structs", "up-rpc", ] @@ -12608,6 +12612,7 @@ "cumulus-primitives-timestamp", "cumulus-primitives-utility", "derivative", + "evm-coder", "fp-evm-mapping", "fp-rpc", "fp-self-contained", @@ -12669,41 +12674,16 @@ "sp-transaction-pool", "sp-version", "substrate-wasm-builder", - "unique-runtime-common", + "up-common", "up-data-structs", "up-rpc", + "up-sponsorship", "xcm", "xcm-builder", "xcm-executor", ] [[package]] -name = "unique-runtime-common" -version = "0.9.24" -dependencies = [ - "evm-coder", - "fp-rpc", - "frame-support", - "frame-system", - "pallet-common", - "pallet-evm", - "pallet-fungible", - "pallet-nonfungible", - "pallet-refungible", - "pallet-unique", - "pallet-unique-scheduler", - "parity-scale-codec 3.1.5", - "rmrk-rpc", - "scale-info", - "sp-consensus-aura", - "sp-core", - "sp-runtime", - "sp-std", - "up-data-structs", - "up-sponsorship", -] - -[[package]] name = "universal-hash" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -12732,6 +12712,19 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] +name = "up-common" +version = "0.9.24" +dependencies = [ + "fp-rpc", + "frame-support", + "pallet-evm", + "sp-consensus-aura", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] name = "up-data-structs" version = "0.2.1" dependencies = [ --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ Wider Unique Ecosystem (most of it was developed during Hackusama): -- [SubstraPunks Game hosted on IPFS](https://github.com/usetech-llc/substrapunks) -- [Unique Wallet and UI](https://uniqueapps.usetech.com/#/nft) +- [SubstraPunks Game hosted on IPFS](https://github.com/UniqueNetwork/substrapunks) +- [Unique Wallet and UI](https://wallet.unique.network) - [NFT Asset for Unity Framework](https://github.com/usetech-llc/nft_unity) Please see our [walk-through instructions](doc/hackusama_walk_through.md) to try everything out! --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -5,7 +5,6 @@ edition = "2021" [dependencies] -unique-runtime-common = { default-features = false, path = "../../runtime/common" } pallet-common = { default-features = false, path = '../../pallets/common' } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } up-rpc = { path = "../../primitives/rpc" } --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -253,9 +253,8 @@ ################################################################################ # Local dependencies -[dependencies.unique-runtime-common] -default-features = false -path = "../../runtime/common" +[dependencies.up-common] +path = "../../primitives/common" [dependencies.unique-runtime] path = '../../runtime/unique' --- a/node/cli/src/chain_spec.rs +++ b/node/cli/src/chain_spec.rs @@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize}; use serde_json::map::Map; -use unique_runtime_common::types::*; +use up_common::types::opaque::*; #[cfg(feature = "unique-runtime")] pub use unique_runtime as default_runtime; --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -64,7 +64,7 @@ use sp_runtime::traits::{AccountIdConversion, Block as BlockT}; use std::{io::Write, net::SocketAddr, time::Duration}; -use unique_runtime_common::types::Block; +use up_common::types::opaque::Block; macro_rules! no_runtime_err { ($chain_name:expr) => { --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -63,7 +63,7 @@ use fc_rpc_core::types::FilterPool; use fc_mapping_sync::{MappingSyncWorker, SyncStrategy}; -use unique_runtime_common::types::{AuraId, RuntimeInstance, AccountId, Balance, Index, Hash, Block}; +use up_common::types::opaque::{AuraId, RuntimeInstance, AccountId, Balance, Index, Hash, Block}; // RMRK use up_data_structs::{ --- a/node/rpc/Cargo.toml +++ b/node/rpc/Cargo.toml @@ -49,7 +49,7 @@ fc-mapping-sync = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } pallet-common = { default-features = false, path = "../../pallets/common" } -unique-runtime-common = { default-features = false, path = "../../runtime/common" } +up-common = { path = "../../primitives/common" } pallet-unique = { path = "../../pallets/unique" } uc-rpc = { path = "../../client/rpc" } up-rpc = { path = "../../primitives/rpc" } --- a/node/rpc/src/lib.rs +++ b/node/rpc/src/lib.rs @@ -40,9 +40,8 @@ use sc_service::TransactionPool; use std::{collections::BTreeMap, sync::Arc}; -use unique_runtime_common::types::{ - Hash, AccountId, RuntimeInstance, Index, Block, BlockNumber, Balance, -}; +use up_common::types::opaque::{Hash, AccountId, RuntimeInstance, Index, Block, BlockNumber, Balance}; + // RMRK use up_data_structs::{ RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo, RmrkBaseInfo, --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -25,7 +25,8 @@ use pallet_evm_coder_substrate::dispatch_to_evm; use sp_std::vec::Vec; use up_data_structs::{ - Property, SponsoringRateLimit, OwnerRestrictedSet, AccessMode, CollectionPermissions, + AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, + SponsoringRateLimit, }; use alloc::format; @@ -408,6 +409,28 @@ save(self) } + + /// Check that account is the owner or admin of the collection + /// + /// @param user account to verify + /// @return "true" if account is the owner or admin + fn verify_owner_or_admin(&self, user: address) -> Result { + Ok(check_is_owner_or_admin(user, self) + .map(|_| true) + .unwrap_or(false)) + } + + /// Returns collection type + /// + /// @return `Fungible` or `NFT` or `ReFungible` + fn unique_collection_type(&mut self) -> Result { + let mode = match self.collection.mode { + CollectionMode::Fungible(_) => "Fungible", + CollectionMode::NFT => "NFT", + CollectionMode::ReFungible => "ReFungible", + }; + Ok(mode.into()) + } } fn check_is_owner_or_admin( @@ -462,6 +485,11 @@ pub fn suffix() -> up_data_structs::PropertyKey { property_key_from_bytes(b"suffix").expect(EXPECT_CONVERT_ERROR) } + + /// Key "parentNft". + pub fn parent_nft() -> up_data_structs::PropertyKey { + property_key_from_bytes(b"parentNft").expect(EXPECT_CONVERT_ERROR) + } } /// Values. --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -535,7 +535,8 @@ /// Can't transfer tokens to ethereum zero address AddressIsZero, - /// Target collection doesn't support this operation + + /// The oprtation is not supported UnsupportedOperation, /// Insufficient funds to perform an action @@ -1112,6 +1113,26 @@ sender: &T::CrossAccountId, property_permission: PropertyKeyPermission, ) -> DispatchResult { + Self::set_scoped_property_permission( + collection, + sender, + PropertyScope::None, + property_permission, + ) + } + + /// Set collection property permission with scope. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `scope` - Property scope. + /// * `property_permission` - Property permission. + pub fn set_scoped_property_permission( + collection: &CollectionHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permission: PropertyKeyPermission, + ) -> DispatchResult { collection.check_is_owner_or_admin(sender)?; let all_permissions = CollectionPropertyPermissions::::get(collection.id); @@ -1125,7 +1146,11 @@ CollectionPropertyPermissions::::try_mutate(collection.id, |permissions| { let property_permission = property_permission.clone(); - permissions.try_set(property_permission.key, property_permission.permission) + permissions.try_scoped_set( + scope, + property_permission.key, + property_permission.permission, + ) }) .map_err(>::from)?; @@ -1148,8 +1173,29 @@ sender: &T::CrossAccountId, property_permissions: Vec, ) -> DispatchResult { + Self::set_scoped_token_property_permissions( + collection, + sender, + PropertyScope::None, + property_permissions, + ) + } + + /// Set token property permission with scope. + /// + /// * `collection` - Collection handler. + /// * `sender` - The owner or administrator of the collection. + /// * `scope` - Property scope. + /// * `property_permissions` - Property permissions. + #[transactional] + pub fn set_scoped_token_property_permissions( + collection: &CollectionHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permissions: Vec, + ) -> DispatchResult { for prop_pemission in property_permissions { - Self::set_property_permission(collection, sender, prop_pemission)?; + Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?; } Ok(()) @@ -1353,8 +1399,8 @@ /// Indicates unsupported methods by returning [Error::UnsupportedOperation]. #[macro_export] macro_rules! unsupported { - () => { - Err(>::UnsupportedOperation.into()) + ($runtime:path) => { + Err($crate::Error::<$runtime>::UnsupportedOperation.into()) }; } @@ -1429,6 +1475,9 @@ .saturating_mul(max_selfs.max(1) as u64) .saturating_add(Self::burn_recursively_breadth_raw(max_breadth)) } + + /// The price of retrieving token owner + fn token_owner() -> Weight; } /// Weight info extension trait for refungible pallet. @@ -1567,7 +1616,7 @@ /// /// * `sender` - Must be either the owner of the token or its admin. /// * `token_id` - The token for which the properties are being set. - /// * `properties` - Properties to be set. + /// * `property_permissions` - Property permissions to be set. /// * `budget` - Budget for setting properties. fn set_token_property_permissions( &self, --- a/pallets/fungible/src/common.rs +++ b/pallets/fungible/src/common.rs @@ -103,6 +103,10 @@ // Fungible tokens can't have children 0 } + + fn token_owner() -> Weight { + 0 + } } /// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -31,19 +31,7 @@ ); } -// Selector: 79cc6790 -contract ERC20UniqueExtensions is Dummy, ERC165 { - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 amount) public returns (bool) { - require(false, stub_error); - from; - amount; - dummy = 0; - return false; - } -} - -// Selector: 7d9262e6 +// Selector: 6cf113cd contract Collection is Dummy, ERC165 { // Set collection property. // @@ -268,6 +256,42 @@ mode; dummy = 0; } + + // Check that account is the owner or admin of the collection + // + // @param user account to verify + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin(address) c2282493 + function verifyOwnerOrAdmin(address user) public view returns (bool) { + require(false, stub_error); + user; + dummy; + return false; + } + + // Returns collection type + // + // @return `Fungible` or `NFT` or `ReFungible` + // + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() public returns (string memory) { + require(false, stub_error); + dummy = 0; + return ""; + } +} + +// Selector: 79cc6790 +contract ERC20UniqueExtensions is Dummy, ERC165 { + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 amount) public returns (bool) { + require(false, stub_error); + from; + amount; + dummy = 0; + return false; + } } // Selector: 942e8b22 --- a/pallets/nonfungible/src/benchmarking.rs +++ b/pallets/nonfungible/src/benchmarking.rs @@ -17,11 +17,14 @@ use super::*; use crate::{Pallet, Config, NonfungibleHandle}; -use sp_std::prelude::*; -use pallet_common::benchmarking::{create_collection_raw, property_key, property_value}; use frame_benchmarking::{benchmarks, account}; +use pallet_common::{ + bench_init, + benchmarking::{create_collection_raw, property_key, property_value}, + CommonCollectionOperations, +}; +use sp_std::prelude::*; use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited}; -use pallet_common::bench_init; const SEED: u32 = 1; @@ -208,4 +211,13 @@ >::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?; let to_delete = (0..b).map(|k| property_key(k as usize)).collect::>(); }: {>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?} + + token_owner { + bench_init!{ + owner: sub; collection: collection(owner); + owner: cross_from_sub; + }; + let item = create_max_item(&collection, &owner, owner.clone())?; + + }: {collection.token_owner(item)} } --- a/pallets/nonfungible/src/common.rs +++ b/pallets/nonfungible/src/common.rs @@ -118,6 +118,10 @@ >::burn_recursively_breadth_plus_self_plus_self_per_each_raw(amount) .saturating_sub(Self::burn_recursively_self_raw().saturating_mul(amount as u64 + 1)) } + + fn token_owner() -> Weight { + >::token_owner() + } } fn map_create_data( --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -784,6 +784,23 @@ >::set_token_property_permissions(collection, sender, property_permissions) } + /// Set property permissions for the token with scope. + /// + /// Sender should be the owner or admin of token's collection. + pub fn set_scoped_token_property_permissions( + collection: &CollectionHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permissions: Vec, + ) -> DispatchResult { + >::set_scoped_token_property_permissions( + collection, + sender, + scope, + property_permissions, + ) + } + /// Set property permissions for the collection. /// /// Sender should be the owner or admin of the collection. --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -373,49 +373,7 @@ } } -// Selector: 780e9d63 -contract ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid NFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) - // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) public view returns (uint256) { - require(false, stub_error); - index; - dummy; - return 0; - } - - // @dev Not implemented - // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - public - view - returns (uint256) - { - require(false, stub_error); - owner; - index; - dummy; - return 0; - } - - // @notice Count NFTs tracked by this contract - // @return A count of valid NFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; - } -} - -// Selector: 7d9262e6 +// Selector: 6cf113cd contract Collection is Dummy, ERC165 { // Set collection property. // @@ -640,6 +598,72 @@ mode; dummy = 0; } + + // Check that account is the owner or admin of the collection + // + // @param user account to verify + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin(address) c2282493 + function verifyOwnerOrAdmin(address user) public view returns (bool) { + require(false, stub_error); + user; + dummy; + return false; + } + + // Returns collection type + // + // @return `Fungible` or `NFT` or `ReFungible` + // + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() public returns (string memory) { + require(false, stub_error); + dummy = 0; + return ""; + } +} + +// Selector: 780e9d63 +contract ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid NFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) public view returns (uint256) { + require(false, stub_error); + index; + dummy; + return 0; + } + + // @dev Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + returns (uint256) + { + require(false, stub_error); + owner; + index; + dummy; + return 0; + } + + // @notice Count NFTs tracked by this contract + // @return A count of valid NFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } } // Selector: d74d154f --- a/pallets/nonfungible/src/weights.rs +++ b/pallets/nonfungible/src/weights.rs @@ -3,7 +3,7 @@ //! Autogenerated weights for pallet_nonfungible //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-08-01, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: @@ -46,6 +46,7 @@ fn set_token_property_permissions(b: u32, ) -> Weight; fn set_token_properties(b: u32, ) -> Weight; fn delete_token_properties(b: u32, ) -> Weight; + fn token_owner() -> Weight; } /// Weights for pallet_nonfungible using the Substrate node and recommended hardware. @@ -56,7 +57,7 @@ // Storage: Nonfungible TokenData (r:0 w:1) // Storage: Nonfungible Owned (r:0 w:1) fn create_item() -> Weight { - (20_328_000 as Weight) + (20_909_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -65,9 +66,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (10_134_000 as Weight) - // Standard Error: 3_000 - .saturating_add((4_927_000 as Weight).saturating_mul(b as Weight)) + (12_601_000 as Weight) + // Standard Error: 1_000 + .saturating_add((4_920_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(b as Weight))) @@ -77,9 +78,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items_ex(b: u32, ) -> Weight { - (5_710_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_578_000 as Weight).saturating_mul(b as Weight)) + (0 as Weight) + // Standard Error: 3_000 + .saturating_add((7_734_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -93,7 +94,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_item() -> Weight { - (28_433_000 as Weight) + (29_746_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -105,7 +106,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_recursively_self_raw() -> Weight { - (34_435_000 as Weight) + (36_077_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -119,8 +120,8 @@ // Storage: Common CollectionById (r:1 w:0) fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_539_000 - .saturating_add((304_456_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_605_000 + .saturating_add((312_391_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(6 as Weight)) @@ -131,14 +132,14 @@ // Storage: Nonfungible Allowance (r:1 w:0) // Storage: Nonfungible Owned (r:0 w:2) fn transfer() -> Weight { - (24_376_000 as Weight) + (25_248_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Nonfungible TokenData (r:1 w:0) // Storage: Nonfungible Allowance (r:1 w:1) fn approve() -> Weight { - (15_890_000 as Weight) + (16_321_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -147,7 +148,7 @@ // Storage: Nonfungible AccountBalance (r:2 w:2) // Storage: Nonfungible Owned (r:0 w:2) fn transfer_from() -> Weight { - (28_634_000 as Weight) + (29_325_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -159,15 +160,15 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (32_201_000 as Weight) + (33_323_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 57_000 - .saturating_add((15_232_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 62_000 + .saturating_add((16_222_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -175,8 +176,8 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_648_000 - .saturating_add((288_654_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_750_000 + .saturating_add((304_476_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -184,11 +185,16 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_632_000 - .saturating_add((289_190_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_638_000 + .saturating_add((294_096_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Nonfungible TokenData (r:1 w:0) + fn token_owner() -> Weight { + (2_986_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + } } // For backwards compatibility and tests @@ -198,7 +204,7 @@ // Storage: Nonfungible TokenData (r:0 w:1) // Storage: Nonfungible Owned (r:0 w:1) fn create_item() -> Weight { - (20_328_000 as Weight) + (20_909_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -207,9 +213,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (10_134_000 as Weight) - // Standard Error: 3_000 - .saturating_add((4_927_000 as Weight).saturating_mul(b as Weight)) + (12_601_000 as Weight) + // Standard Error: 1_000 + .saturating_add((4_920_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(b as Weight))) @@ -219,9 +225,9 @@ // Storage: Nonfungible TokenData (r:0 w:4) // Storage: Nonfungible Owned (r:0 w:4) fn create_multiple_items_ex(b: u32, ) -> Weight { - (5_710_000 as Weight) - // Standard Error: 4_000 - .saturating_add((7_578_000 as Weight).saturating_mul(b as Weight)) + (0 as Weight) + // Standard Error: 3_000 + .saturating_add((7_734_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -235,7 +241,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_item() -> Weight { - (28_433_000 as Weight) + (29_746_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -247,7 +253,7 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_recursively_self_raw() -> Weight { - (34_435_000 as Weight) + (36_077_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -261,8 +267,8 @@ // Storage: Common CollectionById (r:1 w:0) fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_539_000 - .saturating_add((304_456_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_605_000 + .saturating_add((312_391_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) @@ -273,14 +279,14 @@ // Storage: Nonfungible Allowance (r:1 w:0) // Storage: Nonfungible Owned (r:0 w:2) fn transfer() -> Weight { - (24_376_000 as Weight) + (25_248_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Nonfungible TokenData (r:1 w:0) // Storage: Nonfungible Allowance (r:1 w:1) fn approve() -> Weight { - (15_890_000 as Weight) + (16_321_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -289,7 +295,7 @@ // Storage: Nonfungible AccountBalance (r:2 w:2) // Storage: Nonfungible Owned (r:0 w:2) fn transfer_from() -> Weight { - (28_634_000 as Weight) + (29_325_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -301,15 +307,15 @@ // Storage: Nonfungible Owned (r:0 w:1) // Storage: Nonfungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (32_201_000 as Weight) + (33_323_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 57_000 - .saturating_add((15_232_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 62_000 + .saturating_add((16_222_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -317,8 +323,8 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_648_000 - .saturating_add((288_654_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_750_000 + .saturating_add((304_476_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -326,9 +332,14 @@ // Storage: Nonfungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_632_000 - .saturating_add((289_190_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_638_000 + .saturating_add((294_096_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: Nonfungible TokenData (r:1 w:0) + fn token_owner() -> Weight { + (2_986_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + } } --- a/pallets/refungible/src/benchmarking.rs +++ b/pallets/refungible/src/benchmarking.rs @@ -17,25 +17,26 @@ use super::*; use crate::{Pallet, Config, RefungibleHandle}; +use core::convert::TryInto; +use core::iter::IntoIterator; +use frame_benchmarking::{benchmarks, account}; +use pallet_common::{ + bench_init, + benchmarking::{create_collection_raw, property_key, property_value, create_data}, +}; +use sp_core::H160; use sp_std::prelude::*; -use pallet_common::benchmarking::{create_collection_raw, property_key, property_value, create_data}; -use frame_benchmarking::{benchmarks, account}; use up_data_structs::{ CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT, budget::Unlimited, }; -use pallet_common::bench_init; -use core::convert::TryInto; -use core::iter::IntoIterator; const SEED: u32 = 1; fn create_max_item_data( users: impl IntoIterator, ) -> CreateRefungibleExData { - let const_data = create_data::(); CreateRefungibleExData { - const_data, users: users .into_iter() .collect::>() @@ -44,6 +45,7 @@ properties: Default::default(), } } + fn create_max_item( collection: &RefungibleHandle, sender: &T::CrossAccountId, @@ -59,11 +61,12 @@ ) -> Result, DispatchError> { create_collection_raw( owner, - CollectionMode::NFT, + CollectionMode::ReFungible, >::init_collection, RefungibleHandle::cast, ) } + benchmarks! { create_item { bench_init!{ @@ -277,4 +280,21 @@ }; let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?; }: {>::repartition(&collection, &owner, item, 200)?} + + set_parent_nft_unchecked { + bench_init!{ + owner: sub; collection: collection(owner); + sender: cross_from_sub(owner); owner: cross_sub; + }; + let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?; + + }: {>::set_parent_nft_unchecked(&collection, item, owner, T::CrossAccountId::from_eth(H160::default()))?} + + token_owner { + bench_init!{ + owner: sub; collection: collection(owner); + sender: cross_from_sub(owner); owner: cross_sub; + }; + let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?; + }: {>::token_owner(collection.id, item)} } --- a/pallets/refungible/src/common.rs +++ b/pallets/refungible/src/common.rs @@ -148,6 +148,10 @@ // Refungible token can't have children 0 } + + fn token_owner() -> Weight { + >::token_owner() + } } fn map_create_data( --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -41,8 +41,8 @@ use sp_core::H160; use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec}; use up_data_structs::{ - CollectionId, CollectionPropertiesVec, Property, PropertyKey, PropertyKeyPermission, - PropertyPermission, TokenId, + CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey, + PropertyKeyPermission, PropertyPermission, TokenId, }; use crate::{ @@ -413,7 +413,7 @@ } /// Returns amount of pieces of `token` that `owner` have -fn balance( +pub fn balance( collection: &RefungibleHandle, token: TokenId, owner: &T::CrossAccountId, @@ -424,7 +424,7 @@ } /// Throws if `owner_balance` is lower than total amount of `token` pieces -fn ensure_single_owner( +pub fn ensure_single_owner( collection: &RefungibleHandle, token: TokenId, owner_balance: u128, @@ -788,6 +788,16 @@ .map_err(dispatch_to_evm::)?; Ok(true) } + + /// Returns EVM address for refungible token + /// + /// @param token ID of the token + fn token_contract_address(&self, token: uint256) -> Result
{ + Ok(T::EvmTokenAddressMapping::token_to_address( + self.id, + token.try_into().map_err(|_| "token id overflow")?, + )) + } } #[solidity_interface( --- a/pallets/refungible/src/erc_token.rs +++ b/pallets/refungible/src/erc_token.rs @@ -20,29 +20,95 @@ //! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods. extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::format; + use core::{ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, ops::Deref, }; -use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight}; +use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight}; use pallet_common::{ CommonWeightInfo, - erc::{CommonEvmHandler, PrecompileResult}, + erc::{CommonEvmHandler, PrecompileResult, static_property::key}, + eth::map_eth_to_id, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::{call, dispatch_to_evm, WithRecorder}; use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _}; +use sp_core::H160; use sp_std::vec::Vec; -use up_data_structs::TokenId; +use up_data_structs::{mapping::TokenAddressMapping, PropertyScope, TokenId}; use crate::{ Allowance, Balance, common::CommonWeights, Config, Pallet, RefungibleHandle, SelfWeightOf, - weights::WeightInfo, TotalSupply, + TokenProperties, TotalSupply, weights::WeightInfo, }; pub struct RefungibleTokenHandle(pub RefungibleHandle, pub TokenId); +#[solidity_interface(name = "ERC1633")] +impl RefungibleTokenHandle { + fn parent_token(&self) -> Result
{ + self.consume_store_reads(2)?; + let props = >::get((self.id, self.1)); + let key = key::parent_nft(); + + let key_scoped = PropertyScope::Eth + .apply(key) + .expect("property key shouldn't exceed length limit"); + if let Some(value) = props.get(&key_scoped) { + Ok(H160::from_slice(value.as_slice())) + } else { + Ok(*T::CrossTokenAddressMapping::token_to_address(self.id, self.1).as_eth()) + } + } + + fn parent_token_id(&self) -> Result { + self.consume_store_reads(2)?; + let props = >::get((self.id, self.1)); + let key = key::parent_nft(); + + let key_scoped = PropertyScope::Eth + .apply(key) + .expect("property key shouldn't exceed length limit"); + if let Some(value) = props.get(&key_scoped) { + let nft_token_address = H160::from_slice(value.as_slice()); + let nft_token_account = T::CrossAccountId::from_eth(nft_token_address); + let (_, token_id) = T::CrossTokenAddressMapping::address_to_token(&nft_token_account) + .ok_or("parent NFT should contain NFT token address")?; + + Ok(token_id.into()) + } else { + Ok(self.1.into()) + } + } +} + +#[solidity_interface(name = "ERC1633UniqueExtensions")] +impl RefungibleTokenHandle { + #[solidity(rename_selector = "setParentNFT")] + #[weight(>::token_owner() + >::set_parent_nft_unchecked())] + fn set_parent_nft( + &mut self, + caller: caller, + collection: address, + nft_id: uint256, + ) -> Result { + self.consume_store_reads(1)?; + let caller = T::CrossAccountId::from_eth(caller); + let nft_collection = map_eth_to_id(&collection).ok_or("collection not found")?; + let nft_token = nft_id.try_into()?; + + >::set_parent_nft(&self.0, self.1, caller, nft_collection, nft_token) + .map_err(dispatch_to_evm::)?; + + Ok(true) + } +} + #[derive(ToLog)] pub enum ERC20Events { /// @dev This event is emitted when the amount of tokens (value) is sent @@ -120,7 +186,7 @@ .weight_calls_budget(>::find_parent()); >::transfer(self, &caller, &to, self.1, amount, &budget) - .map_err(|_| "transfer error")?; + .map_err(dispatch_to_evm::)?; Ok(true) } @@ -239,7 +305,10 @@ } } -#[solidity_interface(name = "UniqueRefungibleToken", is(ERC20, ERC20UniqueExtensions,))] +#[solidity_interface( + name = "UniqueRefungibleToken", + is(ERC20, ERC20UniqueExtensions, ERC1633, ERC1633UniqueExtensions) +)] impl RefungibleTokenHandle where T::AccountId: From<[u8; 32]> {} generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true); --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -99,8 +99,12 @@ use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use pallet_evm_coder_substrate::WithRecorder; use pallet_common::{ - CommonCollectionOperations, Error as CommonError, Event as CommonEvent, - eth::collection_id_to_address, Pallet as PalletCommon, + CollectionHandle, CommonCollectionOperations, + dispatch::CollectionDispatch, + erc::static_property::{key, property_value_from_bytes}, + Error as CommonError, + eth::collection_id_to_address, + Event as CommonEvent, Pallet as PalletCommon, }; use pallet_structure::Pallet as PalletStructure; use scale_info::TypeInfo; @@ -108,10 +112,10 @@ use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome}; use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap}; use up_data_structs::{ - AccessMode, budget::Budget, CollectionId, CreateCollectionData, CustomDataLimit, - mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, MAX_ITEMS_PER_BATCH, TokenId, Property, - PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, - TrySetProperty, CollectionPropertiesVec, + AccessMode, budget::Budget, CollectionId, CollectionMode, CollectionPropertiesVec, + CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH, + MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission, + PropertyScope, PropertyValue, TokenId, TrySetProperty, }; use frame_support::BoundedBTreeMap; use derivative::Derivative; @@ -1341,6 +1345,20 @@ >::set_token_property_permissions(collection, sender, property_permissions) } + pub fn set_scoped_token_property_permissions( + collection: &RefungibleHandle, + sender: &T::CrossAccountId, + scope: PropertyScope, + property_permissions: Vec, + ) -> DispatchResult { + >::set_scoped_token_property_permissions( + collection, + sender, + scope, + property_permissions, + ) + } + /// Returns 10 token in no particular order. /// /// There is no direct way to get token holders in ascending order, @@ -1362,4 +1380,68 @@ Some(res) } } + + /// Sets the NFT token as a parent for the RFT token + /// + /// Throws if `sender` is not the owner of the NFT token. + /// Throws if `sender` is not the owner of all of the RFT token pieces. + pub fn set_parent_nft( + collection: &RefungibleHandle, + rft_token_id: TokenId, + sender: T::CrossAccountId, + nft_collection: CollectionId, + nft_token: TokenId, + ) -> DispatchResult { + let handle = >::try_get(nft_collection)?; + if handle.mode != CollectionMode::NFT { + return Err("Only NFT token could be parent to RFT".into()); + } + let dispatch = T::CollectionDispatch::dispatch(handle); + let dispatch = dispatch.as_dyn(); + + let owner = dispatch.token_owner(nft_token).ok_or("owner not found")?; + if owner != sender { + return Err("Only owned token could be set as parent".into()); + } + + let nft_token_address = + T::CrossTokenAddressMapping::token_to_address(nft_collection, nft_token); + + Self::set_parent_nft_unchecked(collection, rft_token_id, sender, nft_token_address) + } + + /// Sets the NFT token as a parent for the RFT token + /// + /// `sender` should be the owner of the NFT token. + /// Throws if `sender` is not the owner of all of the RFT token pieces. + pub fn set_parent_nft_unchecked( + collection: &RefungibleHandle, + rft_token_id: TokenId, + sender: T::CrossAccountId, + nft_token_address: T::CrossAccountId, + ) -> DispatchResult { + let owner_balance = >::get((collection.id, rft_token_id, &sender)); + let total_supply = >::get((collection.id, rft_token_id)); + if total_supply != owner_balance { + return Err("token has multiple owners".into()); + } + + let parent_nft_property_key = key::parent_nft(); + + let parent_nft_property_value = + property_value_from_bytes(&nft_token_address.as_eth().to_fixed_bytes()) + .expect("address should fit in value length limit"); + + >::set_scoped_token_property( + collection.id, + rft_token_id, + PropertyScope::Eth, + Property { + key: parent_nft_property_key, + value: parent_nft_property_value, + }, + )?; + + Ok(()) + } } --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -371,49 +371,7 @@ } } -// Selector: 780e9d63 -contract ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid RFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) - // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) public view returns (uint256) { - require(false, stub_error); - index; - dummy; - return 0; - } - - // Not implemented - // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - public - view - returns (uint256) - { - require(false, stub_error); - owner; - index; - dummy; - return 0; - } - - // @notice Count RFTs tracked by this contract - // @return A count of valid RFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() public view returns (uint256) { - require(false, stub_error); - dummy; - return 0; - } -} - -// Selector: 7d9262e6 +// Selector: 6cf113cd contract Collection is Dummy, ERC165 { // Set collection property. // @@ -638,9 +596,75 @@ mode; dummy = 0; } + + // Check that account is the owner or admin of the collection + // + // @param user account to verify + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin(address) c2282493 + function verifyOwnerOrAdmin(address user) public view returns (bool) { + require(false, stub_error); + user; + dummy; + return false; + } + + // Returns collection type + // + // @return `Fungible` or `NFT` or `ReFungible` + // + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() public returns (string memory) { + require(false, stub_error); + dummy = 0; + return ""; + } +} + +// Selector: 780e9d63 +contract ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid RFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) public view returns (uint256) { + require(false, stub_error); + index; + dummy; + return 0; + } + + // Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + returns (uint256) + { + require(false, stub_error); + owner; + index; + dummy; + return 0; + } + + // @notice Count RFTs tracked by this contract + // @return A count of valid RFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } } -// Selector: d74d154f +// Selector: 7c3bef89 contract ERC721UniqueExtensions is Dummy, ERC165 { // @notice Transfer ownership of an RFT // @dev Throws unless `msg.sender` is the current owner. Throws if `to` @@ -719,6 +743,18 @@ dummy = 0; return false; } + + // Returns EVM address for refungible token + // + // @param token ID of the token + // + // Selector: tokenContractAddress(uint256) ab76fac6 + function tokenContractAddress(uint256 token) public view returns (address) { + require(false, stub_error); + token; + dummy; + return 0x0000000000000000000000000000000000000000; + } } contract UniqueRefungible is --- a/pallets/refungible/src/stubs/UniqueRefungibleToken.sol +++ b/pallets/refungible/src/stubs/UniqueRefungibleToken.sol @@ -31,6 +31,38 @@ ); } +// Selector: 042f1106 +contract ERC1633UniqueExtensions is Dummy, ERC165 { + // Selector: setParentNFT(address,uint256) 042f1106 + function setParentNFT(address collection, uint256 nftId) + public + returns (bool) + { + require(false, stub_error); + collection; + nftId; + dummy = 0; + return false; + } +} + +// Selector: 5755c3f2 +contract ERC1633 is Dummy, ERC165 { + // Selector: parentToken() 80a54001 + function parentToken() public view returns (address) { + require(false, stub_error); + dummy; + return 0x0000000000000000000000000000000000000000; + } + + // Selector: parentTokenId() d7f083f3 + function parentTokenId() public view returns (uint256) { + require(false, stub_error); + dummy; + return 0; + } +} + // Selector: 942e8b22 contract ERC20 is Dummy, ERC165, ERC20Events { // @return the name of the token. @@ -178,4 +210,11 @@ } } -contract UniqueRefungibleToken is Dummy, ERC165, ERC20, ERC20UniqueExtensions {} +contract UniqueRefungibleToken is + Dummy, + ERC165, + ERC20, + ERC20UniqueExtensions, + ERC1633, + ERC1633UniqueExtensions +{} --- a/pallets/refungible/src/weights.rs +++ b/pallets/refungible/src/weights.rs @@ -3,7 +3,7 @@ //! Autogenerated weights for pallet_refungible //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-07-20, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-08-01, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: @@ -53,6 +53,8 @@ fn set_token_properties(b: u32, ) -> Weight; fn delete_token_properties(b: u32, ) -> Weight; fn repartition_item() -> Weight; + fn set_parent_nft_unchecked() -> Weight; + fn token_owner() -> Weight; } /// Weights for pallet_refungible using the Substrate node and recommended hardware. @@ -65,7 +67,7 @@ // Storage: Refungible TokenData (r:0 w:1) // Storage: Refungible Owned (r:0 w:1) fn create_item() -> Weight { - (21_310_000 as Weight) + (25_197_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -76,9 +78,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (9_552_000 as Weight) + (10_852_000 as Weight) // Standard Error: 2_000 - .saturating_add((7_056_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((8_087_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(b as Weight))) @@ -90,9 +92,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight { - (4_857_000 as Weight) + (9_978_000 as Weight) // Standard Error: 2_000 - .saturating_add((9_838_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((10_848_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -105,9 +107,9 @@ // Storage: Refungible Balance (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight { - (11_335_000 as Weight) + (15_419_000 as Weight) // Standard Error: 2_000 - .saturating_add((6_784_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((7_813_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -118,7 +120,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn burn_item_partial() -> Weight { - (21_239_000 as Weight) + (25_578_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -130,13 +132,13 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_item_fully() -> Weight { - (29_426_000 as Weight) + (33_593_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } // Storage: Refungible Balance (r:2 w:2) fn transfer_normal() -> Weight { - (17_743_000 as Weight) + (21_049_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -144,7 +146,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_creating() -> Weight { - (20_699_000 as Weight) + (24_646_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -152,7 +154,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_removing() -> Weight { - (22_833_000 as Weight) + (26_570_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -160,21 +162,21 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_creating_removing() -> Weight { - (24_936_000 as Weight) + (28_906_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Refungible Balance (r:1 w:0) // Storage: Refungible Allowance (r:0 w:1) fn approve() -> Weight { - (13_446_000 as Weight) + (16_451_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Refungible Allowance (r:1 w:1) // Storage: Refungible Balance (r:2 w:2) fn transfer_from_normal() -> Weight { - (24_777_000 as Weight) + (29_545_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -183,7 +185,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_creating() -> Weight { - (28_483_000 as Weight) + (33_392_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -192,7 +194,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_removing() -> Weight { - (29_896_000 as Weight) + (35_446_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -201,7 +203,7 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_from_creating_removing() -> Weight { - (32_070_000 as Weight) + (37_762_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -214,15 +216,15 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (36_789_000 as Weight) + (42_620_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 62_000 - .saturating_add((15_803_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 65_000 + .saturating_add((16_513_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -230,8 +232,8 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_668_000 - .saturating_add((302_308_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_583_000 + .saturating_add((291_392_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -239,18 +241,31 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_619_000 - .saturating_add((294_574_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_699_000 + .saturating_add((293_270_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Refungible TotalSupply (r:1 w:1) // Storage: Refungible Balance (r:1 w:1) fn repartition_item() -> Weight { - (8_325_000 as Weight) + (19_206_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } + // Storage: Refungible Balance (r:1 w:0) + // Storage: Refungible TotalSupply (r:1 w:0) + // Storage: Refungible TokenProperties (r:1 w:1) + fn set_parent_nft_unchecked() -> Weight { + (10_189_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Refungible Balance (r:2 w:0) + fn token_owner() -> Weight { + (8_205_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + } } // For backwards compatibility and tests @@ -262,7 +277,7 @@ // Storage: Refungible TokenData (r:0 w:1) // Storage: Refungible Owned (r:0 w:1) fn create_item() -> Weight { - (21_310_000 as Weight) + (25_197_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -273,9 +288,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items(b: u32, ) -> Weight { - (9_552_000 as Weight) + (10_852_000 as Weight) // Standard Error: 2_000 - .saturating_add((7_056_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((8_087_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((4 as Weight).saturating_mul(b as Weight))) @@ -287,9 +302,9 @@ // Storage: Refungible TokenData (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_items(b: u32, ) -> Weight { - (4_857_000 as Weight) + (9_978_000 as Weight) // Standard Error: 2_000 - .saturating_add((9_838_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((10_848_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -302,9 +317,9 @@ // Storage: Refungible Balance (r:0 w:4) // Storage: Refungible Owned (r:0 w:4) fn create_multiple_items_ex_multiple_owners(b: u32, ) -> Weight { - (11_335_000 as Weight) + (15_419_000 as Weight) // Standard Error: 2_000 - .saturating_add((6_784_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((7_813_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -315,7 +330,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn burn_item_partial() -> Weight { - (21_239_000 as Weight) + (25_578_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -327,13 +342,13 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_item_fully() -> Weight { - (29_426_000 as Weight) + (33_593_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } // Storage: Refungible Balance (r:2 w:2) fn transfer_normal() -> Weight { - (17_743_000 as Weight) + (21_049_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -341,7 +356,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_creating() -> Weight { - (20_699_000 as Weight) + (24_646_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -349,7 +364,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_removing() -> Weight { - (22_833_000 as Weight) + (26_570_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -357,21 +372,21 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_creating_removing() -> Weight { - (24_936_000 as Weight) + (28_906_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Refungible Balance (r:1 w:0) // Storage: Refungible Allowance (r:0 w:1) fn approve() -> Weight { - (13_446_000 as Weight) + (16_451_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Refungible Allowance (r:1 w:1) // Storage: Refungible Balance (r:2 w:2) fn transfer_from_normal() -> Weight { - (24_777_000 as Weight) + (29_545_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -380,7 +395,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_creating() -> Weight { - (28_483_000 as Weight) + (33_392_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -389,7 +404,7 @@ // Storage: Refungible AccountBalance (r:1 w:1) // Storage: Refungible Owned (r:0 w:1) fn transfer_from_removing() -> Weight { - (29_896_000 as Weight) + (35_446_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -398,7 +413,7 @@ // Storage: Refungible AccountBalance (r:2 w:2) // Storage: Refungible Owned (r:0 w:2) fn transfer_from_creating_removing() -> Weight { - (32_070_000 as Weight) + (37_762_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -411,15 +426,15 @@ // Storage: Refungible Owned (r:0 w:1) // Storage: Refungible TokenProperties (r:0 w:1) fn burn_from() -> Weight { - (36_789_000 as Weight) + (42_620_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } // Storage: Common CollectionPropertyPermissions (r:1 w:1) fn set_token_property_permissions(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 62_000 - .saturating_add((15_803_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 65_000 + .saturating_add((16_513_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -427,8 +442,8 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn set_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_668_000 - .saturating_add((302_308_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_583_000 + .saturating_add((291_392_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -436,16 +451,29 @@ // Storage: Refungible TokenProperties (r:1 w:1) fn delete_token_properties(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_619_000 - .saturating_add((294_574_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 1_699_000 + .saturating_add((293_270_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Refungible TotalSupply (r:1 w:1) // Storage: Refungible Balance (r:1 w:1) fn repartition_item() -> Weight { - (8_325_000 as Weight) + (19_206_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } + // Storage: Refungible Balance (r:1 w:0) + // Storage: Refungible TotalSupply (r:1 w:0) + // Storage: Refungible TokenProperties (r:1 w:1) + fn set_parent_nft_unchecked() -> Weight { + (10_189_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Refungible Balance (r:2 w:0) + fn token_owner() -> Weight { + (8_205_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + } } --- a/pallets/unique/src/eth/mod.rs +++ b/pallets/unique/src/eth/mod.rs @@ -17,26 +17,29 @@ //! Implementation of CollectionHelpers contract. use core::marker::PhantomData; -use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*}; use ethereum as _; -use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder}; -use pallet_evm::{OnMethodCall, PrecompileResult, account::CrossAccountId, PrecompileHandle}; -use up_data_structs::{ - CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData, - CollectionMode, PropertyValue, -}; +use evm_coder::{execution::*, generate_stubgen, solidity_interface, solidity, weight, types::*}; use frame_support::traits::Get; use pallet_common::{ - CollectionById, + CollectionById, CollectionHandle, + dispatch::CollectionDispatch, erc::{ + CollectionHelpersEvents, static_property::{key, value as property_value}, - CollectionHelpersEvents, }, - dispatch::CollectionDispatch, + Pallet as PalletCommon, }; -use crate::{SelfWeightOf, Config, weights::WeightInfo}; +use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder}; +use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult}; +use pallet_evm_coder_substrate::dispatch_to_evm; +use up_data_structs::{ + CollectionName, CollectionDescription, CollectionTokenPrefix, CreateCollectionData, + CollectionMode, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, +}; + +use crate::{Config, SelfWeightOf, weights::WeightInfo}; -use sp_std::vec::Vec; +use sp_std::{vec, vec::Vec}; use alloc::format; /// See [`CollectionHelpersCall`] @@ -151,6 +154,54 @@ Ok(data) } +fn parent_nft_property_permissions() -> PropertyKeyPermission { + PropertyKeyPermission { + key: key::parent_nft(), + permission: PropertyPermission { + mutable: false, + collection_admin: false, + token_owner: true, + }, + } +} + +fn create_refungible_collection_internal< + T: Config + pallet_nonfungible::Config + pallet_refungible::Config, +>( + caller: caller, + name: string, + description: string, + token_prefix: string, + base_uri: string, + add_properties: bool, +) -> Result
{ + let (caller, name, description, token_prefix, base_uri_value) = + convert_data::(caller, name, description, token_prefix, base_uri)?; + let data = make_data::( + name, + CollectionMode::ReFungible, + description, + token_prefix, + base_uri_value, + add_properties, + )?; + + let collection_id = T::CollectionDispatch::create(caller.clone(), data) + .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; + + let handle = >::try_get(collection_id).map_err(dispatch_to_evm::)?; + >::set_scoped_token_property_permissions( + &handle, + &caller, + PropertyScope::Eth, + vec![parent_nft_property_permissions()], + ) + .map_err(dispatch_to_evm::)?; + + let address = pallet_common::eth::collection_id_to_address(collection_id); + Ok(address) +} + /// @title Contract, which allows users to operate with collections #[solidity_interface(name = "CollectionHelpers", events(CollectionHelpersEvents))] impl EvmCollectionHelpers @@ -216,27 +267,20 @@ #[weight(>::create_collection())] fn create_refungible_collection( - &self, + &mut self, caller: caller, name: string, description: string, token_prefix: string, ) -> Result
{ - let (caller, name, description, token_prefix, _base_uri) = - convert_data::(caller, name, description, token_prefix, "".into())?; - let data = make_data::( + create_refungible_collection_internal::( + caller, name, - CollectionMode::ReFungible, description, token_prefix, Default::default(), false, - )?; - let collection_id = T::CollectionDispatch::create(caller, data) - .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; - - let address = pallet_common::eth::collection_id_to_address(collection_id); - Ok(address) + ) } #[weight(>::create_collection())] @@ -249,21 +293,14 @@ token_prefix: string, base_uri: string, ) -> Result
{ - let (caller, name, description, token_prefix, base_uri_value) = - convert_data::(caller, name, description, token_prefix, base_uri)?; - let data = make_data::( + create_refungible_collection_internal::( + caller, name, - CollectionMode::NFT, description, token_prefix, - base_uri_value, + base_uri, true, - )?; - let collection_id = T::CollectionDispatch::create(caller, data) - .map_err(pallet_evm_coder_substrate::dispatch_to_evm::)?; - - let address = pallet_common::eth::collection_id_to_address(collection_id); - Ok(address) + ) } /// Check if a collection exists --- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol +++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol @@ -72,12 +72,12 @@ string memory name, string memory description, string memory tokenPrefix - ) public view returns (address) { + ) public returns (address) { require(false, stub_error); name; description; tokenPrefix; - dummy; + dummy = 0; return 0x0000000000000000000000000000000000000000; } --- /dev/null +++ b/primitives/common/Cargo.toml @@ -0,0 +1,56 @@ +[package] +authors = ['Unique Network '] +description = 'Unique Runtime Common Primitives' +edition = '2021' +homepage = 'https://unique.network' +license = 'All Rights Reserved' +name = 'up-common' +repository = 'https://github.com/UniqueNetwork/unique-chain' +version = '0.9.24' + +[features] +default = ['std'] +std = [ + 'sp-std/std', + 'frame-support/std', + 'sp-runtime/std', + 'sp-core/std', + 'sp-consensus-aura/std', + 'fp-rpc/std', + 'pallet-evm/std', +] + +[dependencies.sp-std] +default-features = false +git = "https://github.com/paritytech/substrate" +branch = "polkadot-v0.9.24" + +[dependencies.frame-support] +default-features = false +git = "https://github.com/paritytech/substrate" +branch = "polkadot-v0.9.24" + +[dependencies.sp-runtime] +default-features = false +git = "https://github.com/paritytech/substrate" +branch = "polkadot-v0.9.24" + +[dependencies.sp-core] +default-features = false +git = "https://github.com/paritytech/substrate" +branch = "polkadot-v0.9.24" + +[dependencies.sp-consensus-aura] +default-features = false +git = "https://github.com/paritytech/substrate" +branch = "polkadot-v0.9.24" + +[dependencies.fp-rpc] +default-features = false +git = "https://github.com/uniquenetwork/frontier" +branch = "unique-polkadot-v0.9.24" + +[dependencies.pallet-evm] +default-features = false +git = "https://github.com/uniquenetwork/frontier" +branch = "unique-polkadot-v0.9.24" --- /dev/null +++ b/primitives/common/src/constants.rs @@ -0,0 +1,55 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use sp_runtime::Perbill; +use frame_support::{ + parameter_types, + weights::{Weight, constants::WEIGHT_PER_SECOND}, +}; +use crate::types::{BlockNumber, Balance}; + +pub const MILLISECS_PER_BLOCK: u64 = 12000; + +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +// These time units are defined in number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; + +pub const MICROUNIQUE: Balance = 1_000_000_000_000; +pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE; +pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE; +pub const UNIQUE: Balance = 100 * CENTIUNIQUE; + +// Targeting 0.1 UNQ per transfer +pub const WEIGHT_TO_FEE_COEFF: u32 = 207_890_902; + +// Targeting 0.15 UNQ per transfer via ETH +pub const MIN_GAS_PRICE: u64 = 1_019_493_469_850; + +/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers. +/// This is used to limit the maximal weight of a single extrinsic. +pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); +/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used +/// by Operational extrinsics. +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +/// We allow for 2 seconds of compute with a 6 second average block time. +pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2; + +parameter_types! { + pub const TransactionByteFee: Balance = 501 * MICROUNIQUE; +} --- /dev/null +++ b/primitives/common/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod constants; +pub mod types; --- /dev/null +++ b/primitives/common/src/types.rs @@ -0,0 +1,81 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use sp_runtime::{ + generic, + traits::{Verify, IdentifyAccount}, + MultiSignature, +}; + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + pub use sp_runtime::{generic, traits::BlakeTwo256, OpaqueExtrinsic as UncheckedExtrinsic}; + + pub use super::{BlockNumber, Signature, AccountId, Balance, Index, Hash, AuraId}; + + /// Opaque block header type. + pub type Header = generic::Header; + + /// Opaque block type. + pub type Block = generic::Block; + + pub trait RuntimeInstance { + type CrossAccountId: pallet_evm::account::CrossAccountId + + Send + + Sync + + 'static; + + type TransactionConverter: fp_rpc::ConvertTransaction + + Send + + Sync + + 'static; + + fn get_transaction_converter() -> Self::TransactionConverter; + } +} + +pub type SessionHandlers = (); + +/// An index to a block. +pub type BlockNumber = u32; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +/// The type for looking up accounts. We don't expect more than 4 billion of them, but you +/// never know... +pub type AccountIndex = u32; + +/// Balance of an account. +pub type Balance = u128; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// Digest item type. +pub type DigestItem = generic::DigestItem; + +pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -1050,6 +1050,7 @@ pub enum PropertyScope { None, Rmrk, + Eth, } impl PropertyScope { @@ -1058,6 +1059,7 @@ let scope_str: &[u8] = match self { Self::None => return Ok(key), Self::Rmrk => b"rmrk", + Self::Eth => b"eth", }; [scope_str, b":", key.as_slice()] --- a/runtime/common/Cargo.toml +++ /dev/null @@ -1,115 +0,0 @@ -[package] -authors = ['Unique Network '] -description = 'Unique Runtime Common' -edition = '2021' -homepage = 'https://unique.network' -license = 'All Rights Reserved' -name = 'unique-runtime-common' -repository = 'https://github.com/UniqueNetwork/unique-chain' -version = '0.9.24' - -[features] -default = ['std'] -std = [ - 'sp-core/std', - 'sp-std/std', - 'sp-runtime/std', - 'codec/std', - 'frame-support/std', - 'frame-system/std', - 'sp-consensus-aura/std', - 'pallet-common/std', - 'pallet-unique/std', - 'pallet-fungible/std', - 'pallet-nonfungible/std', - 'pallet-refungible/std', - 'up-data-structs/std', - 'pallet-evm/std', - 'fp-rpc/std', -] -runtime-benchmarks = [ - 'sp-runtime/runtime-benchmarks', - 'frame-support/runtime-benchmarks', - 'frame-system/runtime-benchmarks', -] - -[dependencies.sp-core] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.24" - -[dependencies.sp-std] -default-features = false -git = 'https://github.com/paritytech/substrate' -branch = 'polkadot-v0.9.24' - -[dependencies.sp-runtime] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.24" - -[dependencies.codec] -default-features = false -features = ['derive'] -package = 'parity-scale-codec' -version = '3.1.2' - -[dependencies.scale-info] -default-features = false -features = ["derive"] -version = "2.0.1" - -[dependencies.frame-support] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.24" - -[dependencies.frame-system] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.24" - -[dependencies.pallet-common] -default-features = false -path = "../../pallets/common" - -[dependencies.pallet-unique] -default-features = false -path = "../../pallets/unique" - -[dependencies.pallet-fungible] -default-features = false -path = "../../pallets/fungible" - -[dependencies.pallet-nonfungible] -default-features = false -path = "../../pallets/nonfungible" - -[dependencies.pallet-refungible] -default-features = false -path = "../../pallets/refungible" - -[dependencies.pallet-unique-scheduler] -default-features = false -path = "../../pallets/scheduler" - -[dependencies.up-data-structs] -default-features = false -path = "../../primitives/data-structs" - -[dependencies.sp-consensus-aura] -default-features = false -git = "https://github.com/paritytech/substrate" -branch = "polkadot-v0.9.24" - -[dependencies.fp-rpc] -default-features = false -git = "https://github.com/uniquenetwork/frontier" -branch = "unique-polkadot-v0.9.24" - -[dependencies] -pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } -evm-coder = { default-features = false, path = '../../crates/evm-coder' } -up-sponsorship = { default-features = false, git = "https://github.com/UniqueNetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' } - -rmrk-rpc = { default-features = false, path = "../../primitives/rmrk-rpc" } --- /dev/null +++ b/runtime/common/config/ethereum.rs @@ -0,0 +1,131 @@ +use sp_core::{U256, H160}; +use frame_support::{ + weights::{Weight, constants::WEIGHT_PER_SECOND}, + traits::{FindAuthor}, + parameter_types, ConsensusEngineId, +}; +use sp_runtime::{RuntimeAppPublic, Perbill}; +use crate::{ + runtime_common::{ + dispatch::CollectionDispatchT, ethereum::sponsoring::EvmSponsorshipHandler, + config::sponsoring::DefaultSponsoringRateLimit, DealWithFees, + }, + Runtime, Aura, Balances, Event, ChainId, +}; +use pallet_evm::{EnsureAddressTruncated, HashedAddressMapping}; +use up_common::constants::*; + +pub type CrossAccountId = pallet_evm::account::BasicCrossAccountId; + +impl pallet_evm::account::Config for Runtime { + type CrossAccountId = CrossAccountId; + type EvmAddressMapping = pallet_evm::HashedAddressMapping; + type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated; +} + +// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case +// (contract, which only writes a lot of data), +// approximating on top of our real store write weight +parameter_types! { + pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / ::DbWeight::get().write; + pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000; + pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get(); +} + +/// Limiting EVM execution to 50% of block for substrate users and management tasks +/// EVM transaction consumes more weight than substrate's, so we can't rely on them being +/// scheduled fairly +const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50); +parameter_types! { + pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get()); +} + +pub enum FixedGasWeightMapping {} +impl pallet_evm::GasWeightMapping for FixedGasWeightMapping { + fn gas_to_weight(gas: u64) -> Weight { + gas.saturating_mul(WeightPerGas::get()) + } + fn weight_to_gas(weight: Weight) -> u64 { + weight / WeightPerGas::get() + } +} + +pub struct FixedFee; +impl pallet_evm::FeeCalculator for FixedFee { + fn min_gas_price() -> (U256, u64) { + (MIN_GAS_PRICE.into(), 0) + } +} + +pub struct EthereumFindAuthor(core::marker::PhantomData); +impl> FindAuthor for EthereumFindAuthor { + fn find_author<'a, I>(digests: I) -> Option + where + I: 'a + IntoIterator, + { + if let Some(author_index) = F::find_author(digests) { + let authority_id = Aura::authorities()[author_index as usize].clone(); + return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24])); + } + None + } +} + +impl pallet_evm::Config for Runtime { + type BlockGasLimit = BlockGasLimit; + type FeeCalculator = FixedFee; + type GasWeightMapping = FixedGasWeightMapping; + type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; + type CallOrigin = EnsureAddressTruncated; + type WithdrawOrigin = EnsureAddressTruncated; + type AddressMapping = HashedAddressMapping; + type PrecompilesType = (); + type PrecompilesValue = (); + type Currency = Balances; + type Event = Event; + type OnMethodCall = ( + pallet_evm_migration::OnMethodCall, + pallet_evm_contract_helpers::HelpersOnMethodCall, + CollectionDispatchT, + pallet_unique::eth::CollectionHelpersOnMethodCall, + ); + type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; + type ChainId = ChainId; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter; + type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack; + type FindAuthor = EthereumFindAuthor; +} + +impl pallet_evm_migration::Config for Runtime { + type WeightInfo = pallet_evm_migration::weights::SubstrateWeight; +} + +impl pallet_ethereum::Config for Runtime { + type Event = Event; + type StateRoot = pallet_ethereum::IntermediateStateRoot; +} + +parameter_types! { + // 0x842899ECF380553E8a4de75bF534cdf6fBF64049 + pub const HelpersContractAddress: H160 = H160([ + 0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49, + ]); + + // 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f + pub const EvmCollectionHelpersAddress: H160 = H160([ + 0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f, + ]); +} + +impl pallet_evm_contract_helpers::Config for Runtime { + type ContractAddress = HelpersContractAddress; + type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; +} + +impl pallet_evm_coder_substrate::Config for Runtime {} + +impl pallet_evm_transaction_payment::Config for Runtime { + type EvmSponsorshipHandler = EvmSponsorshipHandler; + type Currency = Balances; +} --- /dev/null +++ b/runtime/common/config/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +pub mod ethereum; +pub mod orml; +pub mod pallets; +pub mod parachain; +pub mod sponsoring; +pub mod substrate; +pub mod xcm; --- /dev/null +++ b/runtime/common/config/orml.rs @@ -0,0 +1,38 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::parameter_types; +use frame_system::EnsureSigned; +use crate::{Runtime, Event, RelayChainBlockNumberProvider}; +use up_common::{ + types::{AccountId, Balance}, + constants::*, +}; + +parameter_types! { + pub const MinVestedTransfer: Balance = 10 * UNIQUE; + pub const MaxVestingSchedules: u32 = 28; +} + +impl orml_vesting::Config for Runtime { + type Event = Event; + type Currency = pallet_balances::Pallet; + type MinVestedTransfer = MinVestedTransfer; + type VestedTransferOrigin = EnsureSigned; + type WeightInfo = (); + type MaxVestingSchedules = MaxVestingSchedules; + type BlockNumberProvider = RelayChainBlockNumberProvider; +} --- /dev/null +++ b/runtime/common/config/pallets/mod.rs @@ -0,0 +1,93 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::parameter_types; +use sp_runtime::traits::AccountIdConversion; +use crate::{ + runtime_common::{ + dispatch::CollectionDispatchT, + config::{substrate::TreasuryModuleId, ethereum::EvmCollectionHelpersAddress}, + weights::CommonWeights, + RelayChainBlockNumberProvider, + }, + Runtime, Event, Call, Balances, +}; +use up_common::{ + types::{AccountId, Balance, BlockNumber}, + constants::*, +}; +use up_data_structs::{ + mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}, +}; + +#[cfg(feature = "rmrk")] +pub mod rmrk; + +#[cfg(feature = "scheduler")] +pub mod scheduler; + +parameter_types! { + pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating(); + pub const CollectionCreationPrice: Balance = 2 * UNIQUE; +} + +impl pallet_common::Config for Runtime { + type WeightInfo = pallet_common::weights::SubstrateWeight; + type Event = Event; + type Currency = Balances; + type CollectionCreationPrice = CollectionCreationPrice; + type TreasuryAccountId = TreasuryAccountId; + type CollectionDispatch = CollectionDispatchT; + + type EvmTokenAddressMapping = EvmTokenAddressMapping; + type CrossTokenAddressMapping = CrossTokenAddressMapping; + type ContractAddress = EvmCollectionHelpersAddress; +} + +impl pallet_structure::Config for Runtime { + type Event = Event; + type Call = Call; + type WeightInfo = pallet_structure::weights::SubstrateWeight; +} + +impl pallet_fungible::Config for Runtime { + type WeightInfo = pallet_fungible::weights::SubstrateWeight; +} +impl pallet_refungible::Config for Runtime { + type WeightInfo = pallet_refungible::weights::SubstrateWeight; +} +impl pallet_nonfungible::Config for Runtime { + type WeightInfo = pallet_nonfungible::weights::SubstrateWeight; +} + +parameter_types! { + pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied +} + +/// Used for the pallet inflation +impl pallet_inflation::Config for Runtime { + type Currency = Balances; + type TreasuryAccountId = TreasuryAccountId; + type InflationBlockInterval = InflationBlockInterval; + type BlockNumberProvider = RelayChainBlockNumberProvider; +} + +impl pallet_unique::Config for Runtime { + type Event = Event; + type WeightInfo = pallet_unique::weights::SubstrateWeight; + type CommonWeightInfo = CommonWeights; + type RefungibleExtensionsWeightInfo = CommonWeights; +} --- /dev/null +++ b/runtime/common/config/pallets/rmrk.rs @@ -0,0 +1,27 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use crate::{Runtime, Event}; + +impl pallet_proxy_rmrk_core::Config for Runtime { + type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight; + type Event = Event; +} + +impl pallet_proxy_rmrk_equip::Config for Runtime { + type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight; + type Event = Event; +} --- /dev/null +++ b/runtime/common/config/pallets/scheduler.rs @@ -0,0 +1,59 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::{traits::PrivilegeCmp, weights::Weight, parameter_types}; +use frame_system::EnsureSigned; +use sp_runtime::Perbill; +use sp_std::cmp::Ordering; +use crate::{ + runtime_common::{scheduler::SchedulerPaymentExecutor, config::substrate::RuntimeBlockWeights}, + Runtime, Call, Event, Origin, OriginCaller, Balances, +}; +use up_common::types::AccountId; + +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) * + RuntimeBlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 50; + + pub const NoPreimagePostponement: Option = Some(10); + pub const Preimage: Option = Some(10); +} + +/// Used the compare the privilege of an origin inside the scheduler. +pub struct OriginPrivilegeCmp; + +impl PrivilegeCmp for OriginPrivilegeCmp { + fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { + Some(Ordering::Equal) + } +} + +impl pallet_unique_scheduler::Config for Runtime { + type Event = Event; + type Origin = Origin; + type Currency = Balances; + type PalletsOrigin = OriginCaller; + type Call = Call; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureSigned; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = (); + type CallExecutor = SchedulerPaymentExecutor; + type OriginPrivilegeCmp = OriginPrivilegeCmp; + type PreimageProvider = (); + type NoPreimagePostponement = NoPreimagePostponement; +} --- /dev/null +++ b/runtime/common/config/parachain.rs @@ -0,0 +1,44 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::{weights::Weight, parameter_types}; +use crate::{Runtime, Event, XcmpQueue, DmpQueue}; +use up_common::constants::*; + +parameter_types! { + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type Event = Event; + type SelfParaId = parachain_info::Pallet; + type OnSystemEvent = (); + // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent< + // MaxDownwardMessageWeight, + // XcmExecutor, + // Call, + // >; + type OutboundXcmpMessageSource = XcmpQueue; + type DmpMessageHandler = DmpQueue; + type ReservedDmpWeight = ReservedDmpWeight; + type ReservedXcmpWeight = ReservedXcmpWeight; + type XcmpMessageHandler = XcmpQueue; +} + +impl parachain_info::Config for Runtime {} + +impl cumulus_pallet_aura_ext::Config for Runtime {} --- /dev/null +++ b/runtime/common/config/sponsoring.rs @@ -0,0 +1,35 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::parameter_types; +use crate::{ + runtime_common::{sponsoring::UniqueSponsorshipHandler}, + Runtime, +}; +use up_common::{types::BlockNumber, constants::*}; + +parameter_types! { + pub const DefaultSponsoringRateLimit: BlockNumber = 1 * DAYS; +} + +type SponsorshipHandler = ( + UniqueSponsorshipHandler, + pallet_evm_transaction_payment::BridgeSponsorshipHandler, +); + +impl pallet_charge_transaction::Config for Runtime { + type SponsorshipHandler = SponsorshipHandler; +} --- /dev/null +++ b/runtime/common/config/substrate.rs @@ -0,0 +1,237 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::{ + traits::{Everything, ConstU32}, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}, + DispatchClass, WeightToFeePolynomial, WeightToFeeCoefficients, ConstantMultiplier, + WeightToFeeCoefficient, + }, + parameter_types, PalletId, +}; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, AccountIdLookup}, + Perbill, Permill, Percent, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use smallvec::smallvec; +use crate::{ + runtime_common::DealWithFees, Runtime, Event, Call, Origin, PalletInfo, System, Balances, + Treasury, SS58Prefix, Version, +}; +use up_common::{types::*, constants::*}; + +parameter_types! { + pub const BlockHashCount: BlockNumber = 2400; + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); + pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); +} + +impl frame_system::Config for Runtime { + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The basic call filter to use in dispatchable. + type BaseCallFilter = Everything; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// The maximum length of a block (in bytes). + type BlockLength = RuntimeBlockLength; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block. + type BlockWeights = RuntimeBlockWeights; + /// The aggregated dispatch type that is available for extrinsics. + type Call = Call; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// The ubiquitous event type. + type Event = Event; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The header type. + type Header = generic::Header; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// What to do if a new account is created. + type OnNewAccount = (); + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + /// The ubiquitous origin type. + type Origin = Origin; + /// This type is being generated by `construct_runtime!`. + type PalletInfo = PalletInfo; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = frame_system::weights::SubstrateWeight; + /// Version of the runtime. + type Version = Version; + type MaxConsumers = ConstU32<16>; +} + +impl pallet_randomness_collective_flip::Config for Runtime {} + +parameter_types! { + pub const MinimumPeriod: u64 = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + // pub const ExistentialDeposit: u128 = 500; + pub const ExistentialDeposit: u128 = 0; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 16]; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type Event = Event; + type DustRemoval = Treasury; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight; +} + +parameter_types! { + /// This value increases the priority of `Operational` transactions by adding + /// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`. + pub const OperationalFeeMultiplier: u8 = 5; +} + +/// Linear implementor of `WeightToFeePolynomial` +pub struct LinearFee(sp_std::marker::PhantomData); + +impl WeightToFeePolynomial for LinearFee +where + T: BaseArithmetic + From + Copy + Unsigned, +{ + type Balance = T; + + fn polynomial() -> WeightToFeeCoefficients { + smallvec!(WeightToFeeCoefficient { + coeff_integer: WEIGHT_TO_FEE_COEFF.into(), + coeff_frac: Perbill::zero(), + negative: false, + degree: 1, + }) + } +} + +impl pallet_transaction_payment::Config for Runtime { + type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; + type LengthToFee = ConstantMultiplier; + type OperationalFeeMultiplier = OperationalFeeMultiplier; + type WeightToFee = LinearFee; + type FeeMultiplierUpdate = (); +} + +parameter_types! { + pub const ProposalBond: Permill = Permill::from_percent(5); + pub const ProposalBondMinimum: Balance = 1 * UNIQUE; + pub const ProposalBondMaximum: Balance = 1000 * UNIQUE; + pub const SpendPeriod: BlockNumber = 5 * MINUTES; + pub const Burn: Permill = Permill::from_percent(0); + pub const TipCountdown: BlockNumber = 1 * DAYS; + pub const TipFindersFee: Percent = Percent::from_percent(20); + pub const TipReportDepositBase: Balance = 1 * UNIQUE; + pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE; + pub const BountyDepositBase: Balance = 1 * UNIQUE; + pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; + pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry"); + pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; + pub const MaximumReasonLength: u32 = 16384; + pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + pub const BountyValueMinimum: Balance = 5 * UNIQUE; + pub const MaxApprovals: u32 = 100; +} + +impl pallet_treasury::Config for Runtime { + type PalletId = TreasuryModuleId; + type Currency = Balances; + type ApproveOrigin = EnsureRoot; + type RejectOrigin = EnsureRoot; + type Event = Event; + type OnSlash = (); + type ProposalBond = ProposalBond; + type ProposalBondMinimum = ProposalBondMinimum; + type ProposalBondMaximum = ProposalBondMaximum; + type SpendPeriod = SpendPeriod; + type Burn = Burn; + type BurnDestination = (); + type SpendFunds = (); + type WeightInfo = pallet_treasury::weights::SubstrateWeight; + type MaxApprovals = MaxApprovals; +} + +impl pallet_sudo::Config for Runtime { + type Event = Event; + type Call = Call; +} + +parameter_types! { + pub const MaxAuthorities: u32 = 100_000; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = MaxAuthorities; +} --- /dev/null +++ b/runtime/common/config/xcm.rs @@ -0,0 +1,295 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::{ + traits::{ + tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Get, Everything, + }, + weights::{Weight, WeightToFeePolynomial, WeightToFee}, + parameter_types, match_types, +}; +use frame_system::EnsureRoot; +use sp_runtime::{ + traits::{Saturating, CheckedConversion, Zero}, + SaturatedConversion, +}; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::Sibling; +use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*}; +use xcm::latest::{ + AssetId::{Concrete}, + Fungibility::Fungible as XcmFungible, + MultiAsset, Error as XcmError, +}; +use xcm_executor::traits::{MatchesFungible, WeightTrader}; +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, + FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, ParentIsPreset, +}; +use xcm_executor::{Config, XcmExecutor, Assets}; +use sp_std::marker::PhantomData; +use crate::{ + runtime_common::config::substrate::LinearFee, Runtime, Call, Event, Origin, Balances, + ParachainInfo, ParachainSystem, PolkadotXcm, XcmpQueue, +}; +use up_common::{ + types::{AccountId, Balance}, + constants::*, +}; + +parameter_types! { + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: NetworkId = NetworkId::Polkadot; + pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); + pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the default `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, +); + +pub struct OnlySelfCurrency; +impl> MatchesFungible for OnlySelfCurrency { + fn matches_fungible(a: &MultiAsset) -> Option { + 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: + 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): + AccountId, + // We don't track any teleports. + (), +>; + +/// No local origins on this chain are allowed to dispatch XCM sends/executions. +pub type LocalOriginToLocation = (SignedToAccountId32,); + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = ( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +); + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can +/// biases the kind of local `Origin` it will become. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when + // recognised. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognised. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `Origin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + XcmPassthrough, +); + +parameter_types! { + // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. + pub UnitWeightCost: Weight = 1_000_000; + // 1200 UNIQUEs buy 1 second of weight. + pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE); + pub const MaxInstructions: u32 = 100; +} + +match_types! { + pub type ParentOrParentsUnitPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) } + }; +} + +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + // ^^^ Parent & its unit plurality gets free execution +); + +pub struct UsingOnlySelfCurrencyComponents< + WeightToFee: WeightToFeePolynomial, + AssetId: Get, + AccountId, + Currency: CurrencyT, + OnUnbalanced: OnUnbalancedT, +>( + Weight, + Currency::Balance, + PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, +); +impl< + WeightToFee: WeightToFeePolynomial, + AssetId: Get, + AccountId, + Currency: CurrencyT, + OnUnbalanced: OnUnbalancedT, + > WeightTrader + for UsingOnlySelfCurrencyComponents +{ + fn new() -> Self { + Self(0, Zero::zero(), PhantomData) + } + + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + let amount = WeightToFee::weight_to_fee(&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 { + let weight = weight.min(self.0); + let amount = WeightToFee::weight_to_fee(&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, + AssetId: Get, + AccountId, + Currency: CurrencyT, + OnUnbalanced: OnUnbalancedT, + > Drop + for UsingOnlySelfCurrencyComponents +{ + fn drop(&mut self) { + OnUnbalanced::on_unbalanced(Currency::issue(self.1)); + } +} + +pub struct XcmConfig; +impl Config for XcmConfig { + type Call = Call; + type XcmSender = XcmRouter; + // How to withdraw and deposit an asset. + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + type IsReserve = NativeAsset; + type IsTeleporter = (); // Teleportation is disabled + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = + UsingOnlySelfCurrencyComponents, RelayLocation, AccountId, Balances, ()>; + type ResponseHandler = (); // Don't handle responses for now. + type SubscriptionService = PolkadotXcm; + + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; +} + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = Everything; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; + type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; +} + +impl cumulus_pallet_xcm::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; +} + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type WeightInfo = (); + type Event = Event; + type XcmExecutor = XcmExecutor; + type ChannelInfo = ParachainSystem; + type VersionWrapper = (); + type ExecuteOverweightOrigin = frame_system::EnsureRoot; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; +} + +impl cumulus_pallet_dmp_queue::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; + type ExecuteOverweightOrigin = frame_system::EnsureRoot; +} --- /dev/null +++ b/runtime/common/constants.rs @@ -0,0 +1,55 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use sp_runtime::Perbill; +use frame_support::{ + parameter_types, + weights::{Weight, constants::WEIGHT_PER_SECOND}, +}; +use up_common::types::{BlockNumber, Balance}; + +pub const MILLISECS_PER_BLOCK: u64 = 12000; + +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +// These time units are defined in number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; + +pub const MICROUNIQUE: Balance = 1_000_000_000_000; +pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE; +pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE; +pub const UNIQUE: Balance = 100 * CENTIUNIQUE; + +// Targeting 0.1 UNQ per transfer +pub const WEIGHT_TO_FEE_COEFF: u32 = 207_890_902; + +// Targeting 0.15 UNQ per transfer via ETH +pub const MIN_GAS_PRICE: u64 = 1_019_493_469_850; + +/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers. +/// This is used to limit the maximal weight of a single extrinsic. +pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); +/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used +/// by Operational extrinsics. +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +/// We allow for 2 seconds of compute with a 6 second average block time. +pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2; + +parameter_types! { + pub const TransactionByteFee: Balance = 501 * MICROUNIQUE; +} --- /dev/null +++ b/runtime/common/construct_runtime/mod.rs @@ -0,0 +1,90 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +mod util; + +#[macro_export] +macro_rules! construct_runtime { + ($select_runtime:ident) => { + $crate::construct_runtime_impl! { + select_runtime($select_runtime); + + pub enum Runtime where + Block = Block, + NodeBlock = opaque::Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned} = 20, + ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21, + + Aura: pallet_aura::{Pallet, Config} = 22, + AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23, + + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 30, + RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33, + Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 34, + Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event} = 35, + System: frame_system::{Pallet, Call, Storage, Config, Event} = 36, + Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 37, + // Vesting: pallet_vesting::{Pallet, Call, Config, Storage, Event} = 37, + // Contracts: pallet_contracts::{Pallet, Call, Storage, Event} = 38, + + // XCM helpers. + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin} = 51, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event, Origin} = 52, + DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 53, + + // Unique Pallets + Inflation: pallet_inflation::{Pallet, Call, Storage} = 60, + Unique: pallet_unique::{Pallet, Call, Storage, Event} = 61, + + #[runtimes(opal)] + Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event} = 62, + + // free = 63 + + Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64, + // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65, + Common: pallet_common::{Pallet, Storage, Event} = 66, + Fungible: pallet_fungible::{Pallet, Storage} = 67, + + #[runtimes(opal)] + Refungible: pallet_refungible::{Pallet, Storage} = 68, + + Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69, + Structure: pallet_structure::{Pallet, Call, Storage, Event} = 70, + + #[runtimes(opal)] + RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event} = 71, + + #[runtimes(opal)] + RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event} = 72, + + // Frontier + EVM: pallet_evm::{Pallet, Config, Call, Storage, Event} = 100, + Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101, + + EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150, + EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151, + EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152, + EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153, + } + } + } +} --- /dev/null +++ b/runtime/common/construct_runtime/util.rs @@ -0,0 +1,222 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +#[macro_export] +macro_rules! construct_runtime_impl { + ( + select_runtime($select_runtime:ident); + + pub enum Runtime where + $($where_ident:ident = $where_ty:ty),* $(,)? + { + $( + $(#[runtimes($($pallet_runtimes:ident),+ $(,)?)])? + $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal + ),* + $(,)? + } + ) => { + $crate::construct_runtime_helper! { + select_runtime($select_runtime), + selected_pallets(), + + where_clause($($where_ident = $where_ty),*), + pallets( + $( + $(#[runtimes($($pallet_runtimes),+)])? + $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index + ),*, + ) + } + } +} + +#[macro_export] +macro_rules! construct_runtime_helper { + ( + select_runtime($select_runtime:ident), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets( + #[runtimes($($pallet_runtimes:ident),+)] + $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal, + + $($pallets_tl:tt)* + ) + ) => { + $crate::add_runtime_specific_pallets! { + select_runtime($select_runtime), + runtimes($($pallet_runtimes),+,), + selected_pallets($($selected_pallets)*), + + where_clause($($where_clause)*), + pallets( + $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index, + $($pallets_tl)* + ) + } + }; + + ( + select_runtime($select_runtime:ident), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets( + $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal, + + $($pallets_tl:tt)* + ) + ) => { + $crate::construct_runtime_helper! { + select_runtime($select_runtime), + selected_pallets( + $($selected_pallets)* + $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index, + ), + + where_clause($($where_clause)*), + pallets($($pallets_tl)*) + } + }; + + ( + select_runtime($select_runtime:ident), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets() + ) => { + frame_support::construct_runtime! { + pub enum Runtime where + $($where_clause)* + { + $($selected_pallets)* + } + } + }; +} + +#[macro_export] +macro_rules! add_runtime_specific_pallets { + ( + select_runtime(opal), + runtimes(opal, $($_runtime_tl:tt)*), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets( + $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal, + $($pallets_tl:tt)* + ) + ) => { + $crate::construct_runtime_helper! { + select_runtime(opal), + selected_pallets( + $($selected_pallets)* + $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index, + ), + + where_clause($($where_clause)*), + pallets($($pallets_tl)*) + } + }; + + ( + select_runtime(quartz), + runtimes(quartz, $($_runtime_tl:tt)*), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets( + $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal, + $($pallets_tl:tt)* + ) + ) => { + $crate::construct_runtime_helper! { + select_runtime(quartz), + selected_pallets( + $($selected_pallets)* + $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index, + ), + + where_clause($($where_clause)*), + pallets($($pallets_tl)*) + } + }; + + ( + select_runtime(unique), + runtimes(unique, $($_runtime_tl:tt)*), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets( + $pallet_name:ident: $pallet_mod:ident::{$($pallet_parts:ty),*} = $index:literal, + $($pallets_tl:tt)* + ) + ) => { + $crate::construct_runtime_helper! { + select_runtime(unique), + selected_pallets( + $($selected_pallets)* + $pallet_name: $pallet_mod::{$($pallet_parts),*} = $index, + ), + + where_clause($($where_clause)*), + pallets($($pallets_tl)*) + } + }; + + ( + select_runtime($select_runtime:ident), + runtimes($_current_runtime:ident, $($runtime_tl:tt)*), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets($($pallets:tt)*) + ) => { + $crate::add_runtime_specific_pallets! { + select_runtime($select_runtime), + runtimes($($runtime_tl)*), + selected_pallets($($selected_pallets)*), + + where_clause($($where_clause)*), + pallets($($pallets)*) + } + }; + + ( + select_runtime($select_runtime:ident), + runtimes(), + selected_pallets($($selected_pallets:tt)*), + + where_clause($($where_clause:tt)*), + pallets( + $_pallet_name:ident: $_pallet_mod:ident::{$($_pallet_parts:ty),*} = $_index:literal, + $($pallets_tl:tt)* + ) + ) => { + $crate::construct_runtime_helper! { + select_runtime($select_runtime), + selected_pallets($($selected_pallets)*), + + where_clause($($where_clause)*), + pallets($($pallets_tl)*) + } + }; +} --- /dev/null +++ b/runtime/common/dispatch.rs @@ -0,0 +1,189 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::{dispatch::DispatchResult, ensure}; +use pallet_evm::{PrecompileHandle, PrecompileResult}; +use sp_core::H160; +use sp_runtime::DispatchError; +use sp_std::{borrow::ToOwned, vec::Vec}; +use pallet_common::{ + CollectionById, CollectionHandle, CommonCollectionOperations, erc::CommonEvmHandler, + eth::map_eth_to_id, +}; +pub use pallet_common::dispatch::CollectionDispatch; +use pallet_fungible::{Pallet as PalletFungible, FungibleHandle}; +use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle}; +use pallet_refungible::{ + Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle, +}; +use up_data_structs::{ + CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping, + CollectionId, +}; + +#[cfg(not(feature = "refungible"))] +use pallet_common::unsupported; + +pub enum CollectionDispatchT +where + T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config, +{ + Fungible(FungibleHandle), + Nonfungible(NonfungibleHandle), + Refungible(RefungibleHandle), +} +impl CollectionDispatch for CollectionDispatchT +where + T: pallet_common::Config + + pallet_unique::Config + + pallet_fungible::Config + + pallet_nonfungible::Config + + pallet_refungible::Config, +{ + fn create( + sender: T::CrossAccountId, + data: CreateCollectionData, + ) -> Result { + let id = match data.mode { + CollectionMode::NFT => >::init_collection(sender, data, false)?, + CollectionMode::Fungible(decimal_points) => { + // check params + ensure!( + decimal_points <= MAX_DECIMAL_POINTS, + pallet_unique::Error::::CollectionDecimalPointLimitExceeded + ); + >::init_collection(sender, data)? + } + + #[cfg(feature = "refungible")] + CollectionMode::ReFungible => >::init_collection(sender, data)?, + + #[cfg(not(feature = "refungible"))] + CollectionMode::ReFungible => return unsupported!(T), + }; + Ok(id) + } + + fn destroy(sender: T::CrossAccountId, collection: CollectionHandle) -> DispatchResult { + match collection.mode { + CollectionMode::ReFungible => { + PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)? + } + CollectionMode::Fungible(_) => { + PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)? + } + CollectionMode::NFT => { + PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)? + } + } + Ok(()) + } + + fn dispatch(handle: CollectionHandle) -> Self { + match handle.mode { + CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)), + CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)), + CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)), + } + } + + fn into_inner(self) -> CollectionHandle { + match self { + Self::Fungible(f) => f.into_inner(), + Self::Nonfungible(f) => f.into_inner(), + Self::Refungible(f) => f.into_inner(), + } + } + + fn as_dyn(&self) -> &dyn CommonCollectionOperations { + match self { + Self::Fungible(h) => h, + Self::Nonfungible(h) => h, + Self::Refungible(h) => h, + } + } +} + +impl pallet_evm::OnMethodCall for CollectionDispatchT +where + T: pallet_common::Config + + pallet_unique::Config + + pallet_fungible::Config + + pallet_nonfungible::Config + + pallet_refungible::Config, + T::AccountId: From<[u8; 32]>, +{ + fn is_reserved(target: &H160) -> bool { + map_eth_to_id(target).is_some() + } + fn is_used(target: &H160) -> bool { + map_eth_to_id(target) + .map(>::contains_key) + .unwrap_or(false) + } + fn get_code(target: &H160) -> Option> { + if let Some(collection_id) = map_eth_to_id(target) { + let collection = >::get(collection_id)?; + Some( + match collection.mode { + CollectionMode::NFT => >::CODE, + CollectionMode::Fungible(_) => >::CODE, + CollectionMode::ReFungible => >::CODE, + } + .to_owned(), + ) + } else if let Some((collection_id, _token_id)) = + ::EvmTokenAddressMapping::address_to_token(target) + { + let collection = >::get(collection_id)?; + if collection.mode != CollectionMode::ReFungible { + return None; + } + // TODO: check token existence + Some(>::CODE.to_owned()) + } else { + None + } + } + fn call(handle: &mut impl PrecompileHandle) -> Option { + if let Some(collection_id) = map_eth_to_id(&handle.code_address()) { + let collection = + >::new_with_gas_limit(collection_id, handle.remaining_gas())?; + let dispatched = Self::dispatch(collection); + + match dispatched { + Self::Fungible(h) => h.call(handle), + Self::Nonfungible(h) => h.call(handle), + Self::Refungible(h) => h.call(handle), + } + } else if let Some((collection_id, token_id)) = + ::EvmTokenAddressMapping::address_to_token( + &handle.code_address(), + ) { + let collection = + >::new_with_gas_limit(collection_id, handle.remaining_gas())?; + if collection.mode != CollectionMode::ReFungible { + return None; + } + + let h = RefungibleHandle::cast(collection); + // TODO: check token existence + RefungibleTokenHandle(h, token_id).call(handle) + } else { + None + } + } +} --- /dev/null +++ b/runtime/common/ethereum/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +pub mod self_contained_call; +pub mod sponsoring; +pub mod transaction_converter; --- /dev/null +++ b/runtime/common/ethereum/self_contained_call.rs @@ -0,0 +1,74 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use sp_core::H160; +use sp_runtime::{ + traits::{Dispatchable, DispatchInfoOf, PostDispatchInfoOf}, + transaction_validity::{TransactionValidityError, TransactionValidity}, +}; +use crate::{Origin, Call}; + +impl fp_self_contained::SelfContainedCall for Call { + type SignedInfo = H160; + + fn is_self_contained(&self) -> bool { + match self { + Call::Ethereum(call) => call.is_self_contained(), + _ => false, + } + } + + fn check_self_contained(&self) -> Option> { + match self { + Call::Ethereum(call) => call.check_self_contained(), + _ => None, + } + } + + fn validate_self_contained( + &self, + info: &Self::SignedInfo, + dispatch_info: &DispatchInfoOf, + len: usize, + ) -> Option { + match self { + Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len), + _ => None, + } + } + + fn pre_dispatch_self_contained( + &self, + info: &Self::SignedInfo, + ) -> Option> { + match self { + Call::Ethereum(call) => call.pre_dispatch_self_contained(info), + _ => None, + } + } + + fn apply_self_contained( + self, + info: Self::SignedInfo, + ) -> Option>> { + match self { + call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch( + Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)), + )), + _ => None, + } + } +} --- /dev/null +++ b/runtime/common/ethereum/sponsoring.rs @@ -0,0 +1,127 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +//! Implements EVM sponsoring logic via TransactionValidityHack + +use evm_coder::{Call, abi::AbiReader}; +use pallet_common::{CollectionHandle, eth::map_eth_to_id}; +use sp_core::H160; +use sp_std::prelude::*; +use up_sponsorship::SponsorshipHandler; +use core::marker::PhantomData; +use core::convert::TryInto; +use pallet_evm::account::CrossAccountId; +use up_data_structs::{TokenId, CreateItemData, CreateNftData, CollectionMode}; +use pallet_unique::Config as UniqueConfig; + +use crate::{Runtime, runtime_common::sponsoring::*}; + +use pallet_nonfungible::erc::{ + UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, TokenPropertiesCall, +}; +use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call}; +use pallet_fungible::Config as FungibleConfig; +use pallet_nonfungible::Config as NonfungibleConfig; +use pallet_refungible::Config as RefungibleConfig; + +pub type EvmSponsorshipHandler = ( + UniqueEthSponsorshipHandler, + pallet_evm_contract_helpers::HelpersContractSponsoring, +); + +pub struct UniqueEthSponsorshipHandler(PhantomData<*const T>); +impl + SponsorshipHandler)> for UniqueEthSponsorshipHandler +{ + fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec)) -> Option { + let collection_id = map_eth_to_id(&call.0)?; + let collection = >::new(collection_id)?; + let sponsor = collection.sponsorship.sponsor()?.clone(); + let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?; + Some(T::CrossAccountId::from_sub(match &collection.mode { + CollectionMode::NFT => { + let call = >::parse(method_id, &mut reader).ok()??; + match call { + UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty { + token_id, + key, + value, + .. + }) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_set_token_property::( + &collection, + &who, + &token_id, + key.len() + value.len(), + ) + .map(|()| sponsor) + } + UniqueNFTCall::ERC721UniqueExtensions( + ERC721UniqueExtensionsCall::Transfer { token_id, .. }, + ) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_transfer::(&collection, &who, &token_id).map(|()| sponsor) + } + UniqueNFTCall::ERC721Mintable( + ERC721MintableCall::Mint { token_id, .. } + | ERC721MintableCall::MintWithTokenUri { token_id, .. }, + ) => { + let _token_id: TokenId = token_id.try_into().ok()?; + withdraw_create_item::( + &collection, + &who, + &CreateItemData::NFT(CreateNftData::default()), + ) + .map(|()| sponsor) + } + UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => { + let token_id: TokenId = token_id.try_into().ok()?; + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&collection, &from, &token_id).map(|()| sponsor) + } + UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_approve::(&collection, who.as_sub(), &token_id) + .map(|()| sponsor) + } + _ => None, + } + } + CollectionMode::Fungible(_) => { + let call = >::parse(method_id, &mut reader).ok()??; + #[allow(clippy::single_match)] + match call { + UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => { + withdraw_transfer::(&collection, who, &TokenId::default()) + .map(|()| sponsor) + } + UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => { + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&collection, &from, &TokenId::default()) + .map(|()| sponsor) + } + UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => { + withdraw_approve::(&collection, who.as_sub(), &TokenId::default()) + .map(|()| sponsor) + } + _ => None, + } + } + _ => None, + }?)) + } +} --- /dev/null +++ b/runtime/common/ethereum/transaction_converter.rs @@ -0,0 +1,42 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use codec::{Encode, Decode}; +use crate::{opaque, Runtime, UncheckedExtrinsic}; + +pub struct TransactionConverter; + +impl fp_rpc::ConvertTransaction for TransactionConverter { + fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic { + UncheckedExtrinsic::new_unsigned( + pallet_ethereum::Call::::transact { transaction }.into(), + ) + } +} + +impl fp_rpc::ConvertTransaction for TransactionConverter { + fn convert_transaction( + &self, + transaction: pallet_ethereum::Transaction, + ) -> opaque::UncheckedExtrinsic { + let extrinsic = UncheckedExtrinsic::new_unsigned( + pallet_ethereum::Call::::transact { transaction }.into(), + ); + let encoded = extrinsic.encode(); + opaque::UncheckedExtrinsic::decode(&mut &encoded[..]) + .expect("Encoded extrinsic is always valid") + } +} --- /dev/null +++ b/runtime/common/instance.rs @@ -0,0 +1,16 @@ +use crate::{ + runtime_common::{ + config::ethereum::CrossAccountId, ethereum::transaction_converter::TransactionConverter, + }, + Runtime, +}; +use up_common::types::opaque::RuntimeInstance; + +impl RuntimeInstance for Runtime { + type CrossAccountId = CrossAccountId; + type TransactionConverter = TransactionConverter; + + fn get_transaction_converter() -> TransactionConverter { + TransactionConverter + } +} --- /dev/null +++ b/runtime/common/mod.rs @@ -0,0 +1,168 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +pub mod config; +pub mod constants; +pub mod construct_runtime; +pub mod dispatch; +pub mod ethereum; +pub mod instance; +pub mod runtime_apis; +pub mod scheduler; +pub mod sponsoring; +pub mod weights; + +use sp_core::H160; +use frame_support::traits::{Currency, OnUnbalanced, Imbalance}; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, BlockNumberProvider}, + impl_opaque_keys, +}; +use sp_std::vec::Vec; + +#[cfg(feature = "std")] +use sp_version::NativeVersion; + +use crate::{ + Runtime, Call, Balances, Treasury, Aura, Signature, AllPalletsReversedWithSystemFirst, + InherentDataExt, +}; +use up_common::types::{AccountId, BlockNumber}; + +#[macro_export] +macro_rules! unsupported { + () => { + pallet_common::unsupported!($crate::Runtime) + }; +} + +/// The address format for describing accounts. +pub type Address = sp_runtime::MultiAddress; +/// Block header type as expected by this runtime. +pub type Header = generic::Header; +/// Block type as expected by this runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { + runtime_version: crate::VERSION, + can_author_with: Default::default(), + } +} + +pub type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment; + +pub type SignedExtra = ( + frame_system::CheckSpecVersion, + // system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + ChargeTransactionPayment, + //pallet_contract_helpers::ContractHelpersExtension, + pallet_ethereum::FakeTransactionFinalizer, +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + fp_self_contained::UncheckedExtrinsic; + +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic; + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsReversedWithSystemFirst, +>; + +type NegativeImbalance = >::NegativeImbalance; + +pub struct DealWithFees; +impl OnUnbalanced for DealWithFees { + fn on_unbalanceds(mut fees_then_tips: impl Iterator) { + if let Some(fees) = fees_then_tips.next() { + // for fees, 100% to treasury + let mut split = fees.ration(100, 0); + if let Some(tips) = fees_then_tips.next() { + // for tips, if any, 100% to treasury + tips.ration_merge_into(100, 0, &mut split); + } + Treasury::on_unbalanced(split.0); + // Author::on_unbalanced(split.1); + } + } +} + +pub struct RelayChainBlockNumberProvider(sp_std::marker::PhantomData); + +impl BlockNumberProvider + for RelayChainBlockNumberProvider +{ + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + cumulus_pallet_parachain_system::Pallet::::validation_data() + .map(|d| d.relay_parent_number) + .unwrap_or_default() + } +} + +pub(crate) struct CheckInherents; + +impl cumulus_pallet_parachain_system::CheckInherents for CheckInherents { + fn check_inherents( + block: &Block, + relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof, + ) -> sp_inherents::CheckInherentsResult { + let relay_chain_slot = relay_state_proof + .read_slot() + .expect("Could not read the relay chain slot from the proof"); + + let inherent_data = + cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration( + relay_chain_slot, + sp_std::time::Duration::from_secs(6), + ) + .create_inherent_data() + .expect("Could not create the timestamp inherent data"); + + inherent_data.check_extrinsics(block) + } +} + +#[derive(codec::Encode, codec::Decode)] +pub enum XCMPMessage { + /// Transfer tokens to the given account from the Parachain account. + TransferToken(XAccountId, XBalance), +} --- /dev/null +++ b/runtime/common/runtime_apis.rs @@ -0,0 +1,724 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +#[macro_export] +macro_rules! dispatch_unique_runtime { + ($collection:ident.$method:ident($($name:ident),*)) => {{ + let collection = ::CollectionDispatch::dispatch(>::try_get($collection)?); + let dispatch = collection.as_dyn(); + + Ok::<_, DispatchError>(dispatch.$method($($name),*)) + }}; +} + +#[macro_export] +macro_rules! impl_common_runtime_apis { + ( + $( + #![custom_apis] + + $($custom_apis:tt)+ + )? + ) => { + use sp_std::prelude::*; + use sp_api::impl_runtime_apis; + use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160}; + use sp_runtime::{ + Permill, + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, DispatchError, + }; + use fp_rpc::TransactionStatus; + use pallet_transaction_payment::{ + FeeDetails, RuntimeDispatchInfo, + }; + use pallet_evm::{ + Runner, account::CrossAccountId as _, + Account as EVMAccount, FeeCalculator, + }; + use runtime_common::{ + sponsoring::{SponsorshipPredict, UniqueSponsorshipPredict}, + dispatch::CollectionDispatch, + config::ethereum::CrossAccountId, + }; + use up_data_structs::*; + + + impl_runtime_apis! { + $($($custom_apis)+)? + + impl up_rpc::UniqueApi for Runtime { + fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result, DispatchError> { + dispatch_unique_runtime!(collection.account_tokens(account)) + } + fn collection_tokens(collection: CollectionId) -> Result, DispatchError> { + dispatch_unique_runtime!(collection.collection_tokens()) + } + fn token_exists(collection: CollectionId, token: TokenId) -> Result { + dispatch_unique_runtime!(collection.token_exists(token)) + } + + fn token_owner(collection: CollectionId, token: TokenId) -> Result, DispatchError> { + dispatch_unique_runtime!(collection.token_owner(token)) + } + + fn token_owners(collection: CollectionId, token: TokenId) -> Result, DispatchError> { + dispatch_unique_runtime!(collection.token_owners(token)) + } + + fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result, DispatchError> { + let budget = up_data_structs::budget::Value::new(10); + + Ok(Some(>::find_topmost_owner(collection, token, &budget)?)) + } + fn token_children(collection: CollectionId, token: TokenId) -> Result, DispatchError> { + Ok(>::token_children_ids(collection, token)) + } + fn collection_properties( + collection: CollectionId, + keys: Option>> + ) -> Result, DispatchError> { + let keys = keys.map( + |keys| Common::bytes_keys_to_property_keys(keys) + ).transpose()?; + + Common::filter_collection_properties(collection, keys) + } + + fn token_properties( + collection: CollectionId, + token_id: TokenId, + keys: Option>> + ) -> Result, DispatchError> { + let keys = keys.map( + |keys| Common::bytes_keys_to_property_keys(keys) + ).transpose()?; + + dispatch_unique_runtime!(collection.token_properties(token_id, keys)) + } + + fn property_permissions( + collection: CollectionId, + keys: Option>> + ) -> Result, DispatchError> { + let keys = keys.map( + |keys| Common::bytes_keys_to_property_keys(keys) + ).transpose()?; + + Common::filter_property_permissions(collection, keys) + } + + fn token_data( + collection: CollectionId, + token_id: TokenId, + keys: Option>> + ) -> Result, DispatchError> { + let token_data = TokenData { + properties: Self::token_properties(collection, token_id, keys)?, + owner: Self::token_owner(collection, token_id)?, + pieces: Self::total_pieces(collection, token_id)?.unwrap_or(0), + }; + + Ok(token_data) + } + + fn total_supply(collection: CollectionId) -> Result { + dispatch_unique_runtime!(collection.total_supply()) + } + fn account_balance(collection: CollectionId, account: CrossAccountId) -> Result { + dispatch_unique_runtime!(collection.account_balance(account)) + } + fn balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result { + dispatch_unique_runtime!(collection.balance(account, token)) + } + fn allowance( + collection: CollectionId, + sender: CrossAccountId, + spender: CrossAccountId, + token: TokenId, + ) -> Result { + dispatch_unique_runtime!(collection.allowance(sender, spender, token)) + } + + fn adminlist(collection: CollectionId) -> Result, DispatchError> { + Ok(>::adminlist(collection)) + } + fn allowlist(collection: CollectionId) -> Result, DispatchError> { + Ok(>::allowlist(collection)) + } + fn allowed(collection: CollectionId, user: CrossAccountId) -> Result { + Ok(>::allowed(collection, user)) + } + fn last_token_id(collection: CollectionId) -> Result { + dispatch_unique_runtime!(collection.last_token_id()) + } + fn collection_by_id(collection: CollectionId) -> Result>, DispatchError> { + Ok(>::rpc_collection(collection)) + } + fn collection_stats() -> Result { + Ok(>::collection_stats()) + } + fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result, DispatchError> { + Ok( as SponsorshipPredict>::predict( + collection, + account, + token + )) + } + + fn effective_collection_limits(collection: CollectionId) -> Result, DispatchError> { + Ok(>::effective_collection_limits(collection)) + } + + fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result, DispatchError> { + dispatch_unique_runtime!(collection.total_pieces(token_id)) + } + } + + impl rmrk_rpc::RmrkApi< + Block, + AccountId, + RmrkCollectionInfo, + RmrkInstanceInfo, + RmrkResourceInfo, + RmrkPropertyInfo, + RmrkBaseInfo, + RmrkPartType, + RmrkTheme + > for Runtime { + fn last_collection_idx() -> Result { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::last_collection_idx::(); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn collection_by_id(collection_id: RmrkCollectionId) -> Result>, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::collection_by_id::(collection_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result>, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::nft_by_id::(collection_id, nft_by_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::account_tokens::(account_id, collection_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::nft_children::(collection_id, nft_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn collection_properties( + collection_id: RmrkCollectionId, + filter_keys: Option> + ) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::collection_properties::(collection_id, filter_keys); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn nft_properties( + collection_id: RmrkCollectionId, + nft_id: RmrkNftId, + filter_keys: Option> + ) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::nft_properties::(collection_id, nft_id, filter_keys); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn nft_resources(collection_id: RmrkCollectionId,nft_id: RmrkNftId) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::nft_resources::(collection_id, nft_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn nft_resource_priority( + collection_id: RmrkCollectionId, + nft_id: RmrkNftId, + resource_id: RmrkResourceId + ) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_core::rpc::nft_resource_priority::(collection_id, nft_id, resource_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn base(base_id: RmrkBaseId) -> Result>, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_equip::rpc::base::(base_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn base_parts(base_id: RmrkBaseId) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_equip::rpc::base_parts::(base_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn theme_names(base_id: RmrkBaseId) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_equip::rpc::theme_names::(base_id); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + + #[allow(unused_variables)] + fn theme( + base_id: RmrkBaseId, + theme_name: RmrkThemeName, + filter_keys: Option> + ) -> Result, DispatchError> { + #[cfg(feature = "rmrk")] + return pallet_proxy_rmrk_equip::rpc::theme::(base_id, theme_name, filter_keys); + + #[cfg(not(feature = "rmrk"))] + return unsupported!(); + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + + // fn random_seed() -> ::Hash { + // RandomnessCollectiveFlip::random_seed().0 + // } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl fp_rpc::EthereumRuntimeRPCApi for Runtime { + fn chain_id() -> u64 { + ::ChainId::get() + } + + fn account_basic(address: H160) -> EVMAccount { + let (account, _) = EVM::account_basic(&address); + account + } + + fn gas_price() -> U256 { + let (price, _) = ::FeeCalculator::min_gas_price(); + price + } + + fn account_code_at(address: H160) -> Vec { + EVM::account_codes(address) + } + + fn author() -> H160 { + >::find_author() + } + + fn storage_at(address: H160, index: U256) -> H256 { + let mut tmp = [0u8; 32]; + index.to_big_endian(&mut tmp); + EVM::account_storages(address, H256::from_slice(&tmp[..])) + } + + #[allow(clippy::redundant_closure)] + fn call( + from: H160, + to: H160, + data: Vec, + value: U256, + gas_limit: U256, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + estimate: bool, + access_list: Option)>>, + ) -> Result { + let config = if estimate { + let mut config = ::config().clone(); + config.estimate = true; + Some(config) + } else { + None + }; + + let is_transactional = false; + ::Runner::call( + CrossAccountId::from_eth(from), + to, + data, + value, + gas_limit.low_u64(), + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list.unwrap_or_default(), + is_transactional, + config.as_ref().unwrap_or_else(|| ::config()), + ).map_err(|err| err.error.into()) + } + + #[allow(clippy::redundant_closure)] + fn create( + from: H160, + data: Vec, + value: U256, + gas_limit: U256, + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + nonce: Option, + estimate: bool, + access_list: Option)>>, + ) -> Result { + let config = if estimate { + let mut config = ::config().clone(); + config.estimate = true; + Some(config) + } else { + None + }; + + let is_transactional = false; + ::Runner::create( + CrossAccountId::from_eth(from), + data, + value, + gas_limit.low_u64(), + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list.unwrap_or_default(), + is_transactional, + config.as_ref().unwrap_or_else(|| ::config()), + ).map_err(|err| err.error.into()) + } + + fn current_transaction_statuses() -> Option> { + Ethereum::current_transaction_statuses() + } + + fn current_block() -> Option { + Ethereum::current_block() + } + + fn current_receipts() -> Option> { + Ethereum::current_receipts() + } + + fn current_all() -> ( + Option, + Option>, + Option> + ) { + ( + Ethereum::current_block(), + Ethereum::current_receipts(), + Ethereum::current_transaction_statuses() + ) + } + + fn extrinsic_filter(xts: Vec<::Extrinsic>) -> Vec { + xts.into_iter().filter_map(|xt| match xt.0.function { + Call::Ethereum(pallet_ethereum::Call::transact { transaction }) => Some(transaction), + _ => None + }).collect() + } + + fn elasticity() -> Option { + None + } + } + + impl fp_rpc::ConvertTransactionRuntimeApi for Runtime { + fn convert_transaction(transaction: pallet_ethereum::Transaction) -> ::Extrinsic { + UncheckedExtrinsic::new_unsigned( + pallet_ethereum::Call::::transact { transaction }.into(), + ) + } + } + + impl sp_session::SessionKeys for Runtime { + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + } + + fn authorities() -> Vec { + Aura::authorities().to_vec() + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Index { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info(uxt: ::Extrinsic, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details(uxt: ::Extrinsic, len: u32) -> FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + } + + /* + impl pallet_contracts_rpc_runtime_api::ContractsApi + for Runtime + { + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: u64, + input_data: Vec, + ) -> pallet_contracts_primitives::ContractExecResult { + Contracts::bare_call(origin, dest, value, gas_limit, input_data, false) + } + + fn instantiate( + origin: AccountId, + endowment: Balance, + gas_limit: u64, + code: pallet_contracts_primitives::Code, + data: Vec, + salt: Vec, + ) -> pallet_contracts_primitives::ContractInstantiateResult + { + Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true, false) + } + + fn get_storage( + address: AccountId, + key: [u8; 32], + ) -> pallet_contracts_primitives::GetStorageResult { + Contracts::get_storage(address, key) + } + + fn rent_projection( + address: AccountId, + ) -> pallet_contracts_primitives::RentProjectionResult { + Contracts::rent_projection(address) + } + } + */ + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + + let mut list = Vec::::new(); + + list_benchmark!(list, extra, pallet_evm_migration, EvmMigration); + list_benchmark!(list, extra, pallet_common, Common); + list_benchmark!(list, extra, pallet_unique, Unique); + list_benchmark!(list, extra, pallet_structure, Structure); + list_benchmark!(list, extra, pallet_inflation, Inflation); + list_benchmark!(list, extra, pallet_fungible, Fungible); + list_benchmark!(list, extra, pallet_refungible, Refungible); + list_benchmark!(list, extra, pallet_nonfungible, Nonfungible); + list_benchmark!(list, extra, pallet_unique_scheduler, Scheduler); + + #[cfg(not(feature = "unique-runtime"))] + list_benchmark!(list, extra, pallet_proxy_rmrk_core, RmrkCore); + + #[cfg(not(feature = "unique-runtime"))] + list_benchmark!(list, extra, pallet_proxy_rmrk_equip, RmrkEquip); + + // list_benchmark!(list, extra, pallet_evm_coder_substrate, EvmCoderSubstrate); + + let storage_info = AllPalletsReversedWithSystemFirst::storage_info(); + + return (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey}; + + let allowlist: Vec = vec![ + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + + // Evm CurrentLogs + hex_literal::hex!("1da53b775b270400e7e61ed5cbc5a146547f210cec367e9af919603343b9cb56").to_vec().into(), + + // Transactional depth + hex_literal::hex!("3a7472616e73616374696f6e5f6c6576656c3a").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &allowlist); + + add_benchmark!(params, batches, pallet_evm_migration, EvmMigration); + add_benchmark!(params, batches, pallet_common, Common); + add_benchmark!(params, batches, pallet_unique, Unique); + add_benchmark!(params, batches, pallet_structure, Structure); + add_benchmark!(params, batches, pallet_inflation, Inflation); + add_benchmark!(params, batches, pallet_fungible, Fungible); + add_benchmark!(params, batches, pallet_refungible, Refungible); + add_benchmark!(params, batches, pallet_nonfungible, Nonfungible); + add_benchmark!(params, batches, pallet_unique_scheduler, Scheduler); + + #[cfg(not(feature = "unique-runtime"))] + add_benchmark!(params, batches, pallet_proxy_rmrk_core, RmrkCore); + + #[cfg(not(feature = "unique-runtime"))] + add_benchmark!(params, batches, pallet_proxy_rmrk_equip, RmrkEquip); + + // add_benchmark!(params, batches, pallet_evm_coder_substrate, EvmCoderSubstrate); + + if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } + Ok(batches) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade() -> (Weight, Weight) { + log::info!("try-runtime::on_runtime_upgrade unique-chain."); + let weight = Executive::try_runtime_upgrade().unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block_no_check(block: Block) -> Weight { + Executive::execute_block_no_check(block) + } + } + } + } +} --- /dev/null +++ b/runtime/common/scheduler.rs @@ -0,0 +1,141 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use frame_support::{ + traits::NamedReservableCurrency, + weights::{GetDispatchInfo, PostDispatchInfo, DispatchInfo}, +}; +use sp_runtime::{ + traits::{Dispatchable, Applyable, Member}, + generic::Era, + transaction_validity::TransactionValidityError, + DispatchErrorWithPostInfo, DispatchError, +}; +use crate::{Runtime, Call, Origin, Balances, ChargeTransactionPayment}; +use up_common::types::{AccountId, Balance}; +use fp_self_contained::SelfContainedCall; +use pallet_unique_scheduler::DispatchCall; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtraScheduler = ( + frame_system::CheckSpecVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, +); + +fn get_signed_extras(from: ::AccountId) -> SignedExtraScheduler { + ( + frame_system::CheckSpecVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(Era::Immortal), + frame_system::CheckNonce::::from(frame_system::Pallet::::account_nonce( + from, + )), + frame_system::CheckWeight::::new(), + // sponsoring transaction logic + // pallet_charge_transaction::ChargeTransactionPayment::::new(0), + ) +} + +pub struct SchedulerPaymentExecutor; + +impl + DispatchCall for SchedulerPaymentExecutor +where + ::Call: Member + + Dispatchable + + SelfContainedCall + + GetDispatchInfo + + From>, + SelfContainedSignedInfo: Send + Sync + 'static, + Call: From<::Call> + + From<::Call> + + SelfContainedCall, + sp_runtime::AccountId32: From<::AccountId>, +{ + fn dispatch_call( + signer: ::AccountId, + call: ::Call, + ) -> Result< + Result>, + TransactionValidityError, + > { + let dispatch_info = call.get_dispatch_info(); + let extrinsic = fp_self_contained::CheckedExtrinsic::< + AccountId, + Call, + SignedExtraScheduler, + SelfContainedSignedInfo, + > { + signed: fp_self_contained::CheckedSignature::< + AccountId, + SignedExtraScheduler, + SelfContainedSignedInfo, + >::Signed(signer.clone().into(), get_signed_extras(signer.into())), + function: call.into(), + }; + + extrinsic.apply::(&dispatch_info, 0) + } + + fn reserve_balance( + id: [u8; 16], + sponsor: ::AccountId, + call: ::Call, + count: u32, + ) -> Result<(), DispatchError> { + let dispatch_info = call.get_dispatch_info(); + let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0) + .saturating_mul(count.into()); + + >::reserve_named( + &id, + &(sponsor.into()), + weight, + ) + } + + fn pay_for_call( + id: [u8; 16], + sponsor: ::AccountId, + call: ::Call, + ) -> Result { + let dispatch_info = call.get_dispatch_info(); + let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0); + Ok( + >::unreserve_named( + &id, + &(sponsor.into()), + weight, + ), + ) + } + + fn cancel_reserve( + id: [u8; 16], + sponsor: ::AccountId, + ) -> Result { + Ok( + >::unreserve_named( + &id, + &(sponsor.into()), + u128::MAX, + ), + ) + } +} --- /dev/null +++ b/runtime/common/sponsoring.rs @@ -0,0 +1,359 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use core::marker::PhantomData; +use up_sponsorship::SponsorshipHandler; +use frame_support::{ + traits::{IsSubType}, + storage::{StorageMap, StorageDoubleMap, StorageNMap}, +}; +use up_data_structs::{ + CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT, + REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode, CreateItemData, +}; +use sp_runtime::traits::Saturating; +use pallet_common::{CollectionHandle}; +use pallet_evm::account::CrossAccountId; +use pallet_unique::{ + Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket, + NftApproveBasket, CreateItemBasket, ReFungibleTransferBasket, FungibleTransferBasket, + NftTransferBasket, TokenPropertyBasket, +}; +use pallet_fungible::Config as FungibleConfig; +use pallet_nonfungible::Config as NonfungibleConfig; +use pallet_refungible::Config as RefungibleConfig; + +pub trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {} +impl Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {} + +// TODO: permission check? +pub fn withdraw_set_token_property( + collection: &CollectionHandle, + who: &T::CrossAccountId, + item_id: &TokenId, + data_size: usize, +) -> Option<()> { + // preliminary sponsoring correctness check + match collection.mode { + CollectionMode::NFT => { + let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; + if !owner.conv_eq(who) { + return None; + } + } + CollectionMode::Fungible(_) => { + // Fungible tokens have no properties + return None; + } + CollectionMode::ReFungible => { + if !>::get((collection.id, who, item_id)) { + return None; + } + } + } + + if data_size > collection.limits.sponsored_data_size() as usize { + return None; + } + + let block_number = >::block_number() as T::BlockNumber; + let limit = collection.limits.sponsored_data_rate_limit()?; + + if let Some(last_tx_block) = TokenPropertyBasket::::get(collection.id, item_id) { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + >::insert(collection.id, item_id, block_number); + + Some(()) +} + +pub fn withdraw_transfer( + collection: &CollectionHandle, + who: &T::CrossAccountId, + item_id: &TokenId, +) -> Option<()> { + // preliminary sponsoring correctness check + match collection.mode { + CollectionMode::NFT => { + let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; + if !owner.conv_eq(who) { + return None; + } + } + CollectionMode::Fungible(_) => { + if item_id != &TokenId::default() { + return None; + } + if >::get((collection.id, who)) == 0 { + return None; + } + } + CollectionMode::ReFungible => { + if !>::get((collection.id, who, item_id)) { + return None; + } + } + } + + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match collection.mode { + CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); + + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, item_id), + CollectionMode::Fungible(_) => { + >::get(collection.id, who.as_sub()) + } + CollectionMode::ReFungible => { + >::get((collection.id, item_id, who.as_sub())) + } + }; + + if let Some(last_tx_block) = last_tx_block { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + match collection.mode { + CollectionMode::NFT => >::insert(collection.id, item_id, block_number), + CollectionMode::Fungible(_) => { + >::insert(collection.id, who.as_sub(), block_number) + } + CollectionMode::ReFungible => >::insert( + (collection.id, item_id, who.as_sub()), + block_number, + ), + }; + + Some(()) +} + +pub fn withdraw_create_item( + collection: &CollectionHandle, + who: &T::CrossAccountId, + properties: &CreateItemData, +) -> Option<()> { + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match properties { + CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT, + CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); + + if let Some(last_tx_block) = >::get((collection.id, who.as_sub())) { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + CreateItemBasket::::insert((collection.id, who.as_sub()), block_number); + + Some(()) +} + +pub fn withdraw_approve( + collection: &CollectionHandle, + who: &T::AccountId, + item_id: &TokenId, +) -> Option<()> { + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection.limits.sponsor_approve_timeout(); + + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, item_id), + CollectionMode::Fungible(_) => >::get(collection.id, who), + CollectionMode::ReFungible => { + >::get((collection.id, item_id, who)) + } + }; + + if let Some(last_tx_block) = last_tx_block { + let timeout = last_tx_block + limit.into(); + if block_number < timeout { + return None; + } + } + + match collection.mode { + CollectionMode::NFT => >::insert(collection.id, item_id, block_number), + CollectionMode::Fungible(_) => { + >::insert(collection.id, who, block_number) + } + CollectionMode::ReFungible => { + >::insert((collection.id, item_id, who), block_number) + } + }; + + Some(()) +} + +fn load(id: CollectionId) -> Option<(T::AccountId, CollectionHandle)> { + let collection = CollectionHandle::new(id)?; + let sponsor = collection.sponsorship.sponsor().cloned()?; + Some((sponsor, collection)) +} + +pub struct UniqueSponsorshipHandler(PhantomData); +impl SponsorshipHandler for UniqueSponsorshipHandler +where + T: Config, + C: IsSubType>, +{ + fn get_sponsor(who: &T::AccountId, call: &C) -> Option { + match IsSubType::>::is_sub_type(call)? { + UniqueCall::set_token_properties { + collection_id, + token_id, + properties, + .. + } => { + let (sponsor, collection) = load::(*collection_id)?; + withdraw_set_token_property( + &collection, + &T::CrossAccountId::from_sub(who.clone()), + &token_id, + // No overflow may happen, as data larger than usize can't reach here + properties.iter().map(|p| p.key.len() + p.value.len()).sum(), + ) + .map(|()| sponsor) + } + UniqueCall::create_item { + collection_id, + data, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_create_item::( + &collection, + &T::CrossAccountId::from_sub(who.clone()), + data, + ) + .map(|()| sponsor) + } + UniqueCall::transfer { + collection_id, + item_id, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_transfer::( + &collection, + &T::CrossAccountId::from_sub(who.clone()), + item_id, + ) + .map(|()| sponsor) + } + UniqueCall::transfer_from { + collection_id, + item_id, + from, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_transfer::(&collection, from, item_id).map(|()| sponsor) + } + UniqueCall::approve { + collection_id, + item_id, + .. + } => { + let (sponsor, collection) = load(*collection_id)?; + withdraw_approve::(&collection, who, item_id).map(|()| sponsor) + } + _ => None, + } + } +} + +pub trait SponsorshipPredict { + fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option + where + u64: From<::BlockNumber>; +} + +pub struct UniqueSponsorshipPredict(PhantomData); + +impl SponsorshipPredict for UniqueSponsorshipPredict { + fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option + where + u64: From<::BlockNumber>, + { + let collection = >::try_get(collection_id).ok()?; + let _ = collection.sponsorship.sponsor()?; + + // sponsor timeout + let block_number = >::block_number() as T::BlockNumber; + let limit = collection + .limits + .sponsor_transfer_timeout(match collection.mode { + CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }); + + let last_tx_block = match collection.mode { + CollectionMode::NFT => >::get(collection.id, token), + CollectionMode::Fungible(_) => { + >::get(collection.id, who.as_sub()) + } + CollectionMode::ReFungible => { + >::get((collection.id, token, who.as_sub())) + } + }; + + if let Some(last_tx_block) = last_tx_block { + return Some( + last_tx_block + .saturating_add(limit.into()) + .saturating_sub(block_number) + .into(), + ); + } + + let token_exists = match collection.mode { + CollectionMode::NFT => { + >::contains_key((collection.id, token)) + } + CollectionMode::Fungible(_) => token == TokenId::default(), + CollectionMode::ReFungible => { + >::contains_key((collection.id, token)) + } + }; + + if token_exists { + Some(0) + } else { + None + } + } +} --- a/runtime/common/src/constants.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -use sp_runtime::Perbill; -use frame_support::{ - parameter_types, - weights::{Weight, constants::WEIGHT_PER_SECOND}, -}; -use crate::types::{BlockNumber, Balance}; - -pub const MILLISECS_PER_BLOCK: u64 = 12000; - -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; - -// These time units are defined in number of blocks. -pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); -pub const HOURS: BlockNumber = MINUTES * 60; -pub const DAYS: BlockNumber = HOURS * 24; - -pub const MICROUNIQUE: Balance = 1_000_000_000_000; -pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE; -pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE; -pub const UNIQUE: Balance = 100 * CENTIUNIQUE; - -// Targeting 0.1 UNQ per transfer -pub const WEIGHT_TO_FEE_COEFF: u32 = 207_890_902; - -// Targeting 0.15 UNQ per transfer via ETH -pub const MIN_GAS_PRICE: u64 = 1_019_493_469_850; - -/// We assume that ~10% of the block weight is consumed by `on_initalize` handlers. -/// This is used to limit the maximal weight of a single extrinsic. -pub const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); -/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used -/// by Operational extrinsics. -pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -/// We allow for 2 seconds of compute with a 6 second average block time. -pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND / 2; - -parameter_types! { - pub const DefaultSponsoringRateLimit: BlockNumber = 1 * DAYS; - - pub const TransactionByteFee: Balance = 501 * MICROUNIQUE; -} --- a/runtime/common/src/dispatch.rs +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -use frame_support::{dispatch::DispatchResult, ensure}; -use pallet_evm::{PrecompileHandle, PrecompileResult}; -use sp_core::H160; -use sp_runtime::DispatchError; -use sp_std::{borrow::ToOwned, vec::Vec}; -use pallet_common::{ - CollectionById, CollectionHandle, CommonCollectionOperations, erc::CommonEvmHandler, - eth::map_eth_to_id, -}; -pub use pallet_common::dispatch::CollectionDispatch; -use pallet_fungible::{Pallet as PalletFungible, FungibleHandle}; -use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle}; -use pallet_refungible::{ - Pallet as PalletRefungible, RefungibleHandle, erc_token::RefungibleTokenHandle, -}; -use up_data_structs::{ - CollectionMode, CreateCollectionData, MAX_DECIMAL_POINTS, mapping::TokenAddressMapping, - CollectionId, -}; - -pub enum CollectionDispatchT -where - T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config, -{ - Fungible(FungibleHandle), - Nonfungible(NonfungibleHandle), - Refungible(RefungibleHandle), -} -impl CollectionDispatch for CollectionDispatchT -where - T: pallet_common::Config - + pallet_unique::Config - + pallet_fungible::Config - + pallet_nonfungible::Config - + pallet_refungible::Config, -{ - fn create( - sender: T::CrossAccountId, - data: CreateCollectionData, - ) -> Result { - let id = match data.mode { - CollectionMode::NFT => >::init_collection(sender, data, false)?, - CollectionMode::Fungible(decimal_points) => { - // check params - ensure!( - decimal_points <= MAX_DECIMAL_POINTS, - pallet_unique::Error::::CollectionDecimalPointLimitExceeded - ); - >::init_collection(sender, data)? - } - CollectionMode::ReFungible => >::init_collection(sender, data)?, - }; - Ok(id) - } - - fn destroy(sender: T::CrossAccountId, collection: CollectionHandle) -> DispatchResult { - match collection.mode { - CollectionMode::ReFungible => { - PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)? - } - CollectionMode::Fungible(_) => { - PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)? - } - CollectionMode::NFT => { - PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)? - } - } - Ok(()) - } - - fn dispatch(handle: CollectionHandle) -> Self { - match handle.mode { - CollectionMode::Fungible(_) => Self::Fungible(FungibleHandle::cast(handle)), - CollectionMode::NFT => Self::Nonfungible(NonfungibleHandle::cast(handle)), - CollectionMode::ReFungible => Self::Refungible(RefungibleHandle::cast(handle)), - } - } - - fn into_inner(self) -> CollectionHandle { - match self { - Self::Fungible(f) => f.into_inner(), - Self::Nonfungible(f) => f.into_inner(), - Self::Refungible(f) => f.into_inner(), - } - } - - fn as_dyn(&self) -> &dyn CommonCollectionOperations { - match self { - Self::Fungible(h) => h, - Self::Nonfungible(h) => h, - Self::Refungible(h) => h, - } - } -} - -impl pallet_evm::OnMethodCall for CollectionDispatchT -where - T: pallet_common::Config - + pallet_unique::Config - + pallet_fungible::Config - + pallet_nonfungible::Config - + pallet_refungible::Config, - T::AccountId: From<[u8; 32]>, -{ - fn is_reserved(target: &H160) -> bool { - map_eth_to_id(target).is_some() - } - fn is_used(target: &H160) -> bool { - map_eth_to_id(target) - .map(>::contains_key) - .unwrap_or(false) - } - fn get_code(target: &H160) -> Option> { - if let Some(collection_id) = map_eth_to_id(target) { - let collection = >::get(collection_id)?; - Some( - match collection.mode { - CollectionMode::NFT => >::CODE, - CollectionMode::Fungible(_) => >::CODE, - CollectionMode::ReFungible => >::CODE, - } - .to_owned(), - ) - } else if let Some((collection_id, _token_id)) = - ::EvmTokenAddressMapping::address_to_token(target) - { - let collection = >::get(collection_id)?; - if collection.mode != CollectionMode::ReFungible { - return None; - } - // TODO: check token existence - Some(>::CODE.to_owned()) - } else { - None - } - } - fn call(handle: &mut impl PrecompileHandle) -> Option { - if let Some(collection_id) = map_eth_to_id(&handle.code_address()) { - let collection = - >::new_with_gas_limit(collection_id, handle.remaining_gas())?; - let dispatched = Self::dispatch(collection); - - match dispatched { - Self::Fungible(h) => h.call(handle), - Self::Nonfungible(h) => h.call(handle), - Self::Refungible(h) => h.call(handle), - } - } else if let Some((collection_id, token_id)) = - ::EvmTokenAddressMapping::address_to_token( - &handle.code_address(), - ) { - let collection = - >::new_with_gas_limit(collection_id, handle.remaining_gas())?; - if collection.mode != CollectionMode::ReFungible { - return None; - } - - let h = RefungibleHandle::cast(collection); - // TODO: check token existence - RefungibleTokenHandle(h, token_id).call(handle) - } else { - None - } - } -} --- a/runtime/common/src/eth_sponsoring.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -//! Implements EVM sponsoring logic via TransactionValidityHack - -use evm_coder::{Call, abi::AbiReader}; -use pallet_common::{CollectionHandle, eth::map_eth_to_id}; -use sp_core::H160; -use sp_std::prelude::*; -use up_sponsorship::SponsorshipHandler; -use core::marker::PhantomData; -use core::convert::TryInto; -use pallet_evm::account::CrossAccountId; -use up_data_structs::{TokenId, CreateItemData, CreateNftData, CollectionMode}; -use pallet_unique::Config as UniqueConfig; - -use crate::sponsoring::*; - -use pallet_nonfungible::erc::{ - UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721MintableCall, ERC721Call, TokenPropertiesCall, -}; -use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call}; -use pallet_fungible::Config as FungibleConfig; -use pallet_nonfungible::Config as NonfungibleConfig; -use pallet_refungible::Config as RefungibleConfig; - -pub struct UniqueEthSponsorshipHandler(PhantomData<*const T>); -impl - SponsorshipHandler)> for UniqueEthSponsorshipHandler -{ - fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec)) -> Option { - let collection_id = map_eth_to_id(&call.0)?; - let collection = >::new(collection_id)?; - let sponsor = collection.sponsorship.sponsor()?.clone(); - let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?; - Some(T::CrossAccountId::from_sub(match &collection.mode { - CollectionMode::NFT => { - let call = >::parse(method_id, &mut reader).ok()??; - match call { - UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty { - token_id, - key, - value, - .. - }) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_set_token_property::( - &collection, - &who, - &token_id, - key.len() + value.len(), - ) - .map(|()| sponsor) - } - UniqueNFTCall::ERC721UniqueExtensions( - ERC721UniqueExtensionsCall::Transfer { token_id, .. }, - ) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_transfer::(&collection, &who, &token_id).map(|()| sponsor) - } - UniqueNFTCall::ERC721Mintable( - ERC721MintableCall::Mint { token_id, .. } - | ERC721MintableCall::MintWithTokenUri { token_id, .. }, - ) => { - let _token_id: TokenId = token_id.try_into().ok()?; - withdraw_create_item::( - &collection, - &who, - &CreateItemData::NFT(CreateNftData::default()), - ) - .map(|()| sponsor) - } - UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => { - let token_id: TokenId = token_id.try_into().ok()?; - let from = T::CrossAccountId::from_eth(from); - withdraw_transfer::(&collection, &from, &token_id).map(|()| sponsor) - } - UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_approve::(&collection, who.as_sub(), &token_id) - .map(|()| sponsor) - } - _ => None, - } - } - CollectionMode::Fungible(_) => { - let call = >::parse(method_id, &mut reader).ok()??; - #[allow(clippy::single_match)] - match call { - UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => { - withdraw_transfer::(&collection, who, &TokenId::default()) - .map(|()| sponsor) - } - UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => { - let from = T::CrossAccountId::from_eth(from); - withdraw_transfer::(&collection, &from, &TokenId::default()) - .map(|()| sponsor) - } - UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => { - withdraw_approve::(&collection, who.as_sub(), &TokenId::default()) - .map(|()| sponsor) - } - _ => None, - } - } - _ => None, - }?)) - } -} --- a/runtime/common/src/lib.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] - -pub mod constants; -pub mod dispatch; -pub mod eth_sponsoring; -pub mod runtime_apis; -pub mod sponsoring; -pub mod types; -pub mod weights; --- a/runtime/common/src/runtime_apis.rs +++ /dev/null @@ -1,546 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -#[macro_export] -macro_rules! impl_common_runtime_apis { - ( - $( - #![custom_apis] - - $($custom_apis:tt)+ - )? - ) => { - impl_runtime_apis! { - $($($custom_apis)+)? - - impl up_rpc::UniqueApi for Runtime { - fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result, DispatchError> { - dispatch_unique_runtime!(collection.account_tokens(account)) - } - fn collection_tokens(collection: CollectionId) -> Result, DispatchError> { - dispatch_unique_runtime!(collection.collection_tokens()) - } - fn token_exists(collection: CollectionId, token: TokenId) -> Result { - dispatch_unique_runtime!(collection.token_exists(token)) - } - - fn token_owner(collection: CollectionId, token: TokenId) -> Result, DispatchError> { - dispatch_unique_runtime!(collection.token_owner(token)) - } - - fn token_owners(collection: CollectionId, token: TokenId) -> Result, DispatchError> { - dispatch_unique_runtime!(collection.token_owners(token)) - } - - fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result, DispatchError> { - let budget = up_data_structs::budget::Value::new(10); - - Ok(Some(>::find_topmost_owner(collection, token, &budget)?)) - } - fn token_children(collection: CollectionId, token: TokenId) -> Result, DispatchError> { - Ok(>::token_children_ids(collection, token)) - } - fn collection_properties( - collection: CollectionId, - keys: Option>> - ) -> Result, DispatchError> { - let keys = keys.map( - |keys| Common::bytes_keys_to_property_keys(keys) - ).transpose()?; - - Common::filter_collection_properties(collection, keys) - } - - fn token_properties( - collection: CollectionId, - token_id: TokenId, - keys: Option>> - ) -> Result, DispatchError> { - let keys = keys.map( - |keys| Common::bytes_keys_to_property_keys(keys) - ).transpose()?; - - dispatch_unique_runtime!(collection.token_properties(token_id, keys)) - } - - fn property_permissions( - collection: CollectionId, - keys: Option>> - ) -> Result, DispatchError> { - let keys = keys.map( - |keys| Common::bytes_keys_to_property_keys(keys) - ).transpose()?; - - Common::filter_property_permissions(collection, keys) - } - - fn token_data( - collection: CollectionId, - token_id: TokenId, - keys: Option>> - ) -> Result, DispatchError> { - let token_data = TokenData { - properties: Self::token_properties(collection, token_id, keys)?, - owner: Self::token_owner(collection, token_id)?, - pieces: Self::total_pieces(collection, token_id)?.unwrap_or(0), - }; - - Ok(token_data) - } - - fn total_supply(collection: CollectionId) -> Result { - dispatch_unique_runtime!(collection.total_supply()) - } - fn account_balance(collection: CollectionId, account: CrossAccountId) -> Result { - dispatch_unique_runtime!(collection.account_balance(account)) - } - fn balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result { - dispatch_unique_runtime!(collection.balance(account, token)) - } - fn allowance( - collection: CollectionId, - sender: CrossAccountId, - spender: CrossAccountId, - token: TokenId, - ) -> Result { - dispatch_unique_runtime!(collection.allowance(sender, spender, token)) - } - - fn adminlist(collection: CollectionId) -> Result, DispatchError> { - Ok(>::adminlist(collection)) - } - fn allowlist(collection: CollectionId) -> Result, DispatchError> { - Ok(>::allowlist(collection)) - } - fn allowed(collection: CollectionId, user: CrossAccountId) -> Result { - Ok(>::allowed(collection, user)) - } - fn last_token_id(collection: CollectionId) -> Result { - dispatch_unique_runtime!(collection.last_token_id()) - } - fn collection_by_id(collection: CollectionId) -> Result>, DispatchError> { - Ok(>::rpc_collection(collection)) - } - fn collection_stats() -> Result { - Ok(>::collection_stats()) - } - fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result, DispatchError> { - Ok(<$crate::sponsoring::UniqueSponsorshipPredict as - $crate::sponsoring::SponsorshipPredict>::predict( - collection, - account, - token)) - } - - fn effective_collection_limits(collection: CollectionId) -> Result, DispatchError> { - Ok(>::effective_collection_limits(collection)) - } - - fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result, DispatchError> { - dispatch_unique_runtime!(collection.total_pieces(token_id)) - } - } - - impl sp_api::Core for Runtime { - fn version() -> RuntimeVersion { - VERSION - } - - fn execute_block(block: Block) { - Executive::execute_block(block) - } - - fn initialize_block(header: &::Header) { - Executive::initialize_block(header) - } - } - - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) - } - } - - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> ::Header { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { - data.create_extrinsics() - } - - fn check_inherents( - block: Block, - data: sp_inherents::InherentData, - ) -> sp_inherents::CheckInherentsResult { - data.check_extrinsics(&block) - } - - // fn random_seed() -> ::Hash { - // RandomnessCollectiveFlip::random_seed().0 - // } - } - - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ::Extrinsic, - hash: ::Hash, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx, hash) - } - } - - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - Executive::offchain_worker(header) - } - } - - impl fp_rpc::EthereumRuntimeRPCApi for Runtime { - fn chain_id() -> u64 { - ::ChainId::get() - } - - fn account_basic(address: H160) -> EVMAccount { - let (account, _) = EVM::account_basic(&address); - account - } - - fn gas_price() -> U256 { - let (price, _) = ::FeeCalculator::min_gas_price(); - price - } - - fn account_code_at(address: H160) -> Vec { - EVM::account_codes(address) - } - - fn author() -> H160 { - >::find_author() - } - - fn storage_at(address: H160, index: U256) -> H256 { - let mut tmp = [0u8; 32]; - index.to_big_endian(&mut tmp); - EVM::account_storages(address, H256::from_slice(&tmp[..])) - } - - #[allow(clippy::redundant_closure)] - fn call( - from: H160, - to: H160, - data: Vec, - value: U256, - gas_limit: U256, - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - nonce: Option, - estimate: bool, - access_list: Option)>>, - ) -> Result { - let config = if estimate { - let mut config = ::config().clone(); - config.estimate = true; - Some(config) - } else { - None - }; - - let is_transactional = false; - ::Runner::call( - CrossAccountId::from_eth(from), - to, - data, - value, - gas_limit.low_u64(), - max_fee_per_gas, - max_priority_fee_per_gas, - nonce, - access_list.unwrap_or_default(), - is_transactional, - config.as_ref().unwrap_or_else(|| ::config()), - ).map_err(|err| err.error.into()) - } - - #[allow(clippy::redundant_closure)] - fn create( - from: H160, - data: Vec, - value: U256, - gas_limit: U256, - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - nonce: Option, - estimate: bool, - access_list: Option)>>, - ) -> Result { - let config = if estimate { - let mut config = ::config().clone(); - config.estimate = true; - Some(config) - } else { - None - }; - - let is_transactional = false; - ::Runner::create( - CrossAccountId::from_eth(from), - data, - value, - gas_limit.low_u64(), - max_fee_per_gas, - max_priority_fee_per_gas, - nonce, - access_list.unwrap_or_default(), - is_transactional, - config.as_ref().unwrap_or_else(|| ::config()), - ).map_err(|err| err.error.into()) - } - - fn current_transaction_statuses() -> Option> { - Ethereum::current_transaction_statuses() - } - - fn current_block() -> Option { - Ethereum::current_block() - } - - fn current_receipts() -> Option> { - Ethereum::current_receipts() - } - - fn current_all() -> ( - Option, - Option>, - Option> - ) { - ( - Ethereum::current_block(), - Ethereum::current_receipts(), - Ethereum::current_transaction_statuses() - ) - } - - fn extrinsic_filter(xts: Vec<::Extrinsic>) -> Vec { - xts.into_iter().filter_map(|xt| match xt.0.function { - Call::Ethereum(pallet_ethereum::Call::transact { transaction }) => Some(transaction), - _ => None - }).collect() - } - - fn elasticity() -> Option { - None - } - } - - impl fp_rpc::ConvertTransactionRuntimeApi for Runtime { - fn convert_transaction(transaction: pallet_ethereum::Transaction) -> ::Extrinsic { - UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), - ) - } - } - - impl sp_session::SessionKeys for Runtime { - fn decode_session_keys( - encoded: Vec, - ) -> Option, KeyTypeId)>> { - SessionKeys::decode_into_raw_public_keys(&encoded) - } - - fn generate_session_keys(seed: Option>) -> Vec { - SessionKeys::generate(seed) - } - } - - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) - } - - fn authorities() -> Vec { - Aura::authorities().to_vec() - } - } - - impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info(header) - } - } - - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Index { - System::account_nonce(account) - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { - fn query_info(uxt: ::Extrinsic, len: u32) -> RuntimeDispatchInfo { - TransactionPayment::query_info(uxt, len) - } - fn query_fee_details(uxt: ::Extrinsic, len: u32) -> FeeDetails { - TransactionPayment::query_fee_details(uxt, len) - } - } - - /* - impl pallet_contracts_rpc_runtime_api::ContractsApi - for Runtime - { - fn call( - origin: AccountId, - dest: AccountId, - value: Balance, - gas_limit: u64, - input_data: Vec, - ) -> pallet_contracts_primitives::ContractExecResult { - Contracts::bare_call(origin, dest, value, gas_limit, input_data, false) - } - - fn instantiate( - origin: AccountId, - endowment: Balance, - gas_limit: u64, - code: pallet_contracts_primitives::Code, - data: Vec, - salt: Vec, - ) -> pallet_contracts_primitives::ContractInstantiateResult - { - Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true, false) - } - - fn get_storage( - address: AccountId, - key: [u8; 32], - ) -> pallet_contracts_primitives::GetStorageResult { - Contracts::get_storage(address, key) - } - - fn rent_projection( - address: AccountId, - ) -> pallet_contracts_primitives::RentProjectionResult { - Contracts::rent_projection(address) - } - } - */ - - #[cfg(feature = "runtime-benchmarks")] - impl frame_benchmarking::Benchmark for Runtime { - fn benchmark_metadata(extra: bool) -> ( - Vec, - Vec, - ) { - use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList}; - use frame_support::traits::StorageInfoTrait; - - let mut list = Vec::::new(); - - list_benchmark!(list, extra, pallet_evm_migration, EvmMigration); - list_benchmark!(list, extra, pallet_common, Common); - list_benchmark!(list, extra, pallet_unique, Unique); - list_benchmark!(list, extra, pallet_structure, Structure); - list_benchmark!(list, extra, pallet_inflation, Inflation); - list_benchmark!(list, extra, pallet_fungible, Fungible); - list_benchmark!(list, extra, pallet_refungible, Refungible); - list_benchmark!(list, extra, pallet_nonfungible, Nonfungible); - list_benchmark!(list, extra, pallet_unique_scheduler, Scheduler); - - #[cfg(not(feature = "unique-runtime"))] - list_benchmark!(list, extra, pallet_proxy_rmrk_core, RmrkCore); - - #[cfg(not(feature = "unique-runtime"))] - list_benchmark!(list, extra, pallet_proxy_rmrk_equip, RmrkEquip); - - // list_benchmark!(list, extra, pallet_evm_coder_substrate, EvmCoderSubstrate); - - let storage_info = AllPalletsReversedWithSystemFirst::storage_info(); - - return (list, storage_info) - } - - fn dispatch_benchmark( - config: frame_benchmarking::BenchmarkConfig - ) -> Result, sp_runtime::RuntimeString> { - use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey}; - - let allowlist: Vec = vec![ - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - - // Evm CurrentLogs - hex_literal::hex!("1da53b775b270400e7e61ed5cbc5a146547f210cec367e9af919603343b9cb56").to_vec().into(), - - // Transactional depth - hex_literal::hex!("3a7472616e73616374696f6e5f6c6576656c3a").to_vec().into(), - ]; - - let mut batches = Vec::::new(); - let params = (&config, &allowlist); - - add_benchmark!(params, batches, pallet_evm_migration, EvmMigration); - add_benchmark!(params, batches, pallet_common, Common); - add_benchmark!(params, batches, pallet_unique, Unique); - add_benchmark!(params, batches, pallet_structure, Structure); - add_benchmark!(params, batches, pallet_inflation, Inflation); - add_benchmark!(params, batches, pallet_fungible, Fungible); - add_benchmark!(params, batches, pallet_refungible, Refungible); - add_benchmark!(params, batches, pallet_nonfungible, Nonfungible); - add_benchmark!(params, batches, pallet_unique_scheduler, Scheduler); - - #[cfg(not(feature = "unique-runtime"))] - add_benchmark!(params, batches, pallet_proxy_rmrk_core, RmrkCore); - - #[cfg(not(feature = "unique-runtime"))] - add_benchmark!(params, batches, pallet_proxy_rmrk_equip, RmrkEquip); - - // add_benchmark!(params, batches, pallet_evm_coder_substrate, EvmCoderSubstrate); - - if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } - Ok(batches) - } - } - - #[cfg(feature = "try-runtime")] - impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade() -> (Weight, Weight) { - log::info!("try-runtime::on_runtime_upgrade unique-chain."); - let weight = Executive::try_runtime_upgrade().unwrap(); - (weight, RuntimeBlockWeights::get().max_block) - } - - fn execute_block_no_check(block: Block) -> Weight { - Executive::execute_block_no_check(block) - } - } - } - } -} --- a/runtime/common/src/sponsoring.rs +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -use core::marker::PhantomData; -use up_sponsorship::SponsorshipHandler; -use frame_support::{ - traits::{IsSubType}, - storage::{StorageMap, StorageDoubleMap, StorageNMap}, -}; -use up_data_structs::{ - CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT, - REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId, CollectionMode, CreateItemData, -}; -use sp_runtime::traits::Saturating; -use pallet_common::{CollectionHandle}; -use pallet_evm::account::CrossAccountId; -use pallet_unique::{ - Call as UniqueCall, Config as UniqueConfig, FungibleApproveBasket, RefungibleApproveBasket, - NftApproveBasket, CreateItemBasket, ReFungibleTransferBasket, FungibleTransferBasket, - NftTransferBasket, TokenPropertyBasket, -}; -use pallet_fungible::Config as FungibleConfig; -use pallet_nonfungible::Config as NonfungibleConfig; -use pallet_refungible::Config as RefungibleConfig; - -pub trait Config: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {} -impl Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {} - -// TODO: permission check? -pub fn withdraw_set_token_property( - collection: &CollectionHandle, - who: &T::CrossAccountId, - item_id: &TokenId, - data_size: usize, -) -> Option<()> { - // preliminary sponsoring correctness check - match collection.mode { - CollectionMode::NFT => { - let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; - if !owner.conv_eq(who) { - return None; - } - } - CollectionMode::Fungible(_) => { - // Fungible tokens have no properties - return None; - } - CollectionMode::ReFungible => { - if !>::get((collection.id, who, item_id)) { - return None; - } - } - } - - if data_size > collection.limits.sponsored_data_size() as usize { - return None; - } - - let block_number = >::block_number() as T::BlockNumber; - let limit = collection.limits.sponsored_data_rate_limit()?; - - if let Some(last_tx_block) = TokenPropertyBasket::::get(collection.id, item_id) { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - >::insert(collection.id, item_id, block_number); - - Some(()) -} - -pub fn withdraw_transfer( - collection: &CollectionHandle, - who: &T::CrossAccountId, - item_id: &TokenId, -) -> Option<()> { - // preliminary sponsoring correctness check - match collection.mode { - CollectionMode::NFT => { - let owner = pallet_nonfungible::TokenData::::get((collection.id, item_id))?.owner; - if !owner.conv_eq(who) { - return None; - } - } - CollectionMode::Fungible(_) => { - if item_id != &TokenId::default() { - return None; - } - if >::get((collection.id, who)) == 0 { - return None; - } - } - CollectionMode::ReFungible => { - if !>::get((collection.id, who, item_id)) { - return None; - } - } - } - - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection - .limits - .sponsor_transfer_timeout(match collection.mode { - CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }); - - let last_tx_block = match collection.mode { - CollectionMode::NFT => >::get(collection.id, item_id), - CollectionMode::Fungible(_) => { - >::get(collection.id, who.as_sub()) - } - CollectionMode::ReFungible => { - >::get((collection.id, item_id, who.as_sub())) - } - }; - - if let Some(last_tx_block) = last_tx_block { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - match collection.mode { - CollectionMode::NFT => >::insert(collection.id, item_id, block_number), - CollectionMode::Fungible(_) => { - >::insert(collection.id, who.as_sub(), block_number) - } - CollectionMode::ReFungible => >::insert( - (collection.id, item_id, who.as_sub()), - block_number, - ), - }; - - Some(()) -} - -pub fn withdraw_create_item( - collection: &CollectionHandle, - who: &T::CrossAccountId, - properties: &CreateItemData, -) -> Option<()> { - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection - .limits - .sponsor_transfer_timeout(match properties { - CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT, - CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }); - - if let Some(last_tx_block) = >::get((collection.id, who.as_sub())) { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - CreateItemBasket::::insert((collection.id, who.as_sub()), block_number); - - Some(()) -} - -pub fn withdraw_approve( - collection: &CollectionHandle, - who: &T::AccountId, - item_id: &TokenId, -) -> Option<()> { - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection.limits.sponsor_approve_timeout(); - - let last_tx_block = match collection.mode { - CollectionMode::NFT => >::get(collection.id, item_id), - CollectionMode::Fungible(_) => >::get(collection.id, who), - CollectionMode::ReFungible => { - >::get((collection.id, item_id, who)) - } - }; - - if let Some(last_tx_block) = last_tx_block { - let timeout = last_tx_block + limit.into(); - if block_number < timeout { - return None; - } - } - - match collection.mode { - CollectionMode::NFT => >::insert(collection.id, item_id, block_number), - CollectionMode::Fungible(_) => { - >::insert(collection.id, who, block_number) - } - CollectionMode::ReFungible => { - >::insert((collection.id, item_id, who), block_number) - } - }; - - Some(()) -} - -fn load(id: CollectionId) -> Option<(T::AccountId, CollectionHandle)> { - let collection = CollectionHandle::new(id)?; - let sponsor = collection.sponsorship.sponsor().cloned()?; - Some((sponsor, collection)) -} - -pub struct UniqueSponsorshipHandler(PhantomData); -impl SponsorshipHandler for UniqueSponsorshipHandler -where - T: Config, - C: IsSubType>, -{ - fn get_sponsor(who: &T::AccountId, call: &C) -> Option { - match IsSubType::>::is_sub_type(call)? { - UniqueCall::set_token_properties { - collection_id, - token_id, - properties, - .. - } => { - let (sponsor, collection) = load::(*collection_id)?; - withdraw_set_token_property( - &collection, - &T::CrossAccountId::from_sub(who.clone()), - &token_id, - // No overflow may happen, as data larger than usize can't reach here - properties.iter().map(|p| p.key.len() + p.value.len()).sum(), - ) - .map(|()| sponsor) - } - UniqueCall::create_item { - collection_id, - data, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_create_item::( - &collection, - &T::CrossAccountId::from_sub(who.clone()), - data, - ) - .map(|()| sponsor) - } - UniqueCall::transfer { - collection_id, - item_id, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_transfer::( - &collection, - &T::CrossAccountId::from_sub(who.clone()), - item_id, - ) - .map(|()| sponsor) - } - UniqueCall::transfer_from { - collection_id, - item_id, - from, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_transfer::(&collection, from, item_id).map(|()| sponsor) - } - UniqueCall::approve { - collection_id, - item_id, - .. - } => { - let (sponsor, collection) = load(*collection_id)?; - withdraw_approve::(&collection, who, item_id).map(|()| sponsor) - } - _ => None, - } - } -} - -pub trait SponsorshipPredict { - fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option - where - u64: From<::BlockNumber>; -} - -pub struct UniqueSponsorshipPredict(PhantomData); - -impl SponsorshipPredict for UniqueSponsorshipPredict { - fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option - where - u64: From<::BlockNumber>, - { - let collection = >::try_get(collection_id).ok()?; - let _ = collection.sponsorship.sponsor()?; - - // sponsor timeout - let block_number = >::block_number() as T::BlockNumber; - let limit = collection - .limits - .sponsor_transfer_timeout(match collection.mode { - CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }); - - let last_tx_block = match collection.mode { - CollectionMode::NFT => >::get(collection.id, token), - CollectionMode::Fungible(_) => { - >::get(collection.id, who.as_sub()) - } - CollectionMode::ReFungible => { - >::get((collection.id, token, who.as_sub())) - } - }; - - if let Some(last_tx_block) = last_tx_block { - return Some( - last_tx_block - .saturating_add(limit.into()) - .saturating_sub(block_number) - .into(), - ); - } - - let token_exists = match collection.mode { - CollectionMode::NFT => { - >::contains_key((collection.id, token)) - } - CollectionMode::Fungible(_) => token == TokenId::default(), - CollectionMode::ReFungible => { - >::contains_key((collection.id, token)) - } - }; - - if token_exists { - Some(0) - } else { - None - } - } -} --- a/runtime/common/src/types.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -use sp_runtime::{ - traits::{Verify, IdentifyAccount, BlakeTwo256}, - generic, MultiSignature, -}; - -pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; - -/// Opaque block header type. -pub type Header = generic::Header; - -/// Opaque block type. -pub type Block = generic::Block; - -pub type SessionHandlers = (); - -/// An index to a block. -pub type BlockNumber = u32; - -/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = MultiSignature; - -/// Some way of identifying an account on the chain. We intentionally make it equivalent -/// to the public key of our transaction signing scheme. -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; - -/// The type for looking up accounts. We don't expect more than 4 billion of them, but you -/// never know... -pub type AccountIndex = u32; - -/// Balance of an account. -pub type Balance = u128; - -/// Index of a transaction in the chain. -pub type Index = u32; - -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - -/// Digest item type. -pub type DigestItem = generic::DigestItem; - -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; - -pub trait RuntimeInstance { - type CrossAccountId: pallet_evm::account::CrossAccountId - + Send - + Sync - + 'static; - - type TransactionConverter: fp_rpc::ConvertTransaction - + Send - + Sync - + 'static; - - fn get_transaction_converter() -> Self::TransactionConverter; -} --- a/runtime/common/src/weights.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. -// This file is part of Unique Network. - -// Unique Network is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Unique Network is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Unique Network. If not, see . - -use core::marker::PhantomData; -use frame_support::{weights::Weight}; -use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight, RefungibleExtensionsWeightInfo}; - -use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights}; -use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights}; -use pallet_refungible::{ - Config as RefungibleConfig, weights::WeightInfo, common::CommonWeights as RefungibleWeights, -}; -use up_data_structs::{CreateItemExData, CreateItemData}; - -macro_rules! max_weight_of { - ($method:ident ( $($args:tt)* )) => { - >::$method($($args)*) - .max(>::$method($($args)*)) - .max(>::$method($($args)*)) - }; -} - -pub struct CommonWeights(PhantomData) -where - T: FungibleConfig + NonfungibleConfig + RefungibleConfig; -impl CommonWeightInfo for CommonWeights -where - T: FungibleConfig + NonfungibleConfig + RefungibleConfig, -{ - fn create_item() -> Weight { - dispatch_weight::() + max_weight_of!(create_item()) - } - - fn create_multiple_items(data: &[CreateItemData]) -> Weight { - dispatch_weight::() + max_weight_of!(create_multiple_items(data)) - } - - fn create_multiple_items_ex(data: &CreateItemExData) -> Weight { - dispatch_weight::() + max_weight_of!(create_multiple_items_ex(data)) - } - - fn burn_item() -> Weight { - dispatch_weight::() + max_weight_of!(burn_item()) - } - - fn set_collection_properties(amount: u32) -> Weight { - dispatch_weight::() + max_weight_of!(set_collection_properties(amount)) - } - - fn delete_collection_properties(amount: u32) -> Weight { - dispatch_weight::() + max_weight_of!(delete_collection_properties(amount)) - } - - fn set_token_properties(amount: u32) -> Weight { - dispatch_weight::() + max_weight_of!(set_token_properties(amount)) - } - - fn delete_token_properties(amount: u32) -> Weight { - dispatch_weight::() + max_weight_of!(delete_token_properties(amount)) - } - - fn set_token_property_permissions(amount: u32) -> Weight { - dispatch_weight::() + max_weight_of!(set_token_property_permissions(amount)) - } - - fn transfer() -> Weight { - dispatch_weight::() + max_weight_of!(transfer()) - } - - fn approve() -> Weight { - dispatch_weight::() + max_weight_of!(approve()) - } - - fn transfer_from() -> Weight { - dispatch_weight::() + max_weight_of!(transfer_from()) - } - - fn burn_from() -> Weight { - dispatch_weight::() + max_weight_of!(burn_from()) - } - - fn burn_recursively_self_raw() -> Weight { - max_weight_of!(burn_recursively_self_raw()) - } - - fn burn_recursively_breadth_raw(amount: u32) -> Weight { - max_weight_of!(burn_recursively_breadth_raw(amount)) - } -} - -impl RefungibleExtensionsWeightInfo for CommonWeights -where - T: FungibleConfig + NonfungibleConfig + RefungibleConfig, -{ - fn repartition() -> Weight { - dispatch_weight::() + <::WeightInfo>::repartition_item() - } -} --- /dev/null +++ b/runtime/common/weights.rs @@ -0,0 +1,143 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +use core::marker::PhantomData; +use frame_support::{weights::Weight}; +use pallet_common::{CommonWeightInfo, dispatch::dispatch_weight, RefungibleExtensionsWeightInfo}; + +use pallet_fungible::{Config as FungibleConfig, common::CommonWeights as FungibleWeights}; +use pallet_nonfungible::{Config as NonfungibleConfig, common::CommonWeights as NonfungibleWeights}; + +#[cfg(feature = "refungible")] +use pallet_refungible::{ + Config as RefungibleConfig, weights::WeightInfo, common::CommonWeights as RefungibleWeights, +}; +use up_data_structs::{CreateItemExData, CreateItemData}; + +macro_rules! max_weight_of { + ($method:ident ( $($args:tt)* )) => {{ + let max_weight = >::$method($($args)*) + .max(>::$method($($args)*)); + + #[cfg(feature = "refungible")] + let max_weight = max_weight.max(>::$method($($args)*)); + + max_weight + }}; +} + +#[cfg(not(feature = "refungible"))] +pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig {} + +#[cfg(not(feature = "refungible"))] +impl CommonWeightConfigs for T {} + +#[cfg(feature = "refungible")] +pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig + RefungibleConfig {} + +#[cfg(feature = "refungible")] +impl CommonWeightConfigs for T {} + +pub struct CommonWeights(PhantomData); + +impl CommonWeightInfo for CommonWeights +where + T: CommonWeightConfigs, +{ + fn create_item() -> Weight { + dispatch_weight::() + max_weight_of!(create_item()) + } + + fn create_multiple_items(data: &[CreateItemData]) -> Weight { + dispatch_weight::() + max_weight_of!(create_multiple_items(data)) + } + + fn create_multiple_items_ex(data: &CreateItemExData) -> Weight { + dispatch_weight::() + max_weight_of!(create_multiple_items_ex(data)) + } + + fn burn_item() -> Weight { + dispatch_weight::() + max_weight_of!(burn_item()) + } + + fn set_collection_properties(amount: u32) -> Weight { + dispatch_weight::() + max_weight_of!(set_collection_properties(amount)) + } + + fn delete_collection_properties(amount: u32) -> Weight { + dispatch_weight::() + max_weight_of!(delete_collection_properties(amount)) + } + + fn set_token_properties(amount: u32) -> Weight { + dispatch_weight::() + max_weight_of!(set_token_properties(amount)) + } + + fn delete_token_properties(amount: u32) -> Weight { + dispatch_weight::() + max_weight_of!(delete_token_properties(amount)) + } + + fn set_token_property_permissions(amount: u32) -> Weight { + dispatch_weight::() + max_weight_of!(set_token_property_permissions(amount)) + } + + fn transfer() -> Weight { + dispatch_weight::() + max_weight_of!(transfer()) + } + + fn approve() -> Weight { + dispatch_weight::() + max_weight_of!(approve()) + } + + fn transfer_from() -> Weight { + dispatch_weight::() + max_weight_of!(transfer_from()) + } + + fn burn_from() -> Weight { + dispatch_weight::() + max_weight_of!(burn_from()) + } + + fn burn_recursively_self_raw() -> Weight { + max_weight_of!(burn_recursively_self_raw()) + } + + fn burn_recursively_breadth_raw(amount: u32) -> Weight { + max_weight_of!(burn_recursively_breadth_raw(amount)) + } + + fn token_owner() -> Weight { + max_weight_of!(token_owner()) + } +} + +#[cfg(feature = "refungible")] +impl RefungibleExtensionsWeightInfo for CommonWeights +where + T: FungibleConfig + NonfungibleConfig + RefungibleConfig, +{ + fn repartition() -> Weight { + dispatch_weight::() + <::WeightInfo>::repartition_item() + } +} + +#[cfg(not(feature = "refungible"))] +impl RefungibleExtensionsWeightInfo for CommonWeights +where + T: FungibleConfig + NonfungibleConfig, +{ + fn repartition() -> Weight { + dispatch_weight::() + } +} --- a/runtime/opal/Cargo.toml +++ b/runtime/opal/Cargo.toml @@ -16,7 +16,7 @@ targets = ['x86_64-unknown-linux-gnu'] [features] -default = ['std'] +default = ['std', 'opal-runtime'] runtime-benchmarks = [ 'hex-literal', 'frame-benchmarking', @@ -113,13 +113,20 @@ 'xcm/std', 'xcm-builder/std', 'xcm-executor/std', - 'unique-runtime-common/std', + 'up-common/std', 'rmrk-rpc/std', + 'evm-coder/std', + 'up-sponsorship/std', "orml-vesting/std", ] limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing'] +opal-runtime = ['refungible', 'scheduler', 'rmrk'] +refungible = [] +scheduler = [] +rmrk = [] + ################################################################################ # Substrate Dependencies @@ -396,7 +403,7 @@ [dependencies] log = { version = "0.4.16", default-features = false } -unique-runtime-common = { path = "../common", default-features = false } +up-common = { path = "../../primitives/common", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } @@ -426,6 +433,8 @@ pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } +evm-coder = { default-features = false, path = '../../crates/evm-coder' } +up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' } ################################################################################ # Build Dependencies --- a/runtime/opal/src/lib.rs +++ b/runtime/opal/src/lib.rs @@ -25,168 +25,21 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160}; -use sp_runtime::DispatchError; -use fp_self_contained::*; -// #[cfg(any(feature = "std", test))] -// pub use sp_runtime::BuildStorage; - -use sp_runtime::{ - Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, RuntimeAppPublic, -}; +use frame_support::parameter_types; -use sp_std::prelude::*; - -#[cfg(feature = "std")] -use sp_version::NativeVersion; use sp_version::RuntimeVersion; -pub use pallet_transaction_payment::{ - Multiplier, TargetedFeeAdjustment, FeeDetails, RuntimeDispatchInfo, -}; -// A few exports that help ease life for downstream crates. -pub use pallet_balances::Call as BalancesCall; -pub use pallet_evm::{ - EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _, - OnMethodCall, Account as EVMAccount, FeeCalculator, GasWeightMapping, -}; -pub use frame_support::{ - construct_runtime, match_types, - dispatch::DispatchResult, - PalletId, parameter_types, StorageValue, ConsensusEngineId, - traits::{ - tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything, - Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier, - OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp, - }, - weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - DispatchClass, DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight, - WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier, - WeightToFee, - }, -}; -use pallet_unique_scheduler::DispatchCall; -use up_data_structs::{ - CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits, - CollectionStats, RpcCollection, - mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}, - TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo, - RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId, - RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId, -}; +use sp_runtime::create_runtime_str; -// use pallet_contracts::weights::WeightInfo; -// #[cfg(any(feature = "std", test))] -use frame_system::{ - self as frame_system, EnsureRoot, EnsureSigned, - limits::{BlockWeights, BlockLength}, -}; -use sp_arithmetic::{ - traits::{BaseArithmetic, Unsigned}, -}; -use smallvec::smallvec; -// use scale_info::TypeInfo; -use codec::{Encode, Decode}; -use fp_rpc::TransactionStatus; -use sp_runtime::{ - traits::{ - Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf, - Saturating, CheckedConversion, - }, - generic::Era, - transaction_validity::TransactionValidityError, - DispatchErrorWithPostInfo, SaturatedConversion, -}; +use up_common::types::*; -// pub use pallet_timestamp::Call as TimestampCall; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +#[path = "../../common/mod.rs"] +mod runtime_common; -// Polkadot imports -use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::Sibling; -use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*}; -use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - ParentIsPreset, -}; -use xcm_executor::{Config, XcmExecutor, Assets}; -use sp_std::{cmp::Ordering, 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; +pub use runtime_common::*; -use unique_runtime_common::{ - impl_common_runtime_apis, - types::*, - constants::*, - dispatch::{CollectionDispatchT, CollectionDispatch}, - sponsoring::UniqueSponsorshipHandler, - eth_sponsoring::UniqueEthSponsorshipHandler, - weights::CommonWeights, -}; - pub const RUNTIME_NAME: &str = "opal"; pub const TOKEN_SYMBOL: &str = "OPL"; - -type CrossAccountId = pallet_evm::account::BasicCrossAccountId; -impl RuntimeInstance for Runtime { - type CrossAccountId = self::CrossAccountId; - type TransactionConverter = self::TransactionConverter; - - fn get_transaction_converter() -> TransactionConverter { - TransactionConverter - } -} - -/// The type for looking up accounts. We don't expect more than 4 billion of them, but you -/// never know... -pub type AccountIndex = u32; - -/// Balance of an account. -pub type Balance = u128; - -/// Index of a transaction in the chain. -pub type Index = u32; - -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - -/// Digest item type. -pub type DigestItem = generic::DigestItem; - -/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know -/// the specifics of the runtime. They can then be made to be agnostic over specific formats -/// of data like extrinsics, allowing for them to continue syncing the network through upgrades -/// to even the core data structures. -pub mod opaque { - use sp_std::prelude::*; - use sp_runtime::impl_opaque_keys; - use super::Aura; - - pub use unique_runtime_common::types::*; - - impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } - } -} - /// This runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!(RUNTIME_NAME), @@ -199,1206 +52,15 @@ state_version: 0, }; -#[derive(codec::Encode, codec::Decode)] -pub enum XCMPMessage { - /// Transfer tokens to the given account from the Parachain account. - TransferToken(XAccountId, XBalance), -} - -/// The version information used to identify this runtime when compiled natively. -#[cfg(feature = "std")] -pub fn native_version() -> NativeVersion { - NativeVersion { - runtime_version: VERSION, - can_author_with: Default::default(), - } -} - -type NegativeImbalance = >::NegativeImbalance; - -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(fees) = fees_then_tips.next() { - // for fees, 100% to treasury - let mut split = fees.ration(100, 0); - if let Some(tips) = fees_then_tips.next() { - // for tips, if any, 100% to treasury - tips.ration_merge_into(100, 0, &mut split); - } - Treasury::on_unbalanced(split.0); - // Author::on_unbalanced(split.1); - } - } -} - parameter_types! { - pub const BlockHashCount: BlockNumber = 2400; - pub RuntimeBlockLength: BlockLength = - BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); - pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; - pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() - .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = ExtrinsicBaseWeight::get(); - }) - .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); - }) - .for_class(DispatchClass::Operational, |weights| { - weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); - // Operational transactions have some extra reserved space, so that they - // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. - weights.reserved = Some( - MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT - ); - }) - .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) - .build_or_panic(); pub const Version: RuntimeVersion = VERSION; pub const SS58Prefix: u8 = 42; -} - -parameter_types! { pub const ChainId: u64 = 8882; -} - -pub struct FixedFee; -impl FeeCalculator for FixedFee { - fn min_gas_price() -> (U256, u64) { - (MIN_GAS_PRICE.into(), 0) - } -} - -// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case -// (contract, which only writes a lot of data), -// approximating on top of our real store write weight -parameter_types! { - pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / ::DbWeight::get().write; - pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000; - pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get(); } -/// Limiting EVM execution to 50% of block for substrate users and management tasks -/// EVM transaction consumes more weight than substrate's, so we can't rely on them being -/// scheduled fairly -const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50); -parameter_types! { - pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get()); -} - -pub enum FixedGasWeightMapping {} -impl GasWeightMapping for FixedGasWeightMapping { - fn gas_to_weight(gas: u64) -> Weight { - gas.saturating_mul(WeightPerGas::get()) - } - fn weight_to_gas(weight: Weight) -> u64 { - weight / WeightPerGas::get() - } -} - -impl pallet_evm::account::Config for Runtime { - type CrossAccountId = pallet_evm::account::BasicCrossAccountId; - type EvmAddressMapping = pallet_evm::HashedAddressMapping; - type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated; -} - -impl pallet_evm::Config for Runtime { - type BlockGasLimit = BlockGasLimit; - type FeeCalculator = FixedFee; - type GasWeightMapping = FixedGasWeightMapping; - type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; - type CallOrigin = EnsureAddressTruncated; - type WithdrawOrigin = EnsureAddressTruncated; - type AddressMapping = HashedAddressMapping; - type PrecompilesType = (); - type PrecompilesValue = (); - type Currency = Balances; - type Event = Event; - type OnMethodCall = ( - pallet_evm_migration::OnMethodCall, - pallet_evm_contract_helpers::HelpersOnMethodCall, - CollectionDispatchT, - pallet_unique::eth::CollectionHelpersOnMethodCall, - ); - type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; - type ChainId = ChainId; - type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter; - type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack; - type FindAuthor = EthereumFindAuthor; -} - -impl pallet_evm_migration::Config for Runtime { - type WeightInfo = pallet_evm_migration::weights::SubstrateWeight; -} - -pub struct EthereumFindAuthor(core::marker::PhantomData); -impl> FindAuthor for EthereumFindAuthor { - fn find_author<'a, I>(digests: I) -> Option - where - I: 'a + IntoIterator, - { - if let Some(author_index) = F::find_author(digests) { - let authority_id = Aura::authorities()[author_index as usize].clone(); - return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24])); - } - None - } -} - -impl pallet_ethereum::Config for Runtime { - type Event = Event; - type StateRoot = pallet_ethereum::IntermediateStateRoot; -} - -impl pallet_randomness_collective_flip::Config for Runtime {} - -impl frame_system::Config for Runtime { - /// The data to be stored in an account. - type AccountData = pallet_balances::AccountData; - /// The identifier used to distinguish between accounts. - type AccountId = AccountId; - /// The basic call filter to use in dispatchable. - type BaseCallFilter = Everything; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). - type BlockHashCount = BlockHashCount; - /// The maximum length of a block (in bytes). - type BlockLength = RuntimeBlockLength; - /// The index type for blocks. - type BlockNumber = BlockNumber; - /// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block. - type BlockWeights = RuntimeBlockWeights; - /// The aggregated dispatch type that is available for extrinsics. - type Call = Call; - /// The weight of database operations that the runtime can invoke. - type DbWeight = RocksDbWeight; - /// The ubiquitous event type. - type Event = Event; - /// The type for hashing blocks and tries. - type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; - /// The header type. - type Header = generic::Header; - /// The index type for storing how many extrinsics an account has signed. - type Index = Index; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; - /// What to do if an account is fully reaped from the system. - type OnKilledAccount = (); - /// What to do if a new account is created. - type OnNewAccount = (); - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - /// The ubiquitous origin type. - type Origin = Origin; - /// This type is being generated by `construct_runtime!`. - type PalletInfo = PalletInfo; - /// This is used as an identifier of the chain. 42 is the generic substrate prefix. - type SS58Prefix = SS58Prefix; - /// Weight information for the extrinsics of this pallet. - type SystemWeightInfo = frame_system::weights::SubstrateWeight; - /// Version of the runtime. - type Version = Version; - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; -} - -impl pallet_timestamp::Config for Runtime { - /// A timestamp: milliseconds since the unix epoch. - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_types! { - // pub const ExistentialDeposit: u128 = 500; - pub const ExistentialDeposit: u128 = 0; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; -} - -impl pallet_balances::Config for Runtime { - type MaxLocks = MaxLocks; - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 16]; - /// The type for recording an account's balance. - type Balance = Balance; - /// The ubiquitous event type. - type Event = Event; - type DustRemoval = Treasury; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = pallet_balances::weights::SubstrateWeight; -} - -pub const fn deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 15 * CENTIUNIQUE + (bytes as Balance) * 6 * CENTIUNIQUE -} - -/* -parameter_types! { - pub TombstoneDeposit: Balance = deposit( - 1, - sp_std::mem::size_of::> as u32, - ); - pub DepositPerContract: Balance = TombstoneDeposit::get(); - pub const DepositPerStorageByte: Balance = deposit(0, 1); - pub const DepositPerStorageItem: Balance = deposit(1, 0); - pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * DAYS); - pub const SurchargeReward: Balance = 150 * MILLIUNIQUE; - pub const SignedClaimHandicap: u32 = 2; - pub const MaxDepth: u32 = 32; - pub const MaxValueSize: u32 = 16 * 1024; - pub const MaxCodeSize: u32 = 1024 * 1024 * 25; // 25 Mb - // The lazy deletion runs inside on_initialize. - pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * - RuntimeBlockWeights::get().max_block; - // The weight needed for decoding the queue should be less or equal than a fifth - // of the overall weight dedicated to the lazy deletion. - pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / ( - ::WeightInfo::on_initialize_per_queue_item(1) - - ::WeightInfo::on_initialize_per_queue_item(0) - )) / 5) as u32; - pub Schedule: pallet_contracts::Schedule = Default::default(); -} - -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = RandomnessCollectiveFlip; - type Currency = Balances; - type Event = Event; - type RentPayment = (); - type SignedClaimHandicap = SignedClaimHandicap; - type TombstoneDeposit = TombstoneDeposit; - type DepositPerContract = DepositPerContract; - type DepositPerStorageByte = DepositPerStorageByte; - type DepositPerStorageItem = DepositPerStorageItem; - type RentFraction = RentFraction; - type SurchargeReward = SurchargeReward; - type WeightPrice = pallet_transaction_payment::Pallet; - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = NFTExtension; - type DeletionQueueDepth = DeletionQueueDepth; - type DeletionWeightLimit = DeletionWeightLimit; - type Schedule = Schedule; - type CallStack = [pallet_contracts::Frame; 31]; -} -*/ +construct_runtime!(opal); -parameter_types! { - /// This value increases the priority of `Operational` transactions by adding - /// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`. - pub const OperationalFeeMultiplier: u8 = 5; -} - -/// Linear implementor of `WeightToFeePolynomial` -pub struct LinearFee(sp_std::marker::PhantomData); - -impl WeightToFeePolynomial for LinearFee -where - T: BaseArithmetic + From + Copy + Unsigned, -{ - type Balance = T; - - fn polynomial() -> WeightToFeeCoefficients { - smallvec!(WeightToFeeCoefficient { - coeff_integer: WEIGHT_TO_FEE_COEFF.into(), - coeff_frac: Perbill::zero(), - negative: false, - degree: 1, - }) - } -} - -impl pallet_transaction_payment::Config for Runtime { - type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; - type LengthToFee = ConstantMultiplier; - type OperationalFeeMultiplier = OperationalFeeMultiplier; - type WeightToFee = LinearFee; - type FeeMultiplierUpdate = (); -} - -parameter_types! { - pub const ProposalBond: Permill = Permill::from_percent(5); - pub const ProposalBondMinimum: Balance = 1 * UNIQUE; - pub const ProposalBondMaximum: Balance = 1000 * UNIQUE; - pub const SpendPeriod: BlockNumber = 5 * MINUTES; - pub const Burn: Permill = Permill::from_percent(0); - pub const TipCountdown: BlockNumber = 1 * DAYS; - pub const TipFindersFee: Percent = Percent::from_percent(20); - pub const TipReportDepositBase: Balance = 1 * UNIQUE; - pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE; - pub const BountyDepositBase: Balance = 1 * UNIQUE; - pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; - pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; - pub const MaximumReasonLength: u32 = 16384; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: Balance = 5 * UNIQUE; - pub const MaxApprovals: u32 = 100; -} - -impl pallet_treasury::Config for Runtime { - type PalletId = TreasuryModuleId; - type Currency = Balances; - type ApproveOrigin = EnsureRoot; - type RejectOrigin = EnsureRoot; - type Event = Event; - type OnSlash = (); - type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type ProposalBondMaximum = ProposalBondMaximum; - type SpendPeriod = SpendPeriod; - type Burn = Burn; - type BurnDestination = (); - type SpendFunds = (); - type WeightInfo = pallet_treasury::weights::SubstrateWeight; - type MaxApprovals = MaxApprovals; -} - -impl pallet_sudo::Config for Runtime { - type Event = Event; - type Call = Call; -} - -pub struct RelayChainBlockNumberProvider(sp_std::marker::PhantomData); - -impl BlockNumberProvider - for RelayChainBlockNumberProvider -{ - type BlockNumber = BlockNumber; - - fn current_block_number() -> Self::BlockNumber { - cumulus_pallet_parachain_system::Pallet::::validation_data() - .map(|d| d.relay_parent_number) - .unwrap_or_default() - } -} - -parameter_types! { - pub const MinVestedTransfer: Balance = 10 * UNIQUE; - pub const MaxVestingSchedules: u32 = 28; -} - -impl orml_vesting::Config for Runtime { - type Event = Event; - type Currency = pallet_balances::Pallet; - type MinVestedTransfer = MinVestedTransfer; - type VestedTransferOrigin = EnsureSigned; - type WeightInfo = (); - type MaxVestingSchedules = MaxVestingSchedules; - type BlockNumberProvider = RelayChainBlockNumberProvider; -} - -parameter_types! { - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; - pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type Event = Event; - type SelfParaId = parachain_info::Pallet; - type OnSystemEvent = (); - // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent< - // MaxDownwardMessageWeight, - // XcmExecutor, - // Call, - // >; - type OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = DmpQueue; - type ReservedDmpWeight = ReservedDmpWeight; - type ReservedXcmpWeight = ReservedXcmpWeight; - type XcmpMessageHandler = XcmpQueue; -} - -impl parachain_info::Config for Runtime {} - -impl cumulus_pallet_aura_ext::Config for Runtime {} - -parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Polkadot; - pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); -} - -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used -/// when determining ownership of accounts for asset transacting and when attempting to use XCM -/// `Transact` in order to determine the dispatch Origin. -pub type LocationToAccountId = ( - // The parent (Relay-chain) origin converts to the default `AccountId`. - ParentIsPreset, - // Sibling parachain origins convert to AccountId via the `ParaId::into`. - SiblingParachainConvertsVia, - // Straight up local `AccountId32` origins just alias directly to `AccountId`. - AccountId32Aliases, -); - -pub struct OnlySelfCurrency; -impl> MatchesFungible for OnlySelfCurrency { - fn matches_fungible(a: &MultiAsset) -> Option { - 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: - 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): - AccountId, - // We don't track any teleports. - (), ->; - -/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// biases the kind of local `Origin` it will become. -pub type XcmOriginToTransactDispatchOrigin = ( - // Sovereign account converter; this attempts to derive an `AccountId` from the origin location - // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for - // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when - // recognised. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognised. - SiblingParachainAsNative, - // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a - // transaction from the Root origin. - ParentAsSuperuser, - // Native signed account converter; this just converts an `AccountId32` origin into a normal - // `Origin::Signed` origin of the same 32-byte value. - SignedAccountId32AsNative, - // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. - XcmPassthrough, -); - -parameter_types! { - // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. - pub UnitWeightCost: Weight = 1_000_000; - // 1200 UNIQUEs buy 1 second of weight. - pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE); - pub const MaxInstructions: u32 = 100; - pub const MaxAuthorities: u32 = 100_000; -} - -match_types! { - pub type ParentOrParentsUnitPlurality: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) } - }; -} - -pub type Barrier = ( - TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, - // ^^^ Parent & its unit plurality gets free execution -); - -pub struct UsingOnlySelfCurrencyComponents< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, ->( - Weight, - Currency::Balance, - PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, -); -impl< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > WeightTrader - for UsingOnlySelfCurrencyComponents -{ - fn new() -> Self { - Self(0, Zero::zero(), PhantomData) - } - - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { - let amount = WeightToFee::weight_to_fee(&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 { - let weight = weight.min(self.0); - let amount = WeightToFee::weight_to_fee(&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, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > Drop - for UsingOnlySelfCurrencyComponents -{ - fn drop(&mut self) { - OnUnbalanced::on_unbalanced(Currency::issue(self.1)); - } -} - -pub struct XcmConfig; -impl Config for XcmConfig { - type Call = Call; - type XcmSender = XcmRouter; - // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = NativeAsset; - type IsTeleporter = (); // Teleportation is disabled - type LocationInverter = LocationInverter; - type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = - UsingOnlySelfCurrencyComponents, RelayLocation, AccountId, Balances, ()>; - type ResponseHandler = (); // Don't handle responses for now. - type SubscriptionService = PolkadotXcm; - - type AssetTrap = PolkadotXcm; - type AssetClaims = PolkadotXcm; -} - -// parameter_types! { -// pub const MaxDownwardMessageWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10; -// } - -/// No local origins on this chain are allowed to dispatch XCM sends/executions. -pub type LocalOriginToLocation = (SignedToAccountId32,); - -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = ( - // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, -); - -impl pallet_evm_coder_substrate::Config for Runtime {} - -impl pallet_xcm::Config for Runtime { - type Event = Event; - type SendXcmOrigin = EnsureXcmOrigin; - type XcmRouter = XcmRouter; - type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Everything; - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; - type Origin = Origin; - type Call = Call; - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; -} - -impl cumulus_pallet_xcm::Config for Runtime { - type Event = Event; - type XcmExecutor = XcmExecutor; -} - -impl cumulus_pallet_xcmp_queue::Config for Runtime { - type WeightInfo = (); - type Event = Event; - type XcmExecutor = XcmExecutor; - type ChannelInfo = ParachainSystem; - type VersionWrapper = (); - type ExecuteOverweightOrigin = frame_system::EnsureRoot; - type ControllerOrigin = EnsureRoot; - type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type Event = Event; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = frame_system::EnsureRoot; -} - -impl pallet_aura::Config for Runtime { - type AuthorityId = AuraId; - type DisabledValidators = (); - type MaxAuthorities = MaxAuthorities; -} - -parameter_types! { - pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating(); - pub const CollectionCreationPrice: Balance = 2 * UNIQUE; -} - -impl pallet_common::Config for Runtime { - type WeightInfo = pallet_common::weights::SubstrateWeight; - type Event = Event; - type Currency = Balances; - type CollectionCreationPrice = CollectionCreationPrice; - type TreasuryAccountId = TreasuryAccountId; - type CollectionDispatch = CollectionDispatchT; - - type EvmTokenAddressMapping = EvmTokenAddressMapping; - type CrossTokenAddressMapping = CrossTokenAddressMapping; - type ContractAddress = EvmCollectionHelpersAddress; -} - -impl pallet_structure::Config for Runtime { - type Event = Event; - type Call = Call; - type WeightInfo = pallet_structure::weights::SubstrateWeight; -} - -impl pallet_fungible::Config for Runtime { - type WeightInfo = pallet_fungible::weights::SubstrateWeight; -} -impl pallet_refungible::Config for Runtime { - type WeightInfo = pallet_refungible::weights::SubstrateWeight; -} -impl pallet_nonfungible::Config for Runtime { - type WeightInfo = pallet_nonfungible::weights::SubstrateWeight; -} - -impl pallet_proxy_rmrk_core::Config for Runtime { - type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight; - type Event = Event; -} - -impl pallet_proxy_rmrk_equip::Config for Runtime { - type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight; - type Event = Event; -} - -impl pallet_unique::Config for Runtime { - type Event = Event; - type WeightInfo = pallet_unique::weights::SubstrateWeight; - type CommonWeightInfo = CommonWeights; - type RefungibleExtensionsWeightInfo = CommonWeights; -} - -parameter_types! { - pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied -} - -/// Used for the pallet inflation -impl pallet_inflation::Config for Runtime { - type Currency = Balances; - type TreasuryAccountId = TreasuryAccountId; - type InflationBlockInterval = InflationBlockInterval; - type BlockNumberProvider = RelayChainBlockNumberProvider; -} - -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) * - RuntimeBlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 50; -} - -type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment; -use frame_support::traits::NamedReservableCurrency; - -fn get_signed_extras(from: ::AccountId) -> SignedExtraScheduler { - ( - frame_system::CheckSpecVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(Era::Immortal), - frame_system::CheckNonce::::from(frame_system::Pallet::::account_nonce( - from, - )), - frame_system::CheckWeight::::new(), - // sponsoring transaction logic - // pallet_charge_transaction::ChargeTransactionPayment::::new(0), - ) -} - -pub struct SchedulerPaymentExecutor; -impl - DispatchCall for SchedulerPaymentExecutor -where - ::Call: Member - + Dispatchable - + SelfContainedCall - + GetDispatchInfo - + From>, - SelfContainedSignedInfo: Send + Sync + 'static, - Call: From<::Call> - + From<::Call> - + SelfContainedCall, - sp_runtime::AccountId32: From<::AccountId>, -{ - fn dispatch_call( - signer: ::AccountId, - call: ::Call, - ) -> Result< - Result>, - TransactionValidityError, - > { - let dispatch_info = call.get_dispatch_info(); - let extrinsic = fp_self_contained::CheckedExtrinsic::< - AccountId, - Call, - SignedExtraScheduler, - SelfContainedSignedInfo, - > { - signed: - CheckedSignature::::Signed( - signer.clone().into(), - get_signed_extras(signer.into()), - ), - function: call.into(), - }; - - extrinsic.apply::(&dispatch_info, 0) - } - - fn reserve_balance( - id: [u8; 16], - sponsor: ::AccountId, - call: ::Call, - count: u32, - ) -> Result<(), DispatchError> { - let dispatch_info = call.get_dispatch_info(); - let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0) - .saturating_mul(count.into()); - - >::reserve_named( - &id, - &(sponsor.into()), - weight, - ) - } - - fn pay_for_call( - id: [u8; 16], - sponsor: ::AccountId, - call: ::Call, - ) -> Result { - let dispatch_info = call.get_dispatch_info(); - let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0); - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - weight, - ), - ) - } - - fn cancel_reserve( - id: [u8; 16], - sponsor: ::AccountId, - ) -> Result { - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - u128::MAX, - ), - ) - } -} - -parameter_types! { - pub const NoPreimagePostponement: Option = Some(10); - pub const Preimage: Option = Some(10); -} - -/// Used the compare the privilege of an origin inside the scheduler. -pub struct OriginPrivilegeCmp; - -impl PrivilegeCmp for OriginPrivilegeCmp { - fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { - Some(Ordering::Equal) - } -} - -impl pallet_unique_scheduler::Config for Runtime { - type Event = Event; - type Origin = Origin; - type Currency = Balances; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureSigned; - type MaxScheduledPerBlock = MaxScheduledPerBlock; - type WeightInfo = (); - type CallExecutor = SchedulerPaymentExecutor; - type OriginPrivilegeCmp = OriginPrivilegeCmp; - type PreimageProvider = (); - type NoPreimagePostponement = NoPreimagePostponement; -} - -type EvmSponsorshipHandler = ( - UniqueEthSponsorshipHandler, - pallet_evm_contract_helpers::HelpersContractSponsoring, -); - -type SponsorshipHandler = ( - UniqueSponsorshipHandler, - //pallet_contract_helpers::ContractSponsorshipHandler, - pallet_evm_transaction_payment::BridgeSponsorshipHandler, -); - -impl pallet_evm_transaction_payment::Config for Runtime { - type EvmSponsorshipHandler = EvmSponsorshipHandler; - type Currency = Balances; -} - -impl pallet_charge_transaction::Config for Runtime { - type SponsorshipHandler = SponsorshipHandler; -} - -// impl pallet_contract_helpers::Config for Runtime { -// type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; -// } - -parameter_types! { - // 0x842899ECF380553E8a4de75bF534cdf6fBF64049 - pub const HelpersContractAddress: H160 = H160([ - 0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49, - ]); - - // 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f - pub const EvmCollectionHelpersAddress: H160 = H160([ - 0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f, - ]); -} - -impl pallet_evm_contract_helpers::Config for Runtime { - type ContractAddress = HelpersContractAddress; - type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; -} - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = opaque::Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned} = 20, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21, - - Aura: pallet_aura::{Pallet, Config} = 22, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23, - - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 30, - RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 34, - Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event} = 35, - System: frame_system::{Pallet, Call, Storage, Config, Event} = 36, - Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 37, - // Vesting: pallet_vesting::{Pallet, Call, Config, Storage, Event} = 37, - // Contracts: pallet_contracts::{Pallet, Call, Storage, Event} = 38, - - // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50, - PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin} = 51, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event, Origin} = 52, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 53, - - // Unique Pallets - Inflation: pallet_inflation::{Pallet, Call, Storage} = 60, - Unique: pallet_unique::{Pallet, Call, Storage, Event} = 61, - Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event} = 62, - // free = 63 - Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64, - // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65, - Common: pallet_common::{Pallet, Storage, Event} = 66, - Fungible: pallet_fungible::{Pallet, Storage} = 67, - Refungible: pallet_refungible::{Pallet, Storage} = 68, - Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69, - Structure: pallet_structure::{Pallet, Call, Storage, Event} = 70, - RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event} = 71, - RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event} = 72, - - // Frontier - EVM: pallet_evm::{Pallet, Config, Call, Storage, Event} = 100, - Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101, - - EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150, - EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151, - EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152, - EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153, - } -); - -pub struct TransactionConverter; - -impl fp_rpc::ConvertTransaction for TransactionConverter { - fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic { - UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), - ) - } -} - -impl fp_rpc::ConvertTransaction for TransactionConverter { - fn convert_transaction( - &self, - transaction: pallet_ethereum::Transaction, - ) -> opaque::UncheckedExtrinsic { - let extrinsic = UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), - ); - let encoded = extrinsic.encode(); - opaque::UncheckedExtrinsic::decode(&mut &encoded[..]) - .expect("Encoded extrinsic is always valid") - } -} - -/// The address format for describing accounts. -pub type Address = sp_runtime::MultiAddress; -/// Block header type as expected by this runtime. -pub type Header = generic::Header; -/// Block type as expected by this runtime. -pub type Block = generic::Block; -/// A Block signed with a Justification -pub type SignedBlock = generic::SignedBlock; -/// BlockId type as expected by this runtime. -pub type BlockId = generic::BlockId; -/// The SignedExtension to the basic transaction logic. -pub type SignedExtra = ( - frame_system::CheckSpecVersion, - // system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - ChargeTransactionPayment, - //pallet_contract_helpers::ContractHelpersExtension, - pallet_ethereum::FakeTransactionFinalizer, -); -pub type SignedExtraScheduler = ( - frame_system::CheckSpecVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, -); -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - fp_self_contained::UncheckedExtrinsic; -/// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic; -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsReversedWithSystemFirst, ->; - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -impl fp_self_contained::SelfContainedCall for Call { - type SignedInfo = H160; - - fn is_self_contained(&self) -> bool { - match self { - Call::Ethereum(call) => call.is_self_contained(), - _ => false, - } - } - - fn check_self_contained(&self) -> Option> { - match self { - Call::Ethereum(call) => call.check_self_contained(), - _ => None, - } - } - - fn validate_self_contained( - &self, - info: &Self::SignedInfo, - dispatch_info: &DispatchInfoOf, - len: usize, - ) -> Option { - match self { - Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len), - _ => None, - } - } - - fn pre_dispatch_self_contained( - &self, - info: &Self::SignedInfo, - ) -> Option> { - match self { - Call::Ethereum(call) => call.pre_dispatch_self_contained(info), - _ => None, - } - } - - fn apply_self_contained( - self, - info: Self::SignedInfo, - ) -> Option>> { - match self { - call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch( - Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)), - )), - _ => None, - } - } -} - -macro_rules! dispatch_unique_runtime { - ($collection:ident.$method:ident($($name:ident),*)) => {{ - let collection = ::CollectionDispatch::dispatch(>::try_get($collection)?); - let dispatch = collection.as_dyn(); - - Ok::<_, DispatchError>(dispatch.$method($($name),*)) - }}; -} - -impl_common_runtime_apis! { - #![custom_apis] - - impl rmrk_rpc::RmrkApi< - Block, - AccountId, - RmrkCollectionInfo, - RmrkInstanceInfo, - RmrkResourceInfo, - RmrkPropertyInfo, - RmrkBaseInfo, - RmrkPartType, - RmrkTheme - > for Runtime { - fn last_collection_idx() -> Result { - pallet_proxy_rmrk_core::rpc::last_collection_idx::() - } - - fn collection_by_id(collection_id: RmrkCollectionId) -> Result>, DispatchError> { - pallet_proxy_rmrk_core::rpc::collection_by_id::(collection_id) - } - - fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result>, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_by_id::(collection_id, nft_by_id) - } - - fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::account_tokens::(account_id, collection_id) - } - - fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_children::(collection_id, nft_id) - } - - fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option>) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::collection_properties::(collection_id, filter_keys) - } - - fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option>) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_properties::(collection_id, nft_id, filter_keys) - } - - fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_resources::(collection_id, nft_id) - } - - fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_resource_priority::(collection_id, nft_id, resource_id) - } - - fn base(base_id: RmrkBaseId) -> Result>, DispatchError> { - pallet_proxy_rmrk_equip::rpc::base::(base_id) - } - - fn base_parts(base_id: RmrkBaseId) -> Result, DispatchError> { - pallet_proxy_rmrk_equip::rpc::base_parts::(base_id) - } - - fn theme_names(base_id: RmrkBaseId) -> Result, DispatchError> { - pallet_proxy_rmrk_equip::rpc::theme_names::(base_id) - } - - fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option>) -> Result, DispatchError> { - pallet_proxy_rmrk_equip::rpc::theme::(base_id, theme_name, filter_keys) - } - } -} - -struct CheckInherents; - -impl cumulus_pallet_parachain_system::CheckInherents for CheckInherents { - fn check_inherents( - block: &Block, - relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof, - ) -> sp_inherents::CheckInherentsResult { - let relay_chain_slot = relay_state_proof - .read_slot() - .expect("Could not read the relay chain slot from the proof"); - - let inherent_data = - cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration( - relay_chain_slot, - sp_std::time::Duration::from_secs(6), - ) - .create_inherent_data() - .expect("Could not create the timestamp inherent data"); - - inherent_data.check_extrinsics(block) - } -} +impl_common_runtime_apis!(); cumulus_pallet_parachain_system::register_validate_block!( Runtime = Runtime, --- a/runtime/quartz/Cargo.toml +++ b/runtime/quartz/Cargo.toml @@ -16,7 +16,7 @@ targets = ['x86_64-unknown-linux-gnu'] [features] -default = ['std'] +default = ['std', 'quartz-runtime'] runtime-benchmarks = [ 'hex-literal', 'frame-benchmarking', @@ -113,12 +113,17 @@ 'xcm/std', 'xcm-builder/std', 'xcm-executor/std', - 'unique-runtime-common/std', + 'up-common/std', "orml-vesting/std", ] limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing'] +quartz-runtime = [] +refungible = [] +scheduler = [] +rmrk = [] + ################################################################################ # Substrate Dependencies @@ -403,7 +408,7 @@ [dependencies] log = { version = "0.4.16", default-features = false } -unique-runtime-common = { path = "../common", default-features = false } +up-common = { path = "../../primitives/common", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } @@ -432,6 +437,8 @@ pallet-base-fee = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } +evm-coder = { default-features = false, path = '../../crates/evm-coder' } +up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' } ################################################################################ # Build Dependencies --- a/runtime/quartz/src/lib.rs +++ b/runtime/quartz/src/lib.rs @@ -25,167 +25,21 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160}; -use sp_runtime::DispatchError; -use fp_self_contained::*; -// #[cfg(any(feature = "std", test))] -// pub use sp_runtime::BuildStorage; - -use sp_runtime::{ - Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, RuntimeAppPublic, -}; +use frame_support::parameter_types; -use sp_std::prelude::*; - -#[cfg(feature = "std")] -use sp_version::NativeVersion; use sp_version::RuntimeVersion; -pub use pallet_transaction_payment::{ - Multiplier, TargetedFeeAdjustment, FeeDetails, RuntimeDispatchInfo, -}; -// A few exports that help ease life for downstream crates. -pub use pallet_balances::Call as BalancesCall; -pub use pallet_evm::{ - EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _, - OnMethodCall, Account as EVMAccount, FeeCalculator, GasWeightMapping, -}; -pub use frame_support::{ - construct_runtime, match_types, - dispatch::DispatchResult, - PalletId, parameter_types, StorageValue, ConsensusEngineId, - traits::{ - tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything, - Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier, - OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp, - }, - weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - DispatchClass, DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight, - WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier, - WeightToFee, - }, -}; -use pallet_unique_scheduler::DispatchCall; -use up_data_structs::{ - CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits, - CollectionStats, RpcCollection, - mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}, - TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo, - RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId, - RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId, -}; +use sp_runtime::create_runtime_str; -// use pallet_contracts::weights::WeightInfo; -// #[cfg(any(feature = "std", test))] -use frame_system::{ - self as frame_system, EnsureRoot, EnsureSigned, - limits::{BlockWeights, BlockLength}, -}; -use sp_arithmetic::{ - traits::{BaseArithmetic, Unsigned}, -}; -use smallvec::smallvec; -use codec::{Encode, Decode}; -use fp_rpc::TransactionStatus; -use sp_runtime::{ - traits::{ - Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf, - Saturating, CheckedConversion, - }, - generic::Era, - transaction_validity::TransactionValidityError, - DispatchErrorWithPostInfo, SaturatedConversion, -}; +use up_common::types::*; -// pub use pallet_timestamp::Call as TimestampCall; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +#[path = "../../common/mod.rs"] +mod runtime_common; -// Polkadot imports -use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::Sibling; -use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*}; -use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - ParentIsPreset, -}; -use xcm_executor::{Config, XcmExecutor, Assets}; -use sp_std::{cmp::Ordering, marker::PhantomData}; - -use xcm::latest::{ - // Xcm, - AssetId::{Concrete}, - Fungibility::Fungible as XcmFungible, - MultiAsset, - Error as XcmError, -}; -use xcm_executor::traits::{MatchesFungible, WeightTrader}; +pub use runtime_common::*; -use unique_runtime_common::{ - impl_common_runtime_apis, - types::*, - constants::*, - dispatch::{CollectionDispatchT, CollectionDispatch}, - sponsoring::UniqueSponsorshipHandler, - eth_sponsoring::UniqueEthSponsorshipHandler, - weights::CommonWeights, -}; - pub const RUNTIME_NAME: &str = "quartz"; pub const TOKEN_SYMBOL: &str = "QTZ"; - -type CrossAccountId = pallet_evm::account::BasicCrossAccountId; - -impl RuntimeInstance for Runtime { - type CrossAccountId = self::CrossAccountId; - - type TransactionConverter = self::TransactionConverter; - - fn get_transaction_converter() -> TransactionConverter { - TransactionConverter - } -} - -/// The type for looking up accounts. We don't expect more than 4 billion of them, but you -/// never know... -pub type AccountIndex = u32; -/// Balance of an account. -pub type Balance = u128; - -/// Index of a transaction in the chain. -pub type Index = u32; - -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - -/// Digest item type. -pub type DigestItem = generic::DigestItem; - -/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know -/// the specifics of the runtime. They can then be made to be agnostic over specific formats -/// of data like extrinsics, allowing for them to continue syncing the network through upgrades -/// to even the core data structures. -pub mod opaque { - use sp_std::prelude::*; - use sp_runtime::impl_opaque_keys; - use super::Aura; - - pub use unique_runtime_common::types::*; - - impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } - } -} - /// This runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!(RUNTIME_NAME), @@ -198,1207 +52,15 @@ state_version: 0, }; -#[derive(codec::Encode, codec::Decode)] -pub enum XCMPMessage { - /// Transfer tokens to the given account from the Parachain account. - TransferToken(XAccountId, XBalance), -} - -/// The version information used to identify this runtime when compiled natively. -#[cfg(feature = "std")] -pub fn native_version() -> NativeVersion { - NativeVersion { - runtime_version: VERSION, - can_author_with: Default::default(), - } -} - -type NegativeImbalance = >::NegativeImbalance; - -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(fees) = fees_then_tips.next() { - // for fees, 100% to treasury - let mut split = fees.ration(100, 0); - if let Some(tips) = fees_then_tips.next() { - // for tips, if any, 100% to treasury - tips.ration_merge_into(100, 0, &mut split); - } - Treasury::on_unbalanced(split.0); - // Author::on_unbalanced(split.1); - } - } -} - parameter_types! { - pub const BlockHashCount: BlockNumber = 2400; - pub RuntimeBlockLength: BlockLength = - BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); - pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; - pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() - .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = ExtrinsicBaseWeight::get(); - }) - .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); - }) - .for_class(DispatchClass::Operational, |weights| { - weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); - // Operational transactions have some extra reserved space, so that they - // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. - weights.reserved = Some( - MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT - ); - }) - .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) - .build_or_panic(); pub const Version: RuntimeVersion = VERSION; pub const SS58Prefix: u8 = 255; -} - -parameter_types! { pub const ChainId: u64 = 8881; -} - -pub struct FixedFee; -impl FeeCalculator for FixedFee { - fn min_gas_price() -> (U256, u64) { - (MIN_GAS_PRICE.into(), 0) - } -} - -// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case -// (contract, which only writes a lot of data), -// approximating on top of our real store write weight -parameter_types! { - pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / ::DbWeight::get().write; - pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000; - pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get(); -} - -/// Limiting EVM execution to 50% of block for substrate users and management tasks -/// EVM transaction consumes more weight than substrate's, so we can't rely on them being -/// scheduled fairly -const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50); -parameter_types! { - pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get()); } -pub enum FixedGasWeightMapping {} -impl GasWeightMapping for FixedGasWeightMapping { - fn gas_to_weight(gas: u64) -> Weight { - gas.saturating_mul(WeightPerGas::get()) - } - fn weight_to_gas(weight: Weight) -> u64 { - weight / WeightPerGas::get() - } -} - -impl pallet_evm::account::Config for Runtime { - type CrossAccountId = pallet_evm::account::BasicCrossAccountId; - type EvmAddressMapping = HashedAddressMapping; - type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated; -} - -impl pallet_evm::Config for Runtime { - type BlockGasLimit = BlockGasLimit; - type FeeCalculator = FixedFee; - type GasWeightMapping = FixedGasWeightMapping; - type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; - type CallOrigin = EnsureAddressTruncated; - type WithdrawOrigin = EnsureAddressTruncated; - type AddressMapping = HashedAddressMapping; - type PrecompilesType = (); - type PrecompilesValue = (); - type Currency = Balances; - type Event = Event; - type OnMethodCall = ( - pallet_evm_migration::OnMethodCall, - pallet_evm_contract_helpers::HelpersOnMethodCall, - CollectionDispatchT, - pallet_unique::eth::CollectionHelpersOnMethodCall, - ); - type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; - type ChainId = ChainId; - type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter; - type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack; - type FindAuthor = EthereumFindAuthor; -} - -impl pallet_evm_migration::Config for Runtime { - type WeightInfo = pallet_evm_migration::weights::SubstrateWeight; -} - -pub struct EthereumFindAuthor(core::marker::PhantomData); -impl> FindAuthor for EthereumFindAuthor { - fn find_author<'a, I>(digests: I) -> Option - where - I: 'a + IntoIterator, - { - if let Some(author_index) = F::find_author(digests) { - let authority_id = Aura::authorities()[author_index as usize].clone(); - return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24])); - } - None - } -} - -impl pallet_ethereum::Config for Runtime { - type Event = Event; - type StateRoot = pallet_ethereum::IntermediateStateRoot; -} - -impl pallet_randomness_collective_flip::Config for Runtime {} - -impl frame_system::Config for Runtime { - /// The data to be stored in an account. - type AccountData = pallet_balances::AccountData; - /// The identifier used to distinguish between accounts. - type AccountId = AccountId; - /// The basic call filter to use in dispatchable. - type BaseCallFilter = Everything; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). - type BlockHashCount = BlockHashCount; - /// The maximum length of a block (in bytes). - type BlockLength = RuntimeBlockLength; - /// The index type for blocks. - type BlockNumber = BlockNumber; - /// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block. - type BlockWeights = RuntimeBlockWeights; - /// The aggregated dispatch type that is available for extrinsics. - type Call = Call; - /// The weight of database operations that the runtime can invoke. - type DbWeight = RocksDbWeight; - /// The ubiquitous event type. - type Event = Event; - /// The type for hashing blocks and tries. - type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; - /// The header type. - type Header = generic::Header; - /// The index type for storing how many extrinsics an account has signed. - type Index = Index; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; - /// What to do if an account is fully reaped from the system. - type OnKilledAccount = (); - /// What to do if a new account is created. - type OnNewAccount = (); - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - /// The ubiquitous origin type. - type Origin = Origin; - /// This type is being generated by `construct_runtime!`. - type PalletInfo = PalletInfo; - /// This is used as an identifier of the chain. 42 is the generic substrate prefix. - type SS58Prefix = SS58Prefix; - /// Weight information for the extrinsics of this pallet. - type SystemWeightInfo = frame_system::weights::SubstrateWeight; - /// Version of the runtime. - type Version = Version; - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; -} - -impl pallet_timestamp::Config for Runtime { - /// A timestamp: milliseconds since the unix epoch. - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_types! { - // pub const ExistentialDeposit: u128 = 500; - pub const ExistentialDeposit: u128 = 0; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; -} - -impl pallet_balances::Config for Runtime { - type MaxLocks = MaxLocks; - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 16]; - /// The type for recording an account's balance. - type Balance = Balance; - /// The ubiquitous event type. - type Event = Event; - type DustRemoval = Treasury; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = pallet_balances::weights::SubstrateWeight; -} - -pub const fn deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 15 * CENTIUNIQUE + (bytes as Balance) * 6 * CENTIUNIQUE -} - -/* -parameter_types! { - pub TombstoneDeposit: Balance = deposit( - 1, - sp_std::mem::size_of::> as u32, - ); - pub DepositPerContract: Balance = TombstoneDeposit::get(); - pub const DepositPerStorageByte: Balance = deposit(0, 1); - pub const DepositPerStorageItem: Balance = deposit(1, 0); - pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * DAYS); - pub const SurchargeReward: Balance = 150 * MILLIUNIQUE; - pub const SignedClaimHandicap: u32 = 2; - pub const MaxDepth: u32 = 32; - pub const MaxValueSize: u32 = 16 * 1024; - pub const MaxCodeSize: u32 = 1024 * 1024 * 25; // 25 Mb - // The lazy deletion runs inside on_initialize. - pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * - RuntimeBlockWeights::get().max_block; - // The weight needed for decoding the queue should be less or equal than a fifth - // of the overall weight dedicated to the lazy deletion. - pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / ( - ::WeightInfo::on_initialize_per_queue_item(1) - - ::WeightInfo::on_initialize_per_queue_item(0) - )) / 5) as u32; - pub Schedule: pallet_contracts::Schedule = Default::default(); -} - -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = RandomnessCollectiveFlip; - type Currency = Balances; - type Event = Event; - type RentPayment = (); - type SignedClaimHandicap = SignedClaimHandicap; - type TombstoneDeposit = TombstoneDeposit; - type DepositPerContract = DepositPerContract; - type DepositPerStorageByte = DepositPerStorageByte; - type DepositPerStorageItem = DepositPerStorageItem; - type RentFraction = RentFraction; - type SurchargeReward = SurchargeReward; - type WeightPrice = pallet_transaction_payment::Pallet; - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = NFTExtension; - type DeletionQueueDepth = DeletionQueueDepth; - type DeletionWeightLimit = DeletionWeightLimit; - type Schedule = Schedule; - type CallStack = [pallet_contracts::Frame; 31]; -} -*/ +construct_runtime!(quartz); -parameter_types! { - /// This value increases the priority of `Operational` transactions by adding - /// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`. - pub const OperationalFeeMultiplier: u8 = 5; -} - -/// Linear implementor of `WeightToFeePolynomial` -pub struct LinearFee(sp_std::marker::PhantomData); - -impl WeightToFeePolynomial for LinearFee -where - T: BaseArithmetic + From + Copy + Unsigned, -{ - type Balance = T; - - fn polynomial() -> WeightToFeeCoefficients { - smallvec!(WeightToFeeCoefficient { - coeff_integer: WEIGHT_TO_FEE_COEFF.into(), - coeff_frac: Perbill::zero(), - negative: false, - degree: 1, - }) - } -} - -impl pallet_transaction_payment::Config for Runtime { - type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; - type LengthToFee = ConstantMultiplier; - type OperationalFeeMultiplier = OperationalFeeMultiplier; - type WeightToFee = LinearFee; - type FeeMultiplierUpdate = (); -} - -parameter_types! { - pub const ProposalBond: Permill = Permill::from_percent(5); - pub const ProposalBondMinimum: Balance = 1 * UNIQUE; - pub const ProposalBondMaximum: Balance = 1000 * UNIQUE; - pub const SpendPeriod: BlockNumber = 5 * MINUTES; - pub const Burn: Permill = Permill::from_percent(0); - pub const TipCountdown: BlockNumber = 1 * DAYS; - pub const TipFindersFee: Percent = Percent::from_percent(20); - pub const TipReportDepositBase: Balance = 1 * UNIQUE; - pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE; - pub const BountyDepositBase: Balance = 1 * UNIQUE; - pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; - pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; - pub const MaximumReasonLength: u32 = 16384; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: Balance = 5 * UNIQUE; - pub const MaxApprovals: u32 = 100; -} - -impl pallet_treasury::Config for Runtime { - type PalletId = TreasuryModuleId; - type Currency = Balances; - type ApproveOrigin = EnsureRoot; - type RejectOrigin = EnsureRoot; - type Event = Event; - type OnSlash = (); - type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type ProposalBondMaximum = ProposalBondMaximum; - type SpendPeriod = SpendPeriod; - type Burn = Burn; - type BurnDestination = (); - type SpendFunds = (); - type WeightInfo = pallet_treasury::weights::SubstrateWeight; - type MaxApprovals = MaxApprovals; -} - -impl pallet_sudo::Config for Runtime { - type Event = Event; - type Call = Call; -} - -pub struct RelayChainBlockNumberProvider(sp_std::marker::PhantomData); - -impl BlockNumberProvider - for RelayChainBlockNumberProvider -{ - type BlockNumber = BlockNumber; - - fn current_block_number() -> Self::BlockNumber { - cumulus_pallet_parachain_system::Pallet::::validation_data() - .map(|d| d.relay_parent_number) - .unwrap_or_default() - } -} - -parameter_types! { - pub const MinVestedTransfer: Balance = 10 * UNIQUE; - pub const MaxVestingSchedules: u32 = 28; -} - -impl orml_vesting::Config for Runtime { - type Event = Event; - type Currency = pallet_balances::Pallet; - type MinVestedTransfer = MinVestedTransfer; - type VestedTransferOrigin = EnsureSigned; - type WeightInfo = (); - type MaxVestingSchedules = MaxVestingSchedules; - type BlockNumberProvider = RelayChainBlockNumberProvider; -} - -parameter_types! { - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; - pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type Event = Event; - type SelfParaId = parachain_info::Pallet; - type OnSystemEvent = (); - // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent< - // MaxDownwardMessageWeight, - // XcmExecutor, - // Call, - // >; - type OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = DmpQueue; - type ReservedDmpWeight = ReservedDmpWeight; - type ReservedXcmpWeight = ReservedXcmpWeight; - type XcmpMessageHandler = XcmpQueue; -} - -impl parachain_info::Config for Runtime {} - -impl cumulus_pallet_aura_ext::Config for Runtime {} - -parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Polkadot; - pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); -} - -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used -/// when determining ownership of accounts for asset transacting and when attempting to use XCM -/// `Transact` in order to determine the dispatch Origin. -pub type LocationToAccountId = ( - // The parent (Relay-chain) origin converts to the default `AccountId`. - ParentIsPreset, - // Sibling parachain origins convert to AccountId via the `ParaId::into`. - SiblingParachainConvertsVia, - // Straight up local `AccountId32` origins just alias directly to `AccountId`. - AccountId32Aliases, -); - -pub struct OnlySelfCurrency; -impl> MatchesFungible for OnlySelfCurrency { - fn matches_fungible(a: &MultiAsset) -> Option { - 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: - 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): - AccountId, - // We don't track any teleports. - (), ->; - -/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// biases the kind of local `Origin` it will become. -pub type XcmOriginToTransactDispatchOrigin = ( - // Sovereign account converter; this attempts to derive an `AccountId` from the origin location - // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for - // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when - // recognised. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognised. - SiblingParachainAsNative, - // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a - // transaction from the Root origin. - ParentAsSuperuser, - // Native signed account converter; this just converts an `AccountId32` origin into a normal - // `Origin::Signed` origin of the same 32-byte value. - SignedAccountId32AsNative, - // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. - XcmPassthrough, -); - -parameter_types! { - // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. - pub UnitWeightCost: Weight = 1_000_000; - // 1200 UNIQUEs buy 1 second of weight. - pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE); - pub const MaxInstructions: u32 = 100; - pub const MaxAuthorities: u32 = 100_000; -} - -match_types! { - pub type ParentOrParentsUnitPlurality: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) } - }; -} - -pub type Barrier = ( - TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, - // ^^^ Parent & its unit plurality gets free execution -); - -pub struct UsingOnlySelfCurrencyComponents< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, ->( - Weight, - Currency::Balance, - PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, -); -impl< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > WeightTrader - for UsingOnlySelfCurrencyComponents -{ - fn new() -> Self { - Self(0, Zero::zero(), PhantomData) - } - - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { - let amount = WeightToFee::weight_to_fee(&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 { - let weight = weight.min(self.0); - let amount = WeightToFee::weight_to_fee(&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, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > Drop - for UsingOnlySelfCurrencyComponents -{ - fn drop(&mut self) { - OnUnbalanced::on_unbalanced(Currency::issue(self.1)); - } -} - -pub struct XcmConfig; -impl Config for XcmConfig { - type Call = Call; - type XcmSender = XcmRouter; - // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = NativeAsset; - type IsTeleporter = (); // Teleportation is disabled - type LocationInverter = LocationInverter; - type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = - UsingOnlySelfCurrencyComponents, RelayLocation, AccountId, Balances, ()>; - type ResponseHandler = (); // Don't handle responses for now. - type SubscriptionService = PolkadotXcm; - - type AssetTrap = PolkadotXcm; - type AssetClaims = PolkadotXcm; -} - -// parameter_types! { -// pub const MaxDownwardMessageWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10; -// } - -/// No local origins on this chain are allowed to dispatch XCM sends/executions. -pub type LocalOriginToLocation = (SignedToAccountId32,); - -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = ( - // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, -); - -impl pallet_evm_coder_substrate::Config for Runtime {} - -impl pallet_xcm::Config for Runtime { - type Event = Event; - type SendXcmOrigin = EnsureXcmOrigin; - type XcmRouter = XcmRouter; - type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Everything; - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; - type Origin = Origin; - type Call = Call; - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; -} - -impl cumulus_pallet_xcm::Config for Runtime { - type Event = Event; - type XcmExecutor = XcmExecutor; -} - -impl cumulus_pallet_xcmp_queue::Config for Runtime { - type WeightInfo = (); - type Event = Event; - type XcmExecutor = XcmExecutor; - type ChannelInfo = ParachainSystem; - type VersionWrapper = (); - type ExecuteOverweightOrigin = frame_system::EnsureRoot; - type ControllerOrigin = EnsureRoot; - type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type Event = Event; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = frame_system::EnsureRoot; -} - -impl pallet_aura::Config for Runtime { - type AuthorityId = AuraId; - type DisabledValidators = (); - type MaxAuthorities = MaxAuthorities; -} - -parameter_types! { - pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating(); - pub const CollectionCreationPrice: Balance = 2 * UNIQUE; -} - -impl pallet_common::Config for Runtime { - type WeightInfo = pallet_common::weights::SubstrateWeight; - type Event = Event; - type Currency = Balances; - type CollectionCreationPrice = CollectionCreationPrice; - type TreasuryAccountId = TreasuryAccountId; - type CollectionDispatch = CollectionDispatchT; - - type EvmTokenAddressMapping = EvmTokenAddressMapping; - type CrossTokenAddressMapping = CrossTokenAddressMapping; - type ContractAddress = EvmCollectionHelpersAddress; -} - -impl pallet_structure::Config for Runtime { - type Event = Event; - type Call = Call; - type WeightInfo = pallet_structure::weights::SubstrateWeight; -} - -impl pallet_fungible::Config for Runtime { - type WeightInfo = pallet_fungible::weights::SubstrateWeight; -} -impl pallet_refungible::Config for Runtime { - type WeightInfo = pallet_refungible::weights::SubstrateWeight; -} -impl pallet_nonfungible::Config for Runtime { - type WeightInfo = pallet_nonfungible::weights::SubstrateWeight; -} - -impl pallet_proxy_rmrk_core::Config for Runtime { - type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight; - type Event = Event; -} - -impl pallet_proxy_rmrk_equip::Config for Runtime { - type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight; - type Event = Event; -} - -impl pallet_unique::Config for Runtime { - type Event = Event; - type WeightInfo = pallet_unique::weights::SubstrateWeight; - type CommonWeightInfo = CommonWeights; - type RefungibleExtensionsWeightInfo = CommonWeights; -} - -parameter_types! { - pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied -} - -/// Used for the pallet inflation -impl pallet_inflation::Config for Runtime { - type Currency = Balances; - type TreasuryAccountId = TreasuryAccountId; - type InflationBlockInterval = InflationBlockInterval; - type BlockNumberProvider = RelayChainBlockNumberProvider; -} - -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) * - RuntimeBlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 50; -} - -type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment; -use frame_support::traits::NamedReservableCurrency; - -fn get_signed_extras(from: ::AccountId) -> SignedExtraScheduler { - ( - frame_system::CheckSpecVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(Era::Immortal), - frame_system::CheckNonce::::from(frame_system::Pallet::::account_nonce( - from, - )), - frame_system::CheckWeight::::new(), - // sponsoring transaction logic - // pallet_charge_transaction::ChargeTransactionPayment::::new(0), - ) -} - -pub struct SchedulerPaymentExecutor; -impl - DispatchCall for SchedulerPaymentExecutor -where - ::Call: Member - + Dispatchable - + SelfContainedCall - + GetDispatchInfo - + From>, - SelfContainedSignedInfo: Send + Sync + 'static, - Call: From<::Call> - + From<::Call> - + SelfContainedCall, - sp_runtime::AccountId32: From<::AccountId>, -{ - fn dispatch_call( - signer: ::AccountId, - call: ::Call, - ) -> Result< - Result>, - TransactionValidityError, - > { - let dispatch_info = call.get_dispatch_info(); - let extrinsic = fp_self_contained::CheckedExtrinsic::< - AccountId, - Call, - SignedExtraScheduler, - SelfContainedSignedInfo, - > { - signed: - CheckedSignature::::Signed( - signer.clone().into(), - get_signed_extras(signer.into()), - ), - function: call.into(), - }; - - extrinsic.apply::(&dispatch_info, 0) - } - - fn reserve_balance( - id: [u8; 16], - sponsor: ::AccountId, - call: ::Call, - count: u32, - ) -> Result<(), DispatchError> { - let dispatch_info = call.get_dispatch_info(); - let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0) - .saturating_mul(count.into()); - - >::reserve_named( - &id, - &(sponsor.into()), - weight.into(), - ) - } - - fn pay_for_call( - id: [u8; 16], - sponsor: ::AccountId, - call: ::Call, - ) -> Result { - let dispatch_info = call.get_dispatch_info(); - let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0); - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - weight.into(), - ), - ) - } - - fn cancel_reserve( - id: [u8; 16], - sponsor: ::AccountId, - ) -> Result { - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - u128::MAX, - ), - ) - } -} - -parameter_types! { - pub const NoPreimagePostponement: Option = Some(10); - pub const Preimage: Option = Some(10); -} - -/// Used the compare the privilege of an origin inside the scheduler. -pub struct OriginPrivilegeCmp; - -impl PrivilegeCmp for OriginPrivilegeCmp { - fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { - Some(Ordering::Equal) - } -} - -impl pallet_unique_scheduler::Config for Runtime { - type Event = Event; - type Origin = Origin; - type Currency = Balances; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureSigned; - type MaxScheduledPerBlock = MaxScheduledPerBlock; - type WeightInfo = (); - type CallExecutor = SchedulerPaymentExecutor; - type OriginPrivilegeCmp = OriginPrivilegeCmp; - type PreimageProvider = (); - type NoPreimagePostponement = NoPreimagePostponement; -} - -type EvmSponsorshipHandler = ( - UniqueEthSponsorshipHandler, - pallet_evm_contract_helpers::HelpersContractSponsoring, -); -type SponsorshipHandler = ( - UniqueSponsorshipHandler, - //pallet_contract_helpers::ContractSponsorshipHandler, - pallet_evm_transaction_payment::BridgeSponsorshipHandler, -); - -impl pallet_evm_transaction_payment::Config for Runtime { - type EvmSponsorshipHandler = EvmSponsorshipHandler; - type Currency = Balances; -} - -impl pallet_charge_transaction::Config for Runtime { - type SponsorshipHandler = SponsorshipHandler; -} - -// impl pallet_contract_helpers::Config for Runtime { -// type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; -// } - -parameter_types! { - // 0x842899ECF380553E8a4de75bF534cdf6fBF64049 - pub const HelpersContractAddress: H160 = H160([ - 0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49, - ]); - - // 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f - pub const EvmCollectionHelpersAddress: H160 = H160([ - 0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f, - ]); -} - -impl pallet_evm_contract_helpers::Config for Runtime { - type ContractAddress = HelpersContractAddress; - type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; -} - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = opaque::Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned} = 20, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21, - - Aura: pallet_aura::{Pallet, Config} = 22, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23, - - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 30, - RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 34, - Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event} = 35, - System: frame_system::{Pallet, Call, Storage, Config, Event} = 36, - Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 37, - // Vesting: pallet_vesting::{Pallet, Call, Config, Storage, Event} = 37, - // Contracts: pallet_contracts::{Pallet, Call, Storage, Event} = 38, - - // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50, - PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin} = 51, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event, Origin} = 52, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 53, - - // Unique Pallets - Inflation: pallet_inflation::{Pallet, Call, Storage} = 60, - Unique: pallet_unique::{Pallet, Call, Storage, Event} = 61, - Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event} = 62, - // free = 63 - Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64, - // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65, - Common: pallet_common::{Pallet, Storage, Event} = 66, - Fungible: pallet_fungible::{Pallet, Storage} = 67, - Refungible: pallet_refungible::{Pallet, Storage} = 68, - Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69, - Structure: pallet_structure::{Pallet, Call, Storage, Event} = 70, - RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event} = 71, - RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event} = 72, - - // Frontier - EVM: pallet_evm::{Pallet, Config, Call, Storage, Event} = 100, - Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101, - - EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150, - EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151, - EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152, - EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153, - } -); - -pub struct TransactionConverter; - -impl fp_rpc::ConvertTransaction for TransactionConverter { - fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic { - UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), - ) - } -} - -impl fp_rpc::ConvertTransaction for TransactionConverter { - fn convert_transaction( - &self, - transaction: pallet_ethereum::Transaction, - ) -> opaque::UncheckedExtrinsic { - let extrinsic = UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), - ); - let encoded = extrinsic.encode(); - opaque::UncheckedExtrinsic::decode(&mut &encoded[..]) - .expect("Encoded extrinsic is always valid") - } -} - -/// The address format for describing accounts. -pub type Address = sp_runtime::MultiAddress; -/// Block header type as expected by this runtime. -pub type Header = generic::Header; -/// Block type as expected by this runtime. -pub type Block = generic::Block; -/// A Block signed with a Justification -pub type SignedBlock = generic::SignedBlock; -/// BlockId type as expected by this runtime. -pub type BlockId = generic::BlockId; -/// The SignedExtension to the basic transaction logic. -pub type SignedExtra = ( - frame_system::CheckSpecVersion, - // system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - ChargeTransactionPayment, - //pallet_contract_helpers::ContractHelpersExtension, - pallet_ethereum::FakeTransactionFinalizer, -); - -pub type SignedExtraScheduler = ( - frame_system::CheckSpecVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - // pallet_charge_transaction::ChargeTransactionPayment, -); -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - fp_self_contained::UncheckedExtrinsic; -/// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic; -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsReversedWithSystemFirst, ->; - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -impl fp_self_contained::SelfContainedCall for Call { - type SignedInfo = H160; - - fn is_self_contained(&self) -> bool { - match self { - Call::Ethereum(call) => call.is_self_contained(), - _ => false, - } - } - - fn check_self_contained(&self) -> Option> { - match self { - Call::Ethereum(call) => call.check_self_contained(), - _ => None, - } - } - - fn validate_self_contained( - &self, - info: &Self::SignedInfo, - dispatch_info: &DispatchInfoOf, - len: usize, - ) -> Option { - match self { - Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len), - _ => None, - } - } - - fn pre_dispatch_self_contained( - &self, - info: &Self::SignedInfo, - ) -> Option> { - match self { - Call::Ethereum(call) => call.pre_dispatch_self_contained(info), - _ => None, - } - } - - fn apply_self_contained( - self, - info: Self::SignedInfo, - ) -> Option>> { - match self { - call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch( - Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)), - )), - _ => None, - } - } -} - -macro_rules! dispatch_unique_runtime { - ($collection:ident.$method:ident($($name:ident),*)) => {{ - let collection = ::CollectionDispatch::dispatch(>::try_get($collection)?); - let dispatch = collection.as_dyn(); - - Ok::<_, DispatchError>(dispatch.$method($($name),*)) - }}; -} - -impl_common_runtime_apis! { - #![custom_apis] - - impl rmrk_rpc::RmrkApi< - Block, - AccountId, - RmrkCollectionInfo, - RmrkInstanceInfo, - RmrkResourceInfo, - RmrkPropertyInfo, - RmrkBaseInfo, - RmrkPartType, - RmrkTheme - > for Runtime { - fn last_collection_idx() -> Result { - pallet_proxy_rmrk_core::rpc::last_collection_idx::() - } - - fn collection_by_id(collection_id: RmrkCollectionId) -> Result>, DispatchError> { - pallet_proxy_rmrk_core::rpc::collection_by_id::(collection_id) - } - - fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result>, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_by_id::(collection_id, nft_by_id) - } - - fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::account_tokens::(account_id, collection_id) - } - - fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_children::(collection_id, nft_id) - } - - fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option>) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::collection_properties::(collection_id, filter_keys) - } - - fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option>) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_properties::(collection_id, nft_id, filter_keys) - } - - fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_resources::(collection_id, nft_id) - } - - fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result, DispatchError> { - pallet_proxy_rmrk_core::rpc::nft_resource_priority::(collection_id, nft_id, resource_id) - } - - fn base(base_id: RmrkBaseId) -> Result>, DispatchError> { - pallet_proxy_rmrk_equip::rpc::base::(base_id) - } - - fn base_parts(base_id: RmrkBaseId) -> Result, DispatchError> { - pallet_proxy_rmrk_equip::rpc::base_parts::(base_id) - } - - fn theme_names(base_id: RmrkBaseId) -> Result, DispatchError> { - pallet_proxy_rmrk_equip::rpc::theme_names::(base_id) - } - - fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option>) -> Result, DispatchError> { - pallet_proxy_rmrk_equip::rpc::theme::(base_id, theme_name, filter_keys) - } - } -} - -struct CheckInherents; - -impl cumulus_pallet_parachain_system::CheckInherents for CheckInherents { - fn check_inherents( - block: &Block, - relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof, - ) -> sp_inherents::CheckInherentsResult { - let relay_chain_slot = relay_state_proof - .read_slot() - .expect("Could not read the relay chain slot from the proof"); - - let inherent_data = - cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration( - relay_chain_slot, - sp_std::time::Duration::from_secs(6), - ) - .create_inherent_data() - .expect("Could not create the timestamp inherent data"); - - inherent_data.check_extrinsics(block) - } -} +impl_common_runtime_apis!(); cumulus_pallet_parachain_system::register_validate_block!( Runtime = Runtime, --- a/runtime/tests/Cargo.toml +++ b/runtime/tests/Cargo.toml @@ -3,8 +3,12 @@ version = "0.1.0" edition = "2021" +[features] +default = ['refungible'] + +refungible = [] + [dependencies] -unique-runtime-common = { path = '../common' } up-data-structs = { default-features = false, path = '../../primitives/data-structs' } sp-core = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.24' } @@ -37,3 +41,6 @@ "derive", ] } scale-info = "*" + +evm-coder = { default-features = false, path = '../../crates/evm-coder' } +up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' } --- a/runtime/tests/src/lib.rs +++ b/runtime/tests/src/lib.rs @@ -35,9 +35,18 @@ use parity_scale_codec::{Encode, Decode, MaxEncodedLen}; use scale_info::TypeInfo; -use unique_runtime_common::{dispatch::CollectionDispatchT, weights::CommonWeights}; use up_data_structs::mapping::{CrossTokenAddressMapping, EvmTokenAddressMapping}; +#[path = "../../common/dispatch.rs"] +mod dispatch; + +use dispatch::CollectionDispatchT; + +#[path = "../../common/weights.rs"] +mod weights; + +use weights::CommonWeights; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; --- a/runtime/unique/Cargo.toml +++ b/runtime/unique/Cargo.toml @@ -114,13 +114,17 @@ 'xcm/std', 'xcm-builder/std', 'xcm-executor/std', - 'unique-runtime-common/std', + 'up-common/std', "orml-vesting/std", ] limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing'] unique-runtime = [] +refungible = [] +scheduler = [] +rmrk = [] + ################################################################################ # Substrate Dependencies @@ -397,7 +401,7 @@ [dependencies] log = { version = "0.4.16", default-features = false } -unique-runtime-common = { path = "../common", default-features = false } +up-common = { path = "../../primitives/common", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } @@ -427,6 +431,8 @@ fp-rpc = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } fp-self-contained = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } fp-evm-mapping = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.24" } +evm-coder = { default-features = false, path = '../../crates/evm-coder' } +up-sponsorship = { default-features = false, git = "https://github.com/uniquenetwork/pallet-sponsoring", branch = 'polkadot-v0.9.24' } ################################################################################ # Build Dependencies --- a/runtime/unique/src/lib.rs +++ b/runtime/unique/src/lib.rs @@ -25,166 +25,21 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160}; -use sp_runtime::DispatchError; -use fp_self_contained::*; -// #[cfg(any(feature = "std", test))] -// pub use sp_runtime::BuildStorage; - -use sp_runtime::{ - Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys, - traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member}, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, RuntimeAppPublic, -}; - -use sp_std::prelude::*; +use frame_support::parameter_types; -#[cfg(feature = "std")] -use sp_version::NativeVersion; use sp_version::RuntimeVersion; -pub use pallet_transaction_payment::{ - Multiplier, TargetedFeeAdjustment, FeeDetails, RuntimeDispatchInfo, -}; -// A few exports that help ease life for downstream crates. -pub use pallet_balances::Call as BalancesCall; -pub use pallet_evm::{ - EnsureAddressTruncated, HashedAddressMapping, Runner, account::CrossAccountId as _, - OnMethodCall, Account as EVMAccount, FeeCalculator, GasWeightMapping, -}; -pub use frame_support::{ - construct_runtime, match_types, - dispatch::DispatchResult, - PalletId, parameter_types, StorageValue, ConsensusEngineId, - traits::{ - tokens::currency::Currency as CurrencyT, OnUnbalanced as OnUnbalancedT, Everything, - Currency, ExistenceRequirement, Get, IsInVec, KeyOwnerProofSystem, LockIdentifier, - OnUnbalanced, Randomness, FindAuthor, ConstU32, Imbalance, PrivilegeCmp, - }, - weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - DispatchClass, DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight, - WeightToFeePolynomial, WeightToFeeCoefficient, WeightToFeeCoefficients, ConstantMultiplier, - WeightToFee, - }, -}; -use pallet_unique_scheduler::DispatchCall; -use up_data_structs::{ - CollectionId, TokenId, TokenData, Property, PropertyKeyPermission, CollectionLimits, - CollectionStats, RpcCollection, - mapping::{EvmTokenAddressMapping, CrossTokenAddressMapping}, - TokenChild, RmrkCollectionInfo, RmrkInstanceInfo, RmrkResourceInfo, RmrkPropertyInfo, - RmrkBaseInfo, RmrkPartType, RmrkTheme, RmrkThemeName, RmrkCollectionId, RmrkNftId, - RmrkNftChild, RmrkPropertyKey, RmrkResourceId, RmrkBaseId, -}; +use sp_runtime::create_runtime_str; -// use pallet_contracts::weights::WeightInfo; -// #[cfg(any(feature = "std", test))] -use frame_system::{ - self as frame_system, EnsureRoot, EnsureSigned, - limits::{BlockWeights, BlockLength}, -}; -use sp_arithmetic::{ - traits::{BaseArithmetic, Unsigned}, -}; -use smallvec::smallvec; -use codec::{Encode, Decode}; -use fp_rpc::TransactionStatus; -use sp_runtime::{ - traits::{ - Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, DispatchInfoOf, - Saturating, CheckedConversion, - }, - generic::Era, - transaction_validity::TransactionValidityError, - DispatchErrorWithPostInfo, SaturatedConversion, -}; +use up_common::types::*; -// pub use pallet_timestamp::Call as TimestampCall; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +#[path = "../../common/mod.rs"] +mod runtime_common; -// Polkadot imports -use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::Sibling; -use xcm::v1::{BodyId, Junction::*, MultiLocation, NetworkId, Junctions::*}; -use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - EnsureXcmOrigin, FixedWeightBounds, LocationInverter, NativeAsset, ParentAsSuperuser, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - ParentIsPreset, -}; -use xcm_executor::{Config, XcmExecutor, Assets}; -use sp_std::{cmp::Ordering, marker::PhantomData}; - -use xcm::latest::{ - // Xcm, - AssetId::{Concrete}, - Fungibility::Fungible as XcmFungible, - MultiAsset, - Error as XcmError, -}; -use xcm_executor::traits::{MatchesFungible, WeightTrader}; - -use unique_runtime_common::{ - impl_common_runtime_apis, - types::*, - constants::*, - dispatch::{CollectionDispatchT, CollectionDispatch}, - sponsoring::UniqueSponsorshipHandler, - eth_sponsoring::UniqueEthSponsorshipHandler, - weights::CommonWeights, -}; +pub use runtime_common::*; pub const RUNTIME_NAME: &str = "unique"; pub const TOKEN_SYMBOL: &str = "UNQ"; - -type CrossAccountId = pallet_evm::account::BasicCrossAccountId; - -impl RuntimeInstance for Runtime { - type CrossAccountId = self::CrossAccountId; - type TransactionConverter = self::TransactionConverter; - - fn get_transaction_converter() -> TransactionConverter { - TransactionConverter - } -} - -/// The type for looking up accounts. We don't expect more than 4 billion of them, but you -/// never know... -pub type AccountIndex = u32; - -/// Balance of an account. -pub type Balance = u128; - -/// Index of a transaction in the chain. -pub type Index = u32; - -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - -/// Digest item type. -pub type DigestItem = generic::DigestItem; - -/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know -/// the specifics of the runtime. They can then be made to be agnostic over specific formats -/// of data like extrinsics, allowing for them to continue syncing the network through upgrades -/// to even the core data structures. -pub mod opaque { - use sp_std::prelude::*; - use sp_runtime::impl_opaque_keys; - use super::Aura; - - pub use unique_runtime_common::types::*; - impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } - } -} - /// This runtime version. pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!(RUNTIME_NAME), @@ -197,1194 +52,15 @@ state_version: 0, }; -#[derive(codec::Encode, codec::Decode)] -pub enum XCMPMessage { - /// Transfer tokens to the given account from the Parachain account. - TransferToken(XAccountId, XBalance), -} - -/// The version information used to identify this runtime when compiled natively. -#[cfg(feature = "std")] -pub fn native_version() -> NativeVersion { - NativeVersion { - runtime_version: VERSION, - can_author_with: Default::default(), - } -} - -type NegativeImbalance = >::NegativeImbalance; - -pub struct DealWithFees; -impl OnUnbalanced for DealWithFees { - fn on_unbalanceds(mut fees_then_tips: impl Iterator) { - if let Some(fees) = fees_then_tips.next() { - // for fees, 100% to treasury - let mut split = fees.ration(100, 0); - if let Some(tips) = fees_then_tips.next() { - // for tips, if any, 100% to treasury - tips.ration_merge_into(100, 0, &mut split); - } - Treasury::on_unbalanced(split.0); - // Author::on_unbalanced(split.1); - } - } -} - parameter_types! { - pub const BlockHashCount: BlockNumber = 2400; - pub RuntimeBlockLength: BlockLength = - BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); - pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); - pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; - pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() - .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = ExtrinsicBaseWeight::get(); - }) - .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); - }) - .for_class(DispatchClass::Operational, |weights| { - weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); - // Operational transactions have some extra reserved space, so that they - // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. - weights.reserved = Some( - MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT - ); - }) - .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) - .build_or_panic(); pub const Version: RuntimeVersion = VERSION; pub const SS58Prefix: u16 = 7391; -} - -parameter_types! { pub const ChainId: u64 = 8880; -} - -pub struct FixedFee; -impl FeeCalculator for FixedFee { - fn min_gas_price() -> (U256, u64) { - (MIN_GAS_PRICE.into(), 0) - } -} - -// Assuming slowest ethereum opcode is SSTORE, with gas price of 20000 as our worst case -// (contract, which only writes a lot of data), -// approximating on top of our real store write weight -parameter_types! { - pub const WritesPerSecond: u64 = WEIGHT_PER_SECOND / ::DbWeight::get().write; - pub const GasPerSecond: u64 = WritesPerSecond::get() * 20000; - pub const WeightPerGas: u64 = WEIGHT_PER_SECOND / GasPerSecond::get(); } -/// Limiting EVM execution to 50% of block for substrate users and management tasks -/// EVM transaction consumes more weight than substrate's, so we can't rely on them being -/// scheduled fairly -const EVM_DISPATCH_RATIO: Perbill = Perbill::from_percent(50); -parameter_types! { - pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * EVM_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT / WeightPerGas::get()); -} - -pub enum FixedGasWeightMapping {} -impl GasWeightMapping for FixedGasWeightMapping { - fn gas_to_weight(gas: u64) -> Weight { - gas.saturating_mul(WeightPerGas::get()) - } - fn weight_to_gas(weight: Weight) -> u64 { - weight / WeightPerGas::get() - } -} - -impl pallet_evm::account::Config for Runtime { - type CrossAccountId = pallet_evm::account::BasicCrossAccountId; - type EvmAddressMapping = pallet_evm::HashedAddressMapping; - type EvmBackwardsAddressMapping = fp_evm_mapping::MapBackwardsAddressTruncated; -} - -impl pallet_evm::Config for Runtime { - type BlockGasLimit = BlockGasLimit; - type FeeCalculator = FixedFee; - type GasWeightMapping = FixedGasWeightMapping; - type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; - type CallOrigin = EnsureAddressTruncated; - type WithdrawOrigin = EnsureAddressTruncated; - type AddressMapping = HashedAddressMapping; - type PrecompilesType = (); - type PrecompilesValue = (); - type Currency = Balances; - type Event = Event; - type OnMethodCall = ( - pallet_evm_migration::OnMethodCall, - pallet_evm_contract_helpers::HelpersOnMethodCall, - CollectionDispatchT, - pallet_unique::eth::CollectionHelpersOnMethodCall, - ); - type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate; - type ChainId = ChainId; - type Runner = pallet_evm::runner::stack::Runner; - type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter; - type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack; - type FindAuthor = EthereumFindAuthor; -} - -impl pallet_evm_migration::Config for Runtime { - type WeightInfo = pallet_evm_migration::weights::SubstrateWeight; -} - -pub struct EthereumFindAuthor(core::marker::PhantomData); -impl> FindAuthor for EthereumFindAuthor { - fn find_author<'a, I>(digests: I) -> Option - where - I: 'a + IntoIterator, - { - if let Some(author_index) = F::find_author(digests) { - let authority_id = Aura::authorities()[author_index as usize].clone(); - return Some(H160::from_slice(&authority_id.to_raw_vec()[4..24])); - } - None - } -} - -impl pallet_ethereum::Config for Runtime { - type Event = Event; - type StateRoot = pallet_ethereum::IntermediateStateRoot; -} - -impl pallet_randomness_collective_flip::Config for Runtime {} +construct_runtime!(unique); -impl frame_system::Config for Runtime { - /// The data to be stored in an account. - type AccountData = pallet_balances::AccountData; - /// The identifier used to distinguish between accounts. - type AccountId = AccountId; - /// The basic call filter to use in dispatchable. - type BaseCallFilter = Everything; - /// Maximum number of block number to block hash mappings to keep (oldest pruned first). - type BlockHashCount = BlockHashCount; - /// The maximum length of a block (in bytes). - type BlockLength = RuntimeBlockLength; - /// The index type for blocks. - type BlockNumber = BlockNumber; - /// The weight of the overhead invoked on the block import process, independent of the extrinsics included in that block. - type BlockWeights = RuntimeBlockWeights; - /// The aggregated dispatch type that is available for extrinsics. - type Call = Call; - /// The weight of database operations that the runtime can invoke. - type DbWeight = RocksDbWeight; - /// The ubiquitous event type. - type Event = Event; - /// The type for hashing blocks and tries. - type Hash = Hash; - /// The hashing algorithm used. - type Hashing = BlakeTwo256; - /// The header type. - type Header = generic::Header; - /// The index type for storing how many extrinsics an account has signed. - type Index = Index; - /// The lookup mechanism to get account ID from whatever is passed in dispatchers. - type Lookup = AccountIdLookup; - /// What to do if an account is fully reaped from the system. - type OnKilledAccount = (); - /// What to do if a new account is created. - type OnNewAccount = (); - type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; - /// The ubiquitous origin type. - type Origin = Origin; - /// This type is being generated by `construct_runtime!`. - type PalletInfo = PalletInfo; - /// This is used as an identifier of the chain. 42 is the generic substrate prefix. - type SS58Prefix = SS58Prefix; - /// Weight information for the extrinsics of this pallet. - type SystemWeightInfo = frame_system::weights::SubstrateWeight; - /// Version of the runtime. - type Version = Version; - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; -} - -impl pallet_timestamp::Config for Runtime { - /// A timestamp: milliseconds since the unix epoch. - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; - type WeightInfo = (); -} - -parameter_types! { - // pub const ExistentialDeposit: u128 = 500; - pub const ExistentialDeposit: u128 = 0; - pub const MaxLocks: u32 = 50; - pub const MaxReserves: u32 = 50; -} - -impl pallet_balances::Config for Runtime { - type MaxLocks = MaxLocks; - type MaxReserves = MaxReserves; - type ReserveIdentifier = [u8; 16]; - /// The type for recording an account's balance. - type Balance = Balance; - /// The ubiquitous event type. - type Event = Event; - type DustRemoval = Treasury; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type WeightInfo = pallet_balances::weights::SubstrateWeight; -} - -pub const fn deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 15 * CENTIUNIQUE + (bytes as Balance) * 6 * CENTIUNIQUE -} - -/* -parameter_types! { - pub TombstoneDeposit: Balance = deposit( - 1, - sp_std::mem::size_of::> as u32, - ); - pub DepositPerContract: Balance = TombstoneDeposit::get(); - pub const DepositPerStorageByte: Balance = deposit(0, 1); - pub const DepositPerStorageItem: Balance = deposit(1, 0); - pub RentFraction: Perbill = Perbill::from_rational(1u32, 30 * DAYS); - pub const SurchargeReward: Balance = 150 * MILLIUNIQUE; - pub const SignedClaimHandicap: u32 = 2; - pub const MaxDepth: u32 = 32; - pub const MaxValueSize: u32 = 16 * 1024; - pub const MaxCodeSize: u32 = 1024 * 1024 * 25; // 25 Mb - // The lazy deletion runs inside on_initialize. - pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * - RuntimeBlockWeights::get().max_block; - // The weight needed for decoding the queue should be less or equal than a fifth - // of the overall weight dedicated to the lazy deletion. - pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / ( - ::WeightInfo::on_initialize_per_queue_item(1) - - ::WeightInfo::on_initialize_per_queue_item(0) - )) / 5) as u32; - pub Schedule: pallet_contracts::Schedule = Default::default(); -} - -impl pallet_contracts::Config for Runtime { - type Time = Timestamp; - type Randomness = RandomnessCollectiveFlip; - type Currency = Balances; - type Event = Event; - type RentPayment = (); - type SignedClaimHandicap = SignedClaimHandicap; - type TombstoneDeposit = TombstoneDeposit; - type DepositPerContract = DepositPerContract; - type DepositPerStorageByte = DepositPerStorageByte; - type DepositPerStorageItem = DepositPerStorageItem; - type RentFraction = RentFraction; - type SurchargeReward = SurchargeReward; - type WeightPrice = pallet_transaction_payment::Pallet; - type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = NFTExtension; - type DeletionQueueDepth = DeletionQueueDepth; - type DeletionWeightLimit = DeletionWeightLimit; - type Schedule = Schedule; - type CallStack = [pallet_contracts::Frame; 31]; -} -*/ - -parameter_types! { - /// This value increases the priority of `Operational` transactions by adding - /// a "virtual tip" that's equal to the `OperationalFeeMultiplier * final_fee`. - pub const OperationalFeeMultiplier: u8 = 5; -} - -/// Linear implementor of `WeightToFeePolynomial` -pub struct LinearFee(sp_std::marker::PhantomData); - -impl WeightToFeePolynomial for LinearFee -where - T: BaseArithmetic + From + Copy + Unsigned, -{ - type Balance = T; - - fn polynomial() -> WeightToFeeCoefficients { - smallvec!(WeightToFeeCoefficient { - coeff_integer: WEIGHT_TO_FEE_COEFF.into(), - coeff_frac: Perbill::zero(), - negative: false, - degree: 1, - }) - } -} - -impl pallet_transaction_payment::Config for Runtime { - type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; - type LengthToFee = ConstantMultiplier; - type OperationalFeeMultiplier = OperationalFeeMultiplier; - type WeightToFee = LinearFee; - type FeeMultiplierUpdate = (); -} - -parameter_types! { - pub const ProposalBond: Permill = Permill::from_percent(5); - pub const ProposalBondMinimum: Balance = 1 * UNIQUE; - pub const ProposalBondMaximum: Balance = 1000 * UNIQUE; - pub const SpendPeriod: BlockNumber = 5 * MINUTES; - pub const Burn: Permill = Permill::from_percent(0); - pub const TipCountdown: BlockNumber = 1 * DAYS; - pub const TipFindersFee: Percent = Percent::from_percent(20); - pub const TipReportDepositBase: Balance = 1 * UNIQUE; - pub const DataDepositPerByte: Balance = 1 * CENTIUNIQUE; - pub const BountyDepositBase: Balance = 1 * UNIQUE; - pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; - pub const TreasuryModuleId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; - pub const MaximumReasonLength: u32 = 16384; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: Balance = 5 * UNIQUE; - pub const MaxApprovals: u32 = 100; -} - -impl pallet_treasury::Config for Runtime { - type PalletId = TreasuryModuleId; - type Currency = Balances; - type ApproveOrigin = EnsureRoot; - type RejectOrigin = EnsureRoot; - type Event = Event; - type OnSlash = (); - type ProposalBond = ProposalBond; - type ProposalBondMinimum = ProposalBondMinimum; - type ProposalBondMaximum = ProposalBondMaximum; - type SpendPeriod = SpendPeriod; - type Burn = Burn; - type BurnDestination = (); - type SpendFunds = (); - type WeightInfo = pallet_treasury::weights::SubstrateWeight; - type MaxApprovals = MaxApprovals; -} - -impl pallet_sudo::Config for Runtime { - type Event = Event; - type Call = Call; -} - -pub struct RelayChainBlockNumberProvider(sp_std::marker::PhantomData); - -impl BlockNumberProvider - for RelayChainBlockNumberProvider -{ - type BlockNumber = BlockNumber; - - fn current_block_number() -> Self::BlockNumber { - cumulus_pallet_parachain_system::Pallet::::validation_data() - .map(|d| d.relay_parent_number) - .unwrap_or_default() - } -} - -parameter_types! { - pub const MinVestedTransfer: Balance = 10 * UNIQUE; - pub const MaxVestingSchedules: u32 = 28; -} - -impl orml_vesting::Config for Runtime { - type Event = Event; - type Currency = pallet_balances::Pallet; - type MinVestedTransfer = MinVestedTransfer; - type VestedTransferOrigin = EnsureSigned; - type WeightInfo = (); - type MaxVestingSchedules = MaxVestingSchedules; - type BlockNumberProvider = RelayChainBlockNumberProvider; -} - -parameter_types! { - pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; - pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; -} - -impl cumulus_pallet_parachain_system::Config for Runtime { - type Event = Event; - type SelfParaId = parachain_info::Pallet; - type OnSystemEvent = (); - // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent< - // MaxDownwardMessageWeight, - // XcmExecutor, - // Call, - // >; - type OutboundXcmpMessageSource = XcmpQueue; - type DmpMessageHandler = DmpQueue; - type ReservedDmpWeight = ReservedDmpWeight; - type ReservedXcmpWeight = ReservedXcmpWeight; - type XcmpMessageHandler = XcmpQueue; -} - -impl parachain_info::Config for Runtime {} - -impl cumulus_pallet_aura_ext::Config for Runtime {} - -parameter_types! { - pub const RelayLocation: MultiLocation = MultiLocation::parent(); - pub const RelayNetwork: NetworkId = NetworkId::Polkadot; - pub RelayOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); -} - -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used -/// when determining ownership of accounts for asset transacting and when attempting to use XCM -/// `Transact` in order to determine the dispatch Origin. -pub type LocationToAccountId = ( - // The parent (Relay-chain) origin converts to the default `AccountId`. - ParentIsPreset, - // Sibling parachain origins convert to AccountId via the `ParaId::into`. - SiblingParachainConvertsVia, - // Straight up local `AccountId32` origins just alias directly to `AccountId`. - AccountId32Aliases, -); - -pub struct OnlySelfCurrency; -impl> MatchesFungible for OnlySelfCurrency { - fn matches_fungible(a: &MultiAsset) -> Option { - 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: - 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): - AccountId, - // We don't track any teleports. - (), ->; - -/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// biases the kind of local `Origin` it will become. -pub type XcmOriginToTransactDispatchOrigin = ( - // Sovereign account converter; this attempts to derive an `AccountId` from the origin location - // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for - // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when - // recognised. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognised. - SiblingParachainAsNative, - // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a - // transaction from the Root origin. - ParentAsSuperuser, - // Native signed account converter; this just converts an `AccountId32` origin into a normal - // `Origin::Signed` origin of the same 32-byte value. - SignedAccountId32AsNative, - // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. - XcmPassthrough, -); - -parameter_types! { - // One XCM operation is 1_000_000 weight - almost certainly a conservative estimate. - pub UnitWeightCost: Weight = 1_000_000; - // 1200 UNIQUEs buy 1 second of weight. - pub const WeightPrice: (MultiLocation, u128) = (MultiLocation::parent(), 1_200 * UNIQUE); - pub const MaxInstructions: u32 = 100; - pub const MaxAuthorities: u32 = 100_000; -} - -match_types! { - pub type ParentOrParentsUnitPlurality: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) } - }; -} - -pub type Barrier = ( - TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, - // ^^^ Parent & its unit plurality gets free execution -); - -pub struct UsingOnlySelfCurrencyComponents< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, ->( - Weight, - Currency::Balance, - PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>, -); -impl< - WeightToFee: WeightToFeePolynomial, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > WeightTrader - for UsingOnlySelfCurrencyComponents -{ - fn new() -> Self { - Self(0, Zero::zero(), PhantomData) - } - - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { - let amount = WeightToFee::weight_to_fee(&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 { - let weight = weight.min(self.0); - let amount = WeightToFee::weight_to_fee(&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, - AssetId: Get, - AccountId, - Currency: CurrencyT, - OnUnbalanced: OnUnbalancedT, - > Drop - for UsingOnlySelfCurrencyComponents -{ - fn drop(&mut self) { - OnUnbalanced::on_unbalanced(Currency::issue(self.1)); - } -} - -pub struct XcmConfig; -impl Config for XcmConfig { - type Call = Call; - type XcmSender = XcmRouter; - // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = NativeAsset; - type IsTeleporter = (); // Teleportation is disabled - type LocationInverter = LocationInverter; - type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = - UsingOnlySelfCurrencyComponents, RelayLocation, AccountId, Balances, ()>; - type ResponseHandler = (); // Don't handle responses for now. - type SubscriptionService = PolkadotXcm; - - type AssetTrap = PolkadotXcm; - type AssetClaims = PolkadotXcm; -} - -// parameter_types! { -// pub const MaxDownwardMessageWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 10; -// } - -/// No local origins on this chain are allowed to dispatch XCM sends/executions. -pub type LocalOriginToLocation = (SignedToAccountId32,); - -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = ( - // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, -); - -impl pallet_evm_coder_substrate::Config for Runtime {} - -impl pallet_xcm::Config for Runtime { - type Event = Event; - type SendXcmOrigin = EnsureXcmOrigin; - type XcmRouter = XcmRouter; - type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Everything; - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; - type LocationInverter = LocationInverter; - type Origin = Origin; - type Call = Call; - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; -} - -impl cumulus_pallet_xcm::Config for Runtime { - type Event = Event; - type XcmExecutor = XcmExecutor; -} - -impl cumulus_pallet_xcmp_queue::Config for Runtime { - type WeightInfo = (); - type Event = Event; - type XcmExecutor = XcmExecutor; - type ChannelInfo = ParachainSystem; - type VersionWrapper = (); - type ExecuteOverweightOrigin = frame_system::EnsureRoot; - type ControllerOrigin = EnsureRoot; - type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; -} - -impl cumulus_pallet_dmp_queue::Config for Runtime { - type Event = Event; - type XcmExecutor = XcmExecutor; - type ExecuteOverweightOrigin = frame_system::EnsureRoot; -} - -impl pallet_aura::Config for Runtime { - type AuthorityId = AuraId; - type DisabledValidators = (); - type MaxAuthorities = MaxAuthorities; -} - -parameter_types! { - pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating(); - pub const CollectionCreationPrice: Balance = 2 * UNIQUE; -} - -impl pallet_common::Config for Runtime { - type WeightInfo = pallet_common::weights::SubstrateWeight; - type Event = Event; - type Currency = Balances; - type CollectionCreationPrice = CollectionCreationPrice; - type TreasuryAccountId = TreasuryAccountId; - type CollectionDispatch = CollectionDispatchT; - - type EvmTokenAddressMapping = EvmTokenAddressMapping; - type CrossTokenAddressMapping = CrossTokenAddressMapping; - type ContractAddress = EvmCollectionHelpersAddress; -} - -impl pallet_structure::Config for Runtime { - type Event = Event; - type Call = Call; - type WeightInfo = pallet_structure::weights::SubstrateWeight; -} - -impl pallet_fungible::Config for Runtime { - type WeightInfo = pallet_fungible::weights::SubstrateWeight; -} -impl pallet_refungible::Config for Runtime { - type WeightInfo = pallet_refungible::weights::SubstrateWeight; -} -impl pallet_nonfungible::Config for Runtime { - type WeightInfo = pallet_nonfungible::weights::SubstrateWeight; -} - -impl pallet_unique::Config for Runtime { - type Event = Event; - type WeightInfo = pallet_unique::weights::SubstrateWeight; - type CommonWeightInfo = CommonWeights; - type RefungibleExtensionsWeightInfo = CommonWeights; -} - -parameter_types! { - pub const InflationBlockInterval: BlockNumber = 100; // every time per how many blocks inflation is applied -} - -/// Used for the pallet inflation -impl pallet_inflation::Config for Runtime { - type Currency = Balances; - type TreasuryAccountId = TreasuryAccountId; - type InflationBlockInterval = InflationBlockInterval; - type BlockNumberProvider = RelayChainBlockNumberProvider; -} - -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) * - RuntimeBlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 50; -} - -type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment; -use frame_support::traits::NamedReservableCurrency; - -fn get_signed_extras(from: ::AccountId) -> SignedExtraScheduler { - ( - frame_system::CheckSpecVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(Era::Immortal), - frame_system::CheckNonce::::from(frame_system::Pallet::::account_nonce( - from, - )), - frame_system::CheckWeight::::new(), - // sponsoring transaction logic - // pallet_charge_transaction::ChargeTransactionPayment::::new(0), - ) -} - -pub struct SchedulerPaymentExecutor; -impl - DispatchCall for SchedulerPaymentExecutor -where - ::Call: Member - + Dispatchable - + SelfContainedCall - + GetDispatchInfo - + From>, - SelfContainedSignedInfo: Send + Sync + 'static, - Call: From<::Call> - + From<::Call> - + SelfContainedCall, - sp_runtime::AccountId32: From<::AccountId>, -{ - fn dispatch_call( - signer: ::AccountId, - call: ::Call, - ) -> Result< - Result>, - TransactionValidityError, - > { - let dispatch_info = call.get_dispatch_info(); - let extrinsic = fp_self_contained::CheckedExtrinsic::< - AccountId, - Call, - SignedExtraScheduler, - SelfContainedSignedInfo, - > { - signed: - CheckedSignature::::Signed( - signer.clone().into(), - get_signed_extras(signer.into()), - ), - function: call.into(), - }; - - extrinsic.apply::(&dispatch_info, 0) - } - - fn reserve_balance( - id: [u8; 16], - sponsor: ::AccountId, - call: ::Call, - count: u32, - ) -> Result<(), DispatchError> { - let dispatch_info = call.get_dispatch_info(); - let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0) - .saturating_mul(count.into()); - - >::reserve_named( - &id, - &(sponsor.into()), - weight, - ) - } - - fn pay_for_call( - id: [u8; 16], - sponsor: ::AccountId, - call: ::Call, - ) -> Result { - let dispatch_info = call.get_dispatch_info(); - let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0); - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - weight, - ), - ) - } - - fn cancel_reserve( - id: [u8; 16], - sponsor: ::AccountId, - ) -> Result { - Ok( - >::unreserve_named( - &id, - &(sponsor.into()), - u128::MAX, - ), - ) - } -} - -parameter_types! { - pub const NoPreimagePostponement: Option = Some(10); - pub const Preimage: Option = Some(10); -} - -/// Used the compare the privilege of an origin inside the scheduler. -pub struct OriginPrivilegeCmp; - -impl PrivilegeCmp for OriginPrivilegeCmp { - fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { - Some(Ordering::Equal) - } -} - -impl pallet_unique_scheduler::Config for Runtime { - type Event = Event; - type Origin = Origin; - type Currency = Balances; - type PalletsOrigin = OriginCaller; - type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureSigned; - type MaxScheduledPerBlock = MaxScheduledPerBlock; - type WeightInfo = (); - type CallExecutor = SchedulerPaymentExecutor; - type OriginPrivilegeCmp = OriginPrivilegeCmp; - type PreimageProvider = (); - type NoPreimagePostponement = NoPreimagePostponement; -} - -type EvmSponsorshipHandler = ( - UniqueEthSponsorshipHandler, - pallet_evm_contract_helpers::HelpersContractSponsoring, -); -type SponsorshipHandler = ( - UniqueSponsorshipHandler, - //pallet_contract_helpers::ContractSponsorshipHandler, - pallet_evm_transaction_payment::BridgeSponsorshipHandler, -); - -impl pallet_evm_transaction_payment::Config for Runtime { - type EvmSponsorshipHandler = EvmSponsorshipHandler; - type Currency = Balances; -} - -impl pallet_charge_transaction::Config for Runtime { - type SponsorshipHandler = SponsorshipHandler; -} - -// impl pallet_contract_helpers::Config for Runtime { -// type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; -// } - -parameter_types! { - // 0x842899ECF380553E8a4de75bF534cdf6fBF64049 - pub const HelpersContractAddress: H160 = H160([ - 0x84, 0x28, 0x99, 0xec, 0xf3, 0x80, 0x55, 0x3e, 0x8a, 0x4d, 0xe7, 0x5b, 0xf5, 0x34, 0xcd, 0xf6, 0xfb, 0xf6, 0x40, 0x49, - ]); - - // 0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f - pub const EvmCollectionHelpersAddress: H160 = H160([ - 0x6c, 0x4e, 0x9f, 0xe1, 0xae, 0x37, 0xa4, 0x1e, 0x93, 0xce, 0xe4, 0x29, 0xe8, 0xe1, 0x88, 0x1a, 0xbd, 0xcb, 0xb5, 0x4f, - ]); -} - -impl pallet_evm_contract_helpers::Config for Runtime { - type ContractAddress = HelpersContractAddress; - type DefaultSponsoringRateLimit = DefaultSponsoringRateLimit; -} - -construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = opaque::Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned} = 20, - ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21, - - Aura: pallet_aura::{Pallet, Config} = 22, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23, - - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 30, - RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 32, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage} = 33, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event} = 34, - Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event} = 35, - System: frame_system::{Pallet, Call, Storage, Config, Event} = 36, - Vesting: orml_vesting::{Pallet, Storage, Call, Event, Config} = 37, - // Vesting: pallet_vesting::{Pallet, Call, Config, Storage, Event} = 37, - // Contracts: pallet_contracts::{Pallet, Call, Storage, Event} = 38, - - // XCM helpers. - XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50, - PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin} = 51, - CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event, Origin} = 52, - DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 53, - - // Unique Pallets - Inflation: pallet_inflation::{Pallet, Call, Storage} = 60, - Unique: pallet_unique::{Pallet, Call, Storage, Event} = 61, - Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event} = 62, - // free = 63 - Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64, - // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65, - Common: pallet_common::{Pallet, Storage, Event} = 66, - Fungible: pallet_fungible::{Pallet, Storage} = 67, - Refungible: pallet_refungible::{Pallet, Storage} = 68, - Nonfungible: pallet_nonfungible::{Pallet, Storage} = 69, - Structure: pallet_structure::{Pallet, Call, Storage, Event} = 70, - - // Frontier - EVM: pallet_evm::{Pallet, Config, Call, Storage, Event} = 100, - Ethereum: pallet_ethereum::{Pallet, Config, Call, Storage, Event, Origin} = 101, - - EvmCoderSubstrate: pallet_evm_coder_substrate::{Pallet, Storage} = 150, - EvmContractHelpers: pallet_evm_contract_helpers::{Pallet, Storage} = 151, - EvmTransactionPayment: pallet_evm_transaction_payment::{Pallet} = 152, - EvmMigration: pallet_evm_migration::{Pallet, Call, Storage} = 153, - } -); - -pub struct TransactionConverter; - -impl fp_rpc::ConvertTransaction for TransactionConverter { - fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic { - UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), - ) - } -} - -impl fp_rpc::ConvertTransaction for TransactionConverter { - fn convert_transaction( - &self, - transaction: pallet_ethereum::Transaction, - ) -> opaque::UncheckedExtrinsic { - let extrinsic = UncheckedExtrinsic::new_unsigned( - pallet_ethereum::Call::::transact { transaction }.into(), - ); - let encoded = extrinsic.encode(); - opaque::UncheckedExtrinsic::decode(&mut &encoded[..]) - .expect("Encoded extrinsic is always valid") - } -} - -/// The address format for describing accounts. -pub type Address = sp_runtime::MultiAddress; -/// Block header type as expected by this runtime. -pub type Header = generic::Header; -/// Block type as expected by this runtime. -pub type Block = generic::Block; -/// A Block signed with a Justification -pub type SignedBlock = generic::SignedBlock; -/// BlockId type as expected by this runtime. -pub type BlockId = generic::BlockId; -/// The SignedExtension to the basic transaction logic. -pub type SignedExtra = ( - frame_system::CheckSpecVersion, - // system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_charge_transaction::ChargeTransactionPayment, - //pallet_contract_helpers::ContractHelpersExtension, - pallet_ethereum::FakeTransactionFinalizer, -); -pub type SignedExtraScheduler = ( - frame_system::CheckSpecVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - // pallet_charge_transaction::ChargeTransactionPayment, -); -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - fp_self_contained::UncheckedExtrinsic; -/// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic; -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsReversedWithSystemFirst, ->; - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -impl fp_self_contained::SelfContainedCall for Call { - type SignedInfo = H160; - - fn is_self_contained(&self) -> bool { - match self { - Call::Ethereum(call) => call.is_self_contained(), - _ => false, - } - } - - fn check_self_contained(&self) -> Option> { - match self { - Call::Ethereum(call) => call.check_self_contained(), - _ => None, - } - } - - fn validate_self_contained( - &self, - info: &Self::SignedInfo, - dispatch_info: &DispatchInfoOf, - len: usize, - ) -> Option { - match self { - Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len), - _ => None, - } - } - - fn pre_dispatch_self_contained( - &self, - info: &Self::SignedInfo, - ) -> Option> { - match self { - Call::Ethereum(call) => call.pre_dispatch_self_contained(info), - _ => None, - } - } - - fn apply_self_contained( - self, - info: Self::SignedInfo, - ) -> Option>> { - match self { - call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch( - Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)), - )), - _ => None, - } - } -} - -macro_rules! dispatch_unique_runtime { - ($collection:ident.$method:ident($($name:ident),*)) => {{ - let collection = ::CollectionDispatch::dispatch(>::try_get($collection)?); - let dispatch = collection.as_dyn(); - - Ok::<_, DispatchError>(dispatch.$method($($name),*)) - }}; -} - -impl_common_runtime_apis! { - #![custom_apis] - - impl rmrk_rpc::RmrkApi< - Block, - AccountId, - RmrkCollectionInfo, - RmrkInstanceInfo, - RmrkResourceInfo, - RmrkPropertyInfo, - RmrkBaseInfo, - RmrkPartType, - RmrkTheme - > for Runtime { - fn last_collection_idx() -> Result { - Ok(Default::default()) - } - - fn collection_by_id(_collection_id: RmrkCollectionId) -> Result>, DispatchError> { - Ok(Default::default()) - } - - fn nft_by_id(_collection_id: RmrkCollectionId, _nft_by_id: RmrkNftId) -> Result>, DispatchError> { - Ok(Default::default()) - } - - fn account_tokens(_account_id: AccountId, _collection_id: RmrkCollectionId) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn nft_children(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn collection_properties(_collection_id: RmrkCollectionId, _filter_keys: Option>) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn nft_properties(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _filter_keys: Option>) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn nft_resources(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn nft_resource_priority(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _resource_id: RmrkResourceId) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn base(_base_id: RmrkBaseId) -> Result>, DispatchError> { - Ok(Default::default()) - } - - fn base_parts(_base_id: RmrkBaseId) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn theme_names(_base_id: RmrkBaseId) -> Result, DispatchError> { - Ok(Default::default()) - } - - fn theme(_base_id: RmrkBaseId, _theme_name: RmrkThemeName, _filter_keys: Option>) -> Result, DispatchError> { - Ok(Default::default()) - } - } -} - -struct CheckInherents; - -impl cumulus_pallet_parachain_system::CheckInherents for CheckInherents { - fn check_inherents( - block: &Block, - relay_state_proof: &cumulus_pallet_parachain_system::RelayChainStateProof, - ) -> sp_inherents::CheckInherentsResult { - let relay_chain_slot = relay_state_proof - .read_slot() - .expect("Could not read the relay chain slot from the proof"); - - let inherent_data = - cumulus_primitives_timestamp::InherentDataProvider::from_relay_chain_slot_and_duration( - relay_chain_slot, - sp_std::time::Duration::from_secs(6), - ) - .create_inherent_data() - .expect("Could not create the timestamp inherent data"); - - inherent_data.check_extrinsics(block) - } -} +impl_common_runtime_apis!(); cumulus_pallet_parachain_system::register_validate_block!( Runtime = Runtime, --- a/tests/package.json +++ b/tests/package.json @@ -8,6 +8,7 @@ "@polkadot/typegen": "8.7.2-15", "@types/chai": "^4.3.1", "@types/chai-as-promised": "^7.1.5", + "@types/chai-like": "^1.1.1", "@types/mocha": "^9.1.1", "@types/node": "^17.0.35", "@typescript-eslint/eslint-plugin": "^5.26.0", @@ -96,6 +97,7 @@ "@polkadot/util-crypto": "9.4.1", "bignumber.js": "^9.0.2", "chai-as-promised": "^7.1.1", + "chai-like": "^1.1.1", "find-process": "^1.4.7", "solc": "0.8.14-fixed", "web3": "^1.7.3" --- a/tests/src/approve.test.ts +++ b/tests/src/approve.test.ts @@ -32,6 +32,8 @@ getCreatedCollectionCount, transferFromExpectSuccess, transferFromExpectFail, + requirePallets, + Pallets, } from './util/helpers'; chai.use(chaiAsPromised); @@ -49,34 +51,44 @@ }); }); - it('Execute the extrinsic and check approvedList', async () => { + it('[nft] Execute the extrinsic and check approvedList', async () => { const nftCollectionId = await createCollectionExpectSuccess(); - // nft const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address); - // fungible + }); + + it('[fungible] Execute the extrinsic and check approvedList', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address); - // reFungible + }); + + it('[refungible] Execute the extrinsic and check approvedList', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address); }); - it('Remove approval by using 0 amount', async () => { + it('[nft] Remove approval by using 0 amount', async () => { const nftCollectionId = await createCollectionExpectSuccess(); - // nft const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address, 1); await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address, 0); - // fungible + }); + + it('[fungible] Remove approval by using 0 amount', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 1); await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address, 0); - // reFungible + }); + + it('[refungible] Remove approval by using 0 amount', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); @@ -117,7 +129,9 @@ await approveExpectSuccess(collectionId, itemId, bob, charlie.address); }); - it('ReFungible up to an approved amount', async () => { + it('ReFungible up to an approved amount', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}}); const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address); await approveExpectSuccess(collectionId, itemId, bob, charlie.address); @@ -151,7 +165,9 @@ await transferFromExpectSuccess(collectionId, itemId, charlie, bob, alice, 1, 'Fungible'); }); - it('ReFungible up to an approved amount', async () => { + it('ReFungible up to an approved amount', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}}); const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address); await approveExpectSuccess(collectionId, itemId, bob, charlie.address); @@ -188,7 +204,9 @@ await transferFromExpectFail(collectionId, itemId, charlie, bob, alice, 1); }); - it('ReFungible up to an approved amount', async () => { + it('ReFungible up to an approved amount', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}}); const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', bob.address); await approveExpectSuccess(collectionId, itemId, bob, charlie.address); @@ -250,7 +268,9 @@ await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, bob, charlie, 1); }); - it('ReFungible', async () => { + it('ReFungible', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); @@ -285,7 +305,9 @@ await approveExpectFail(fungibleCollectionId, newFungibleTokenId, bob, charlie, 11); }); - it('ReFungible', async () => { + it('ReFungible', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, charlie, 101); @@ -325,7 +347,9 @@ await transferFromExpectSuccess(collectionId, itemId, bob, dave, alice, 1, 'Fungible'); }); - it('ReFungible up to an approved amount', async () => { + it('ReFungible up to an approved amount', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode:{type: 'ReFungible'}}); await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: true}); const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', charlie.address); @@ -422,63 +446,87 @@ }); }); - it('Approve for a collection that does not exist', async () => { + it('[nft] Approve for a collection that does not exist', async () => { await usingApi(async (api: ApiPromise) => { - // nft const nftCollectionCount = await getCreatedCollectionCount(api); await approveExpectFail(nftCollectionCount + 1, 1, alice, bob); - // fungible + }); + }); + + it('[fungible] Approve for a collection that does not exist', async () => { + await usingApi(async (api: ApiPromise) => { const fungibleCollectionCount = await getCreatedCollectionCount(api); await approveExpectFail(fungibleCollectionCount + 1, 0, alice, bob); - // reFungible + }); + }); + + it('[refungible] Approve for a collection that does not exist', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + await usingApi(async (api: ApiPromise) => { const reFungibleCollectionCount = await getCreatedCollectionCount(api); await approveExpectFail(reFungibleCollectionCount + 1, 1, alice, bob); }); }); - it('Approve for a collection that was destroyed', async () => { - // nft + it('[nft] Approve for a collection that was destroyed', async () => { const nftCollectionId = await createCollectionExpectSuccess(); await destroyCollectionExpectSuccess(nftCollectionId); await approveExpectFail(nftCollectionId, 1, alice, bob); - // fungible + }); + + it('Approve for a collection that was destroyed', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); await destroyCollectionExpectSuccess(fungibleCollectionId); await approveExpectFail(fungibleCollectionId, 0, alice, bob); - // reFungible + }); + + it('[refungible] Approve for a collection that was destroyed', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await destroyCollectionExpectSuccess(reFungibleCollectionId); await approveExpectFail(reFungibleCollectionId, 1, alice, bob); }); - it('Approve transfer of a token that does not exist', async () => { - // nft + it('[nft] Approve transfer of a token that does not exist', async () => { const nftCollectionId = await createCollectionExpectSuccess(); await approveExpectFail(nftCollectionId, 2, alice, bob); - // reFungible + }); + + it('[refungible] Approve transfer of a token that does not exist', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await approveExpectFail(reFungibleCollectionId, 2, alice, bob); }); - it('Approve using the address that does not own the approved token', async () => { + it('[nft] Approve using the address that does not own the approved token', async () => { const nftCollectionId = await createCollectionExpectSuccess(); - // nft const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); await approveExpectFail(nftCollectionId, newNftTokenId, bob, alice); - // fungible + }); + + it('[fungible] Approve using the address that does not own the approved token', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); await approveExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice); - // reFungible + }); + + it('[refungible] Approve using the address that does not own the approved token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, bob, alice); }); - it('should fail if approved more ReFungibles than owned', async () => { + it('should fail if approved more ReFungibles than owned', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const nftCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'ReFungible'); await transferExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 100, 'ReFungible'); --- a/tests/src/burnItem.test.ts +++ b/tests/src/burnItem.test.ts @@ -25,6 +25,8 @@ getBalance, setCollectionLimitsExpectSuccess, isTokenExists, + requirePallets, + Pallets, } from './util/helpers'; import chai from 'chai'; @@ -80,7 +82,9 @@ }); }); - it('Burn item in ReFungible collection', async () => { + it('Burn item in ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const createMode = 'ReFungible'; const collectionId = await createCollectionExpectSuccess({mode: {type: createMode}}); const tokenId = await createItemExpectSuccess(alice, collectionId, createMode); @@ -99,7 +103,9 @@ }); }); - it('Burn owned portion of item in ReFungible collection', async () => { + it('Burn owned portion of item in ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const createMode = 'ReFungible'; const collectionId = await createCollectionExpectSuccess({mode: {type: createMode}}); const tokenId = await createItemExpectSuccess(alice, collectionId, createMode); @@ -189,7 +195,9 @@ }); // TODO: burnFrom - it('Burn item in ReFungible collection', async () => { + it('Burn item in ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const createMode = 'ReFungible'; const collectionId = await createCollectionExpectSuccess({mode: {type: createMode}}); await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: true}); --- a/tests/src/confirmSponsorship.test.ts +++ b/tests/src/confirmSponsorship.test.ts @@ -33,6 +33,8 @@ addCollectionAdminExpectSuccess, getCreatedCollectionCount, UNIQUE, + requirePallets, + Pallets, } from './util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; @@ -124,7 +126,9 @@ }); }); - it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => { + it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await setCollectionSponsorExpectSuccess(collectionId, bob.address); await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); @@ -252,7 +256,9 @@ }); }); - it('ReFungible: Sponsoring is rate limited', async () => { + it('ReFungible: Sponsoring is rate limited', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await setCollectionSponsorExpectSuccess(collectionId, bob.address); await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); --- a/tests/src/createCollection.test.ts +++ b/tests/src/createCollection.test.ts @@ -16,7 +16,7 @@ import {expect} from 'chai'; import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api'; -import {createCollectionWithPropsExpectFailure, createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo, createCollectionWithPropsExpectSuccess} from './util/helpers'; +import {createCollectionWithPropsExpectFailure, createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo, createCollectionWithPropsExpectSuccess, requirePallets, Pallets} from './util/helpers'; describe('integration test: ext. createCollection():', () => { it('Create new NFT collection', async () => { @@ -34,7 +34,9 @@ it('Create new Fungible collection', async () => { await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); }); - it('Create new ReFungible collection', async () => { + it('Create new ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); }); --- a/tests/src/createItem.test.ts +++ b/tests/src/createItem.test.ts @@ -29,6 +29,8 @@ itApi, normalizeAccountId, getCreateItemResult, + requirePallets, + Pallets, } from './util/helpers'; const expect = chai.expect; @@ -79,7 +81,9 @@ } }); - it('Create new item in ReFungible collection', async () => { + it('Create new item in ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const createMode = 'ReFungible'; const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}}); await createItemExpectSuccess(alice, newCollectionID, createMode); @@ -96,7 +100,9 @@ await addCollectionAdminExpectSuccess(alice, newCollectionID, bob.address); await createItemExpectSuccess(bob, newCollectionID, createMode); }); - it('Create new item in ReFungible collection with collection admin permissions', async () => { + it('Create new item in ReFungible collection with collection admin permissions', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const createMode = 'ReFungible'; const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}}); await addCollectionAdminExpectSuccess(alice, newCollectionID, bob.address); @@ -175,7 +181,9 @@ }); }); - it('Check total pieces of ReFungible token', async () => { + it('Check total pieces of ReFungible token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const createMode = 'ReFungible'; const createCollectionResult = await createCollection(api, alice, {mode: {type: createMode}}); @@ -219,7 +227,9 @@ const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode, decimalPoints: 0}}); await expect(createItemExpectSuccess(bob, newCollectionID, createMode)).to.be.rejected; }); - it('Regular user cannot create new item in ReFungible collection', async () => { + it('Regular user cannot create new item in ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const createMode = 'ReFungible'; const newCollectionID = await createCollectionExpectSuccess({mode: {type: createMode}}); await expect(createItemExpectSuccess(bob, newCollectionID, createMode)).to.be.rejected; @@ -296,7 +306,9 @@ }); }); - it('Check total pieces for invalid Refungible token', async () => { + it('Check total pieces for invalid Refungible token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}}); const collectionId = createCollectionResult.collectionId; --- a/tests/src/createMultipleItems.test.ts +++ b/tests/src/createMultipleItems.test.ts @@ -33,6 +33,9 @@ createCollectionWithPropsExpectSuccess, createMultipleItemsWithPropsExpectSuccess, getTokenProperties, + requirePallets, + Pallets, + checkPalletsPresence, } from './util/helpers'; chai.use(chaiAsPromised); @@ -91,7 +94,9 @@ }); }); - it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async () => { + it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api, privateKeyWrapper) => { const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const itemsListIndexBefore = await getLastTokenId(api, collectionId); @@ -272,7 +277,9 @@ }); }); - it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async () => { + it('Create 0x31, 0x32, 0x33 items in active ReFungible collection and verify tokens data in chain', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api: ApiPromise) => { const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const itemsListIndexBefore = await getLastTokenId(api, collectionId); @@ -335,7 +342,9 @@ }); }); - it('Regular user cannot create items in active ReFungible collection', async () => { + it('Regular user cannot create items in active ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api: ApiPromise) => { const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const itemsListIndexBefore = await getLastTokenId(api, collectionId); @@ -358,9 +367,8 @@ }); }); - it('Create NFT and Re-fungible tokens that has reached the maximum data limit', async () => { + it('Create NFTs that has reached the maximum data limit', async function() { await usingApi(async (api, privateKeyWrapper) => { - // NFT const collectionId = await createCollectionWithPropsExpectSuccess({ propPerm: [{key: 'key', permission: {mutable: true, collectionAdmin: true, tokenOwner: true}}], }); @@ -372,8 +380,13 @@ ]; const createMultipleItemsTx = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), args); await expect(submitTransactionExpectFailAsync(alice, createMultipleItemsTx)).to.be.rejected; + }); + }); - // ReFungible + it('Create Refungible tokens that has reached the maximum data limit', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + await usingApi(async api => { const collectionIdReFungible = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); { @@ -402,8 +415,15 @@ it('Create tokens with different types', async () => { await usingApi(async (api: ApiPromise) => { const collectionId = await createCollectionExpectSuccess(); + + const types = ['NFT', 'Fungible']; + + if (await checkPalletsPresence([Pallets.ReFungible])) { + types.push('ReFungible'); + } + const createMultipleItemsTx = api.tx.unique - .createMultipleItems(collectionId, normalizeAccountId(alice.address), ['NFT', 'Fungible', 'ReFungible']); + .createMultipleItems(collectionId, normalizeAccountId(alice.address), types); await expect(executeTransaction(api, alice, createMultipleItemsTx)).to.be.rejectedWith(/nonfungible\.NotNonfungibleDataUsedToMintFungibleCollectionToken/); // garbage collection :-D // lol await destroyCollectionExpectSuccess(collectionId); --- a/tests/src/createMultipleItemsEx.test.ts +++ b/tests/src/createMultipleItemsEx.test.ts @@ -16,9 +16,9 @@ import {expect} from 'chai'; import usingApi, {executeTransaction} from './substrate/substrate-api'; -import {addCollectionAdminExpectSuccess, createCollectionExpectSuccess, createCollectionWithPropsExpectSuccess, getBalance, getLastTokenId, getTokenProperties} from './util/helpers'; +import {addCollectionAdminExpectSuccess, createCollectionExpectSuccess, createCollectionWithPropsExpectSuccess, getBalance, getLastTokenId, getTokenProperties, requirePallets, Pallets} from './util/helpers'; -describe.only('Integration Test: createMultipleItemsEx', () => { +describe('Integration Test: createMultipleItemsEx', () => { it('can initialize multiple NFT with different owners', async () => { const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await usingApi(async (api, privateKeyWrapper) => { @@ -146,7 +146,9 @@ }); }); - it('can initialize an RFT with multiple owners', async () => { + it('can initialize an RFT with multiple owners', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api, privateKeyWrapper) => { const alice = privateKeyWrapper('//Alice'); const bob = privateKeyWrapper('//Bob'); @@ -178,7 +180,9 @@ }); }); - it('can initialize multiple RFTs with the same owner', async () => { + it('can initialize multiple RFTs with the same owner', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api, privateKeyWrapper) => { const alice = privateKeyWrapper('//Alice'); const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); @@ -386,27 +390,6 @@ const tokens = await api.query.nonfungible.tokenData.entries(collection); const json = tokens.map(([, token]) => token.toJSON()); expect(json).to.be.deep.equal(data); - }); - }); - - it('fails when trying to set multiple owners when creating multiple refungibles', async () => { - const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - // Polkadot requires map, and yet requires keys to be JSON encoded - const users = new Map(); - users.set(JSON.stringify({substrate: alice.address}), 1); - users.set(JSON.stringify({substrate: bob.address}), 1); - - // TODO: better error message? - await expect(executeTransaction(api, alice, api.tx.unique.createMultipleItemsEx(collection, { - RefungibleMultipleItems: [ - {users}, - {users}, - ], - }))).to.be.rejectedWith(/^refungible\.NotRefungibleDataUsedToMintFungibleCollectionToken$/); }); }); }); --- a/tests/src/destroyCollection.test.ts +++ b/tests/src/destroyCollection.test.ts @@ -25,6 +25,8 @@ addCollectionAdminExpectSuccess, getCreatedCollectionCount, createItemExpectSuccess, + requirePallets, + Pallets, } from './util/helpers'; chai.use(chaiAsPromised); @@ -38,7 +40,9 @@ const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); await destroyCollectionExpectSuccess(collectionId); }); - it('ReFungible collection can be destroyed', async () => { + it('ReFungible collection can be destroyed', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await destroyCollectionExpectSuccess(collectionId); }); --- a/tests/src/eth/api/CollectionHelpers.sol +++ b/tests/src/eth/api/CollectionHelpers.sol @@ -48,7 +48,7 @@ string memory name, string memory description, string memory tokenPrefix - ) external view returns (address); + ) external returns (address); // Selector: createERC721MetadataCompatibleRFTCollection(string,string,string,string) a5596388 function createERC721MetadataCompatibleRFTCollection( --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -22,13 +22,7 @@ ); } -// Selector: 79cc6790 -interface ERC20UniqueExtensions is Dummy, ERC165 { - // Selector: burnFrom(address,uint256) 79cc6790 - function burnFrom(address from, uint256 amount) external returns (bool); -} - -// Selector: 7d9262e6 +// Selector: 6cf113cd interface Collection is Dummy, ERC165 { // Set collection property. // @@ -174,6 +168,27 @@ // // Selector: setCollectionMintMode(bool) 00018e84 function setCollectionMintMode(bool mode) external; + + // Check that account is the owner or admin of the collection + // + // @param user account to verify + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin(address) c2282493 + function verifyOwnerOrAdmin(address user) external view returns (bool); + + // Returns collection type + // + // @return `Fungible` or `NFT` or `ReFungible` + // + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() external returns (string memory); +} + +// Selector: 79cc6790 +interface ERC20UniqueExtensions is Dummy, ERC165 { + // Selector: burnFrom(address,uint256) 79cc6790 + function burnFrom(address from, uint256 amount) external returns (bool); } // Selector: 942e8b22 --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -250,33 +250,7 @@ function finishMinting() external returns (bool); } -// Selector: 780e9d63 -interface ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid NFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) - // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) external view returns (uint256); - - // @dev Not implemented - // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - external - view - returns (uint256); - - // @notice Count NFTs tracked by this contract - // @return A count of valid NFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() external view returns (uint256); -} - -// Selector: 7d9262e6 +// Selector: 6cf113cd interface Collection is Dummy, ERC165 { // Set collection property. // @@ -422,6 +396,47 @@ // // Selector: setCollectionMintMode(bool) 00018e84 function setCollectionMintMode(bool mode) external; + + // Check that account is the owner or admin of the collection + // + // @param user account to verify + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin(address) c2282493 + function verifyOwnerOrAdmin(address user) external view returns (bool); + + // Returns collection type + // + // @return `Fungible` or `NFT` or `ReFungible` + // + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() external returns (string memory); +} + +// Selector: 780e9d63 +interface ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid NFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) external view returns (uint256); + + // @dev Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + external + view + returns (uint256); + + // @notice Count NFTs tracked by this contract + // @return A count of valid NFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() external view returns (uint256); } // Selector: d74d154f --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -248,33 +248,7 @@ function finishMinting() external returns (bool); } -// Selector: 780e9d63 -interface ERC721Enumerable is Dummy, ERC165 { - // @notice Enumerate valid RFTs - // @param index A counter less than `totalSupply()` - // @return The token identifier for the `index`th NFT, - // (sort order not specified) - // - // Selector: tokenByIndex(uint256) 4f6ccce7 - function tokenByIndex(uint256 index) external view returns (uint256); - - // Not implemented - // - // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 - function tokenOfOwnerByIndex(address owner, uint256 index) - external - view - returns (uint256); - - // @notice Count RFTs tracked by this contract - // @return A count of valid RFTs tracked by this contract, where each one of - // them has an assigned and queryable owner not equal to the zero address - // - // Selector: totalSupply() 18160ddd - function totalSupply() external view returns (uint256); -} - -// Selector: 7d9262e6 +// Selector: 6cf113cd interface Collection is Dummy, ERC165 { // Set collection property. // @@ -420,9 +394,50 @@ // // Selector: setCollectionMintMode(bool) 00018e84 function setCollectionMintMode(bool mode) external; + + // Check that account is the owner or admin of the collection + // + // @param user account to verify + // @return "true" if account is the owner or admin + // + // Selector: verifyOwnerOrAdmin(address) c2282493 + function verifyOwnerOrAdmin(address user) external view returns (bool); + + // Returns collection type + // + // @return `Fungible` or `NFT` or `ReFungible` + // + // Selector: uniqueCollectionType() d34b55b8 + function uniqueCollectionType() external returns (string memory); +} + +// Selector: 780e9d63 +interface ERC721Enumerable is Dummy, ERC165 { + // @notice Enumerate valid RFTs + // @param index A counter less than `totalSupply()` + // @return The token identifier for the `index`th NFT, + // (sort order not specified) + // + // Selector: tokenByIndex(uint256) 4f6ccce7 + function tokenByIndex(uint256 index) external view returns (uint256); + + // Not implemented + // + // Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59 + function tokenOfOwnerByIndex(address owner, uint256 index) + external + view + returns (uint256); + + // @notice Count RFTs tracked by this contract + // @return A count of valid RFTs tracked by this contract, where each one of + // them has an assigned and queryable owner not equal to the zero address + // + // Selector: totalSupply() 18160ddd + function totalSupply() external view returns (uint256); } -// Selector: d74d154f +// Selector: 7c3bef89 interface ERC721UniqueExtensions is Dummy, ERC165 { // @notice Transfer ownership of an RFT // @dev Throws unless `msg.sender` is the current owner. Throws if `to` @@ -473,6 +488,16 @@ function mintBulkWithTokenURI(address to, Tuple0[] memory tokens) external returns (bool); + + // Returns EVM address for refungible token + // + // @param token ID of the token + // + // Selector: tokenContractAddress(uint256) ab76fac6 + function tokenContractAddress(uint256 token) + external + view + returns (address); } interface UniqueRefungible is --- a/tests/src/eth/api/UniqueRefungibleToken.sol +++ b/tests/src/eth/api/UniqueRefungibleToken.sol @@ -22,6 +22,23 @@ ); } +// Selector: 042f1106 +interface ERC1633UniqueExtensions is Dummy, ERC165 { + // Selector: setParentNFT(address,uint256) 042f1106 + function setParentNFT(address collection, uint256 nftId) + external + returns (bool); +} + +// Selector: 5755c3f2 +interface ERC1633 is Dummy, ERC165 { + // Selector: parentToken() 80a54001 + function parentToken() external view returns (address); + + // Selector: parentTokenId() d7f083f3 + function parentTokenId() external view returns (uint256); +} + // Selector: 942e8b22 interface ERC20 is Dummy, ERC165, ERC20Events { // @return the name of the token. @@ -115,5 +132,7 @@ Dummy, ERC165, ERC20, - ERC20UniqueExtensions + ERC20UniqueExtensions, + ERC1633, + ERC1633UniqueExtensions {} --- a/tests/src/eth/collectionAdmin.test.ts +++ b/tests/src/eth/collectionAdmin.test.ts @@ -59,6 +59,22 @@ expect(adminList[0].asSubstrate.toString().toLocaleLowerCase()) .to.be.eq(newAdmin.address.toLocaleLowerCase()); }); + + itWeb3('Verify owner or admin', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const collectionHelper = evmCollectionHelpers(web3, owner); + + const result = await collectionHelper.methods + .createNonfungibleCollection('A', 'B', 'C') + .send(); + const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); + + const newAdmin = createEthAccount(web3); + const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + expect(await collectionEvm.methods.verifyOwnerOrAdmin(newAdmin).call()).to.be.false; + await collectionEvm.methods.addCollectionAdmin(newAdmin).send(); + expect(await collectionEvm.methods.verifyOwnerOrAdmin(newAdmin).call()).to.be.true; + }); itWeb3('(!negative tests!) Add admin by ADMIN is not allowed', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); --- a/tests/src/eth/collectionHelpersAbi.json +++ b/tests/src/eth/collectionHelpersAbi.json @@ -61,7 +61,7 @@ ], "name": "createRefungibleCollection", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", + "stateMutability": "nonpayable", "type": "function" }, { --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -16,7 +16,7 @@ import {evmToAddress} from '@polkadot/util-crypto'; import {expect} from 'chai'; -import {getCreatedCollectionCount, getDetailedCollectionInfo} from '../util/helpers'; +import {getCreatedCollectionCount, getDetailedCollectionInfo, requirePallets, Pallets} from '../util/helpers'; import { evmCollectionHelpers, collectionIdToAddress, @@ -28,6 +28,10 @@ } from './util/helpers'; describe('Create RFT collection from EVM', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('Create collection', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const collectionHelper = evmCollectionHelpers(web3, owner); @@ -146,6 +150,10 @@ }); describe('(!negative tests!) Create RFT collection from EVM', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('(!negative test!) Create collection (bad lengths)', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, owner); @@ -228,4 +236,4 @@ .setCollectionLimit('badLimit', 'true') .call()).to.be.rejectedWith('unknown boolean limit "badLimit"'); }); -}); \ No newline at end of file +}); --- /dev/null +++ b/tests/src/eth/fractionalizer/Fractionalizer.bin @@ -0,0 +1 @@ +60c0604052600a60805269526546756e6769626c6560b01b60a0527fcdb72fd4e1d0d6d4eebd7ab142113ec2b4b06ddb24324db5c287ef01ab484d6b60055534801561004a57600080fd5b50600480546001600160a01b0319163317905561137a8061006c6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063115091401461005c5780631b191ea214610071578063d470e60f14610084578063dbc38ad214610097578063eb292412146100aa575b600080fd5b61006f61006a366004610f85565b6100bd565b005b61006f61007f366004610ff2565b61035b565b61006f61009236600461108c565b6104c0565b61006f6100a53660046110b8565b61089d565b61006f6100b8366004611114565b610ee0565b6004546001600160a01b031633146100f05760405162461bcd60e51b81526004016100e79061114d565b60405180910390fd5b6000546001600160a01b0316156101495760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b60008190506000816001600160a01b031663d34b55b86040518163ffffffff1660e01b81526004016000604051808303816000875af1158015610190573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101b8919081019061118b565b905060055481805190602001201461022f5760405162461bcd60e51b815260206004820152603460248201527f57726f6e6720636f6c6c656374696f6e20747970652e20436f6c6c656374696f604482015273371034b9903737ba103932b33ab733b4b136329760611b60648201526084016100e7565b816001600160a01b03166304a460536040518163ffffffff1660e01b81526004016020604051808303816000875af115801561026f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610293919061125b565b6103055760405162461bcd60e51b815260206004820152603c60248201527f4672616374696f6e616c697a657220636f6e74726163742073686f756c64206260448201527f6520616e2061646d696e206f662074686520636f6c6c656374696f6e0000000060648201526084016100e7565b600080546001600160a01b0319166001600160a01b0385169081179091556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef9060200160405180910390a1505050565b6004546001600160a01b031633146103855760405162461bcd60e51b81526004016100e79061114d565b6000546001600160a01b0316156103de5760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b6040516344a68ad560e01b8152736c4e9fe1ae37a41e93cee429e8e1881abdcbb54f9081906344a68ad590610421908a908a908a908a908a908a906004016112a1565b6020604051808303816000875af1158015610440573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061046491906112ea565b600080546001600160a01b0319166001600160a01b039290921691821790556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef906020015b60405180910390a150505050505050565b6000546001600160a01b03166105145760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b6000546001600160a01b038381169116146105685760405162461bcd60e51b81526020600482015260146024820152732bb937b7339029232a1031b7b63632b1ba34b7b760611b60448201526064016100e7565b600080546040516355bb7d6360e11b8152600481018490526001600160a01b039091169190829063ab76fac690602401602060405180830381865afa1580156105b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d991906112ea565b6001600160a01b03808216600090815260036020908152604091829020825180840190935280549093168083526001909301549082015291925061065f5760405162461bcd60e51b815260206004820181905260248201527f4e6f20636f72726573706f6e64696e67204e465420746f6b656e20666f756e6460448201526064016100e7565b6000829050806001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c69190611307565b6040516370a0823160e01b81523360048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561070a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072e9190611307565b1461078a5760405162461bcd60e51b815260206004820152602660248201527f4e6f7420616c6c2070696563657320617265206f776e6564206279207468652060448201526531b0b63632b960d11b60648201526084016100e7565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd906107ba90339030908a90600401611320565b600060405180830381600087803b1580156107d457600080fd5b505af11580156107e8573d6000803e3d6000fd5b5050835160208501516040516323b872dd60e01b81526001600160a01b0390921693506323b872dd92506108229130913391600401611320565b600060405180830381600087803b15801561083c57600080fd5b505af1158015610850573d6000803e3d6000fd5b5050835160208501516040517fe9e9808d24ff79ccc3b1ecf48be7b2d11591adccc452150d0d7947cb48eb0d53945061088d935087929190611320565b60405180910390a1505050505050565b6000546001600160a01b03166108f15760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b600080546001600160a01b0385811683526001602081905260409093205491169160ff90911615151461098c5760405162461bcd60e51b815260206004820152603c60248201527f4672616374696f6e616c697a6174696f6e206f66207468697320636f6c6c656360448201527f74696f6e206973206e6f7420616c6c6f7765642062792061646d696e0000000060648201526084016100e7565b6040516331a9108f60e11b81526004810184905233906001600160a01b03861690636352211e90602401602060405180830381865afa1580156109d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f791906112ea565b6001600160a01b031614610a5d5760405162461bcd60e51b815260206004820152602760248201527f4f6e6c7920746f6b656e206f776e657220636f756c64206672616374696f6e616044820152661b1a5e99481a5d60ca1b60648201526084016100e7565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd90610a8d90339030908890600401611320565b600060405180830381600087803b158015610aa757600080fd5b505af1158015610abb573d6000803e3d6000fd5b505050506001600160a01b0384166000908152600260209081526040808320868452909152812054819081908103610d0757836001600160a01b03166375794a3c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b4f9190611307565b6040516340c10f1960e01b8152306004820152602481018290529093506001600160a01b038516906340c10f19906044016020604051808303816000875af1158015610b9f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bc3919061125b565b506040516355bb7d6360e11b8152600481018490526001600160a01b0385169063ab76fac690602401602060405180830381865afa158015610c09573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c2d91906112ea565b6001600160a01b0388811660008181526002602090815260408083208c84528252808320899055805180820182528481528083018d8152878716808652600390945293829020905181546001600160a01b0319169616959095178555915160019094019390935551630217888360e11b81526004810191909152602481018990529193508392509063042f1106906044016020604051808303816000875af1158015610cdd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d01919061125b565b50610d98565b6001600160a01b0387811660009081526002602090815260408083208a8452909152908190205490516355bb7d6360e11b8152600481018290529094509085169063ab76fac690602401602060405180830381865afa158015610d6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9291906112ea565b91508190505b60405163d2418ca760e01b81526001600160801b03861660048201526001600160a01b0382169063d2418ca7906024016020604051808303816000875af1158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b919061125b565b5060405163a9059cbb60e01b81523360048201526001600160801b03861660248201526001600160a01b0382169063a9059cbb906044016020604051808303816000875af1158015610e61573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e85919061125b565b50604080516001600160a01b03808a168252602082018990528416918101919091526001600160801b03861660608201527f29f372538523984f33874da1b50e596ce0f180a6eb04d7ff22bb2ce80e1576b6906080016104af565b6004546001600160a01b03163314610f0a5760405162461bcd60e51b81526004016100e79061114d565b6001600160a01b038216600081815260016020908152604091829020805460ff19168515159081179091558251938452908301527f6dad0aed33f4b7f07095619b668698e17943fd9f4c83e7cfcc7f6dd880a11588910160405180910390a15050565b6001600160a01b0381168114610f8257600080fd5b50565b600060208284031215610f9757600080fd5b8135610fa281610f6d565b9392505050565b60008083601f840112610fbb57600080fd5b50813567ffffffffffffffff811115610fd357600080fd5b602083019150836020828501011115610feb57600080fd5b9250929050565b6000806000806000806060878903121561100b57600080fd5b863567ffffffffffffffff8082111561102357600080fd5b61102f8a838b01610fa9565b9098509650602089013591508082111561104857600080fd5b6110548a838b01610fa9565b9096509450604089013591508082111561106d57600080fd5b5061107a89828a01610fa9565b979a9699509497509295939492505050565b6000806040838503121561109f57600080fd5b82356110aa81610f6d565b946020939093013593505050565b6000806000606084860312156110cd57600080fd5b83356110d881610f6d565b92506020840135915060408401356001600160801b03811681146110fb57600080fd5b809150509250925092565b8015158114610f8257600080fd5b6000806040838503121561112757600080fd5b823561113281610f6d565b9150602083013561114281611106565b809150509250929050565b6020808252600e908201526d27b7363c9037bbb732b91031b0b760911b604082015260600190565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561119e57600080fd5b825167ffffffffffffffff808211156111b657600080fd5b818501915085601f8301126111ca57600080fd5b8151818111156111dc576111dc611175565b604051601f8201601f19908116603f0116810190838211818310171561120457611204611175565b81604052828152888684870101111561121c57600080fd5b600093505b8284101561123e5784840186015181850187015292850192611221565b8284111561124f5760008684830101525b98975050505050505050565b60006020828403121561126d57600080fd5b8151610fa281611106565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6060815260006112b560608301888a611278565b82810360208401526112c8818789611278565b905082810360408401526112dd818587611278565b9998505050505050505050565b6000602082840312156112fc57600080fd5b8151610fa281610f6d565b60006020828403121561131957600080fd5b5051919050565b6001600160a01b03938416815291909216602082015260408101919091526060019056fea2646970667358221220a118fe8c3b83b3933baa7bfe084ed165a014ec509367252e5c99e1a3332d000364736f6c634300080f0033 \ No newline at end of file --- /dev/null +++ b/tests/src/eth/fractionalizer/Fractionalizer.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: Apache License +pragma solidity >=0.8.0; +import {CollectionHelpers} from "../api/CollectionHelpers.sol"; +import {ContractHelpers} from "../api/ContractHelpers.sol"; +import {UniqueRefungibleToken} from "../api/UniqueRefungibleToken.sol"; +import {UniqueRefungible} from "../api/UniqueRefungible.sol"; +import {UniqueNFT} from "../api/UniqueNFT.sol"; + +/// @dev Fractionalization contract. It stores mappings between NFT and RFT tokens, +/// stores allowlist of NFT tokens available for fractionalization, has methods +/// for fractionalization and defractionalization of NFT tokens. +contract Fractionalizer { + struct Token { + address _collection; + uint256 _tokenId; + } + address rftCollection; + mapping(address => bool) nftCollectionAllowList; + mapping(address => mapping(uint256 => uint256)) nft2rftMapping; + mapping(address => Token) rft2nftMapping; + bytes32 refungibleCollectionType = keccak256(bytes("ReFungible")); + + receive() external payable onlyOwner {} + + /// @dev Method modifier to only allow contract owner to call it. + modifier onlyOwner() { + address contracthelpersAddress = 0x842899ECF380553E8a4de75bF534cdf6fBF64049; + ContractHelpers contractHelpers = ContractHelpers(contracthelpersAddress); + address contractOwner = contractHelpers.contractOwner(address(this)); + require(msg.sender == contractOwner, "Only owner can"); + _; + } + + /// @dev This emits when RFT collection setting is changed. + event RFTCollectionSet(address _collection); + + /// @dev This emits when NFT collection is allowed or disallowed. + event AllowListSet(address _collection, bool _status); + + /// @dev This emits when NFT token is fractionalized by contract. + event Fractionalized(address _collection, uint256 _tokenId, address _rftToken, uint128 _amount); + + /// @dev This emits when NFT token is defractionalized by contract. + event Defractionalized(address _rftToken, address _nftCollection, uint256 _nftTokenId); + + /// Set RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens + /// would be created in this collection. + /// @dev Throws if RFT collection is already configured for this contract. + /// Throws if collection of wrong type (NFT, Fungible) is provided instead + /// of RFT collection. + /// Throws if `msg.sender` is not owner or admin of provided RFT collection. + /// Can only be called by contract owner. + /// @param _collection address of RFT collection. + function setRFTCollection(address _collection) public onlyOwner { + require( + rftCollection == address(0), + "RFT collection is already set" + ); + UniqueRefungible refungibleContract = UniqueRefungible(_collection); + string memory collectionType = refungibleContract.uniqueCollectionType(); + + require( + keccak256(bytes(collectionType)) == refungibleCollectionType, + "Wrong collection type. Collection is not refungible." + ); + require( + refungibleContract.verifyOwnerOrAdmin(address(this)), + "Fractionalizer contract should be an admin of the collection" + ); + rftCollection = _collection; + emit RFTCollectionSet(rftCollection); + } + + /// Creates and sets RFT collection that contract will work with. RFT tokens for fractionalized NFT tokens + /// would be created in this collection. + /// @dev Throws if RFT collection is already configured for this contract. + /// Can only be called by contract owner. + /// @param _name name for created RFT collection. + /// @param _description description for created RFT collection. + /// @param _tokenPrefix token prefix for created RFT collection. + function createAndSetRFTCollection(string calldata _name, string calldata _description, string calldata _tokenPrefix) public onlyOwner { + require( + rftCollection == address(0), + "RFT collection is already set" + ); + address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F; + rftCollection = CollectionHelpers(collectionHelpers).createRefungibleCollection(_name, _description, _tokenPrefix); + emit RFTCollectionSet(rftCollection); + } + + /// Allow or disallow NFT collection tokens from being fractionalized by this contract. + /// @dev Can only be called by contract owner. + /// @param collection NFT token address. + /// @param status `true` to allow and `false` to disallow NFT token. + function setNftCollectionIsAllowed(address collection, bool status) public onlyOwner { + nftCollectionAllowList[collection] = status; + emit AllowListSet(collection, status); + } + + /// Fractionilize NFT token. + /// @dev Takes NFT token from `msg.sender` and transfers RFT token to `msg.sender` + /// instead. Creates new RFT token if provided NFT token never was fractionalized + /// by this contract or existing RFT token if it was. + /// Throws if RFT collection isn't configured for this contract. + /// Throws if fractionalization of provided NFT token is not allowed + /// Throws if `msg.sender` is not owner of provided NFT token + /// @param _collection NFT collection address + /// @param _token id of NFT token to be fractionalized + /// @param _pieces number of pieces new RFT token would have + function nft2rft(address _collection, uint256 _token, uint128 _pieces) public { + require( + rftCollection != address(0), + "RFT collection is not set" + ); + UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection); + require( + nftCollectionAllowList[_collection] == true, + "Fractionalization of this collection is not allowed by admin" + ); + require( + UniqueNFT(_collection).ownerOf(_token) == msg.sender, + "Only token owner could fractionalize it" + ); + UniqueNFT(_collection).transferFrom( + msg.sender, + address(this), + _token + ); + uint256 rftTokenId; + address rftTokenAddress; + UniqueRefungibleToken rftTokenContract; + if (nft2rftMapping[_collection][_token] == 0) { + rftTokenId = rftCollectionContract.nextTokenId(); + rftCollectionContract.mint(address(this), rftTokenId); + rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId); + nft2rftMapping[_collection][_token] = rftTokenId; + rft2nftMapping[rftTokenAddress] = Token(_collection, _token); + + rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + rftTokenContract.setParentNFT(_collection, _token); + } else { + rftTokenId = nft2rftMapping[_collection][_token]; + rftTokenAddress = rftCollectionContract.tokenContractAddress(rftTokenId); + rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + } + rftTokenContract.repartition(_pieces); + rftTokenContract.transfer(msg.sender, _pieces); + emit Fractionalized(_collection, _token, rftTokenAddress, _pieces); + } + + /// Defrationalize NFT token. + /// @dev Takes RFT token from `msg.sender` and transfers corresponding NFT token + /// to `msg.sender` instead. + /// Throws if RFT collection isn't configured for this contract. + /// Throws if provided RFT token is no from configured RFT collection. + /// Throws if RFT token was not created by this contract. + /// Throws if `msg.sender` isn't owner of all RFT token pieces. + /// @param _collection RFT collection address + /// @param _token id of RFT token + function rft2nft(address _collection, uint256 _token) public { + require( + rftCollection != address(0), + "RFT collection is not set" + ); + require( + rftCollection == _collection, + "Wrong RFT collection" + ); + UniqueRefungible rftCollectionContract = UniqueRefungible(rftCollection); + address rftTokenAddress = rftCollectionContract.tokenContractAddress(_token); + Token memory nftToken = rft2nftMapping[rftTokenAddress]; + require( + nftToken._collection != address(0), + "No corresponding NFT token found" + ); + UniqueRefungibleToken rftTokenContract = UniqueRefungibleToken(rftTokenAddress); + require( + rftTokenContract.balanceOf(msg.sender) == rftTokenContract.totalSupply(), + "Not all pieces are owned by the caller" + ); + rftCollectionContract.transferFrom(msg.sender, address(this), _token); + UniqueNFT(nftToken._collection).transferFrom( + address(this), + msg.sender, + nftToken._tokenId + ); + emit Defractionalized(rftTokenAddress, nftToken._collection, nftToken._tokenId); + } +} \ No newline at end of file --- /dev/null +++ b/tests/src/eth/fractionalizer/FractionalizerAbi.json @@ -0,0 +1,142 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_collection", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "AllowListSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_rftToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "_nftCollection", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_nftTokenId", + "type": "uint256" + } + ], + "name": "Defractionalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_collection", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "_rftToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "_amount", + "type": "uint128" + } + ], + "name": "Fractionalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "_collection", + "type": "address" + } + ], + "name": "RFTCollectionSet", + "type": "event" + }, + { + "inputs": [ + { "internalType": "string", "name": "_name", "type": "string" }, + { "internalType": "string", "name": "_description", "type": "string" }, + { "internalType": "string", "name": "_tokenPrefix", "type": "string" } + ], + "name": "createAndSetRFTCollection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_collection", "type": "address" }, + { "internalType": "uint256", "name": "_token", "type": "uint256" }, + { "internalType": "uint128", "name": "_pieces", "type": "uint128" } + ], + "name": "nft2rft", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_collection", "type": "address" }, + { "internalType": "uint256", "name": "_token", "type": "uint256" } + ], + "name": "rft2nft", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "collection", "type": "address" }, + { "internalType": "bool", "name": "status", "type": "bool" } + ], + "name": "setNftCollectionIsAllowed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "_collection", "type": "address" } + ], + "name": "setRFTCollection", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] --- /dev/null +++ b/tests/src/eth/fractionalizer/fractionalizer.test.ts @@ -0,0 +1,470 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + + +import Web3 from 'web3'; +import {ApiPromise} from '@polkadot/api'; +import {evmToAddress} from '@polkadot/util-crypto'; +import {readFile} from 'fs/promises'; +import {executeTransaction, submitTransactionAsync} from '../../substrate/substrate-api'; +import {getCreateCollectionResult, getCreateItemResult, UNIQUE} from '../../util/helpers'; +import {collectionIdToAddress, CompiledContract, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, GAS_ARGS, itWeb3, tokenIdFromAddress, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from '../util/helpers'; +import {Contract} from 'web3-eth-contract'; +import * as solc from 'solc'; + +import chai from 'chai'; +import chaiLike from 'chai-like'; +import {IKeyringPair} from '@polkadot/types/types'; +chai.use(chaiLike); +const expect = chai.expect; +let fractionalizer: CompiledContract; + +async function compileFractionalizer() { + if (!fractionalizer) { + const input = { + language: 'Solidity', + sources: { + ['Fractionalizer.sol']: { + content: (await readFile(`${__dirname}/Fractionalizer.sol`)).toString(), + }, + }, + settings: { + outputSelection: { + '*': { + '*': ['*'], + }, + }, + }, + }; + const json = JSON.parse(solc.compile(JSON.stringify(input), {import: await findImports()})); + const out = json.contracts['Fractionalizer.sol']['Fractionalizer']; + + fractionalizer = { + abi: out.abi, + object: '0x' + out.evm.bytecode.object, + }; + } + return fractionalizer; +} + +async function findImports() { + const collectionHelpers = (await readFile(`${__dirname}/../api/CollectionHelpers.sol`)).toString(); + const contractHelpers = (await readFile(`${__dirname}/../api/ContractHelpers.sol`)).toString(); + const uniqueRefungibleToken = (await readFile(`${__dirname}/../api/UniqueRefungibleToken.sol`)).toString(); + const uniqueRefungible = (await readFile(`${__dirname}/../api/UniqueRefungible.sol`)).toString(); + const uniqueNFT = (await readFile(`${__dirname}/../api/UniqueNFT.sol`)).toString(); + + return function(path: string) { + switch (path) { + case 'api/CollectionHelpers.sol': return {contents: `${collectionHelpers}`}; + case 'api/ContractHelpers.sol': return {contents: `${contractHelpers}`}; + case 'api/UniqueRefungibleToken.sol': return {contents: `${uniqueRefungibleToken}`}; + case 'api/UniqueRefungible.sol': return {contents: `${uniqueRefungible}`}; + case 'api/UniqueNFT.sol': return {contents: `${uniqueNFT}`}; + default: return {error: 'File not found'}; + } + }; +} + +async function deployFractionalizer(web3: Web3, owner: string) { + const compiled = await compileFractionalizer(); + const fractionalizerContract = new web3.eth.Contract(compiled.abi, undefined, { + data: compiled.object, + from: owner, + ...GAS_ARGS, + }); + return await fractionalizerContract.deploy({data: compiled.object}).send({from: owner}); +} + +async function initFractionalizer(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair, owner: string) { + const fractionalizer = await deployFractionalizer(web3, owner); + const amount = 10n * UNIQUE; + await web3.eth.sendTransaction({from: owner, to: fractionalizer.options.address, value: `${amount}`, ...GAS_ARGS}); + const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send(); + const rftCollectionAddress = result.events.RFTCollectionSet.returnValues._collection; + return {fractionalizer, rftCollectionAddress}; +} + +async function createRFTToken(api: ApiPromise, web3: Web3, owner: string, fractionalizer: Contract, amount: bigint) { + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send(); + await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send(); + const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, amount).send(); + const {_collection, _tokenId, _rftToken} = result.events.Fractionalized.returnValues; + return { + nftCollectionAddress: _collection, + nftTokenId: _tokenId, + rftTokenAddress: _rftToken, + }; +} + +describe('Fractionalizer contract usage', () => { + itWeb3('Set RFT collection', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const fractionalizer = await deployFractionalizer(web3, owner); + const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner); + const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); + await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send(); + const result = await fractionalizer.methods.setRFTCollection(collectionIdAddress).send(); + expect(result.events).to.be.like({ + RFTCollectionSet: { + returnValues: { + _collection: collectionIdAddress, + }, + }, + }); + }); + + itWeb3('Mint RFT collection', async ({api, web3, privateKeyWrapper}) => { + const alice = privateKeyWrapper('//Alice'); + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const fractionalizer = await deployFractionalizer(web3, owner); + const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE); + await submitTransactionAsync(alice, tx); + + const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send({from: owner}); + expect(result.events).to.be.like({ + RFTCollectionSet: {}, + }); + expect(result.events.RFTCollectionSet.returnValues._collection).to.be.ok; + }); + + itWeb3('Set Allowlist', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const result1 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send({from: owner}); + expect(result1.events).to.be.like({ + AllowListSet: { + returnValues: { + _collection: nftCollectionAddress, + _status: true, + }, + }, + }); + const result2 = await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, false).send({from: owner}); + expect(result2.events).to.be.like({ + AllowListSet: { + returnValues: { + _collection: nftCollectionAddress, + _status: false, + }, + }, + }); + }); + + itWeb3('NFT to RFT', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + + await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send(); + await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send(); + const result = await fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).send(); + expect(result.events).to.be.like({ + Fractionalized: { + returnValues: { + _collection: nftCollectionAddress, + _tokenId: nftTokenId, + _amount: '100', + }, + }, + }); + const rftTokenAddress = result.events.Fractionalized.returnValues._rftToken; + const rftTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + expect(await rftTokenContract.methods.balanceOf(owner).call()).to.equal('100'); + }); + + itWeb3('RFT to NFT', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + const {rftTokenAddress, nftCollectionAddress, nftTokenId} = await createRFTToken(api, web3, owner, fractionalizer, 100n); + + const {collectionId, tokenId} = tokenIdFromAddress(rftTokenAddress); + const refungibleAddress = collectionIdToAddress(collectionId); + expect(rftCollectionAddress).to.be.equal(refungibleAddress); + const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + await refungibleTokenContract.methods.approve(fractionalizer.options.address, 100).send(); + const result = await fractionalizer.methods.rft2nft(refungibleAddress, tokenId).send(); + expect(result.events).to.be.like({ + Defractionalized: { + returnValues: { + _rftToken: rftTokenAddress, + _nftCollection: nftCollectionAddress, + _nftTokenId: nftTokenId, + }, + }, + }); + }); +}); + + + +describe('Negative Integration Tests for fractionalizer', () => { + itWeb3('call setRFTCollection twice', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner); + const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); + + const fractionalizer = await deployFractionalizer(web3, owner); + await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send(); + await fractionalizer.methods.setRFTCollection(collectionIdAddress).send(); + + await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call()) + .to.be.rejectedWith(/RFT collection is already set$/g); + }); + + itWeb3('call setRFTCollection with NFT collection', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const {collectionIdAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, collectionIdAddress, owner); + + const fractionalizer = await deployFractionalizer(web3, owner); + await nftContract.methods.addCollectionAdmin(fractionalizer.options.address).send(); + + await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call()) + .to.be.rejectedWith(/Wrong collection type. Collection is not refungible.$/g); + }); + + itWeb3('call setRFTCollection while not collection admin', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const fractionalizer = await deployFractionalizer(web3, owner); + const {collectionIdAddress} = await createRefungibleCollection(api, web3, owner); + + await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call()) + .to.be.rejectedWith(/Fractionalizer contract should be an admin of the collection$/g); + }); + + itWeb3('call setRFTCollection after createAndSetRFTCollection', async ({api, web3, privateKeyWrapper}) => { + const alice = privateKeyWrapper('//Alice'); + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const fractionalizer = await deployFractionalizer(web3, owner); + const tx = api.tx.balances.transfer(evmToAddress(fractionalizer.options.address), 10n * UNIQUE); + await submitTransactionAsync(alice, tx); + + const result = await fractionalizer.methods.createAndSetRFTCollection('A', 'B', 'C').send({from: owner}); + const collectionIdAddress = result.events.RFTCollectionSet.returnValues._collection; + + await expect(fractionalizer.methods.setRFTCollection(collectionIdAddress).call()) + .to.be.rejectedWith(/RFT collection is already set$/g); + }); + + itWeb3('call nft2rft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + const fractionalizer = await deployFractionalizer(web3, owner); + + await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call()) + .to.be.rejectedWith(/RFT collection is not set$/g); + }); + + itWeb3('call nft2rft while not owner of NFT token', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const nftOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + await nftContract.methods.transfer(nftOwner, 1).send(); + + + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send(); + + await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call()) + .to.be.rejectedWith(/Only token owner could fractionalize it$/g); + }); + + itWeb3('call nft2rft while not in list of allowed accounts', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + + await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send(); + await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call()) + .to.be.rejectedWith(/Fractionalization of this collection is not allowed by admin$/g); + }); + + itWeb3('call nft2rft while fractionalizer doesnt have approval for nft token', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + + await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send(); + await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call()) + .to.be.rejectedWith(/ApprovedValueTooLow$/g); + }); + + itWeb3('call rft2nft without setting RFT collection for contract', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const fractionalizer = await deployFractionalizer(web3, owner); + const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner); + const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner); + const rftTokenId = await refungibleContract.methods.nextTokenId().call(); + await refungibleContract.methods.mint(owner, rftTokenId).send(); + + await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call()) + .to.be.rejectedWith(/RFT collection is not set$/g); + }); + + itWeb3('call rft2nft for RFT token that is not from configured RFT collection', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner); + const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner); + const rftTokenId = await refungibleContract.methods.nextTokenId().call(); + await refungibleContract.methods.mint(owner, rftTokenId).send(); + + await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call()) + .to.be.rejectedWith(/Wrong RFT collection$/g); + }); + + itWeb3('call rft2nft for RFT token that was not minted by fractionalizer contract', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const {collectionIdAddress: rftCollectionAddress} = await createRefungibleCollection(api, web3, owner); + + const fractionalizer = await deployFractionalizer(web3, owner); + const refungibleContract = uniqueRefungible(web3, rftCollectionAddress, owner); + + await refungibleContract.methods.addCollectionAdmin(fractionalizer.options.address).send(); + await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send(); + + const rftTokenId = await refungibleContract.methods.nextTokenId().call(); + await refungibleContract.methods.mint(owner, rftTokenId).send(); + + await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, rftTokenId).call()) + .to.be.rejectedWith(/No corresponding NFT token found$/g); + }); + + itWeb3('call rft2nft without owning all RFT pieces', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {fractionalizer, rftCollectionAddress} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + const {rftTokenAddress} = await createRFTToken(api, web3, owner, fractionalizer, 100n); + + const {tokenId} = tokenIdFromAddress(rftTokenAddress); + const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + await refungibleTokenContract.methods.transfer(receiver, 50).send(); + await refungibleTokenContract.methods.approve(fractionalizer.options.address, 50).send(); + await expect(fractionalizer.methods.rft2nft(rftCollectionAddress, tokenId).call()) + .to.be.rejectedWith(/Not all pieces are owned by the caller$/g); + }); + + itWeb3('send QTZ/UNQ to contract from non owner', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const payer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const fractionalizer = await deployFractionalizer(web3, owner); + const amount = 10n * UNIQUE; + await expect(web3.eth.sendTransaction({from: payer, to: fractionalizer.options.address, value: `${amount}`, ...GAS_ARGS})).to.be.rejected; + }); + + itWeb3('fractionalize NFT with NFT transfers disallowed', async ({api, web3, privateKeyWrapper}) => { + const alice = privateKeyWrapper('//Alice'); + let collectionId; + { + const tx = api.tx.unique.createCollectionEx({name: 'A', description: 'B', tokenPrefix: 'C', mode: 'NFT'}); + const events = await submitTransactionAsync(alice, tx); + const result = getCreateCollectionResult(events); + collectionId = result.collectionId; + } + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + let nftTokenId; + { + const createData = {nft: {}}; + const tx = api.tx.unique.createItem(collectionId, {Ethereum: owner}, createData as any); + const events = await executeTransaction(api, alice, tx); + const result = getCreateItemResult(events); + nftTokenId = result.itemId; + } + { + const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, false); + await executeTransaction(api, alice, tx); + } + const nftCollectionAddress = collectionIdToAddress(collectionId); + const {fractionalizer} = await initFractionalizer(api, web3, privateKeyWrapper, owner); + await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send(); + + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send(); + await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100).call()) + .to.be.rejectedWith(/TransferNotAllowed$/g); + }); + + itWeb3('fractionalize NFT with RFT transfers disallowed', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const alice = privateKeyWrapper('//Alice'); + + let collectionId; + { + const tx = api.tx.unique.createCollectionEx({name: 'A', description: 'B', tokenPrefix: 'C', mode: 'ReFungible'}); + const events = await submitTransactionAsync(alice, tx); + const result = getCreateCollectionResult(events); + collectionId = result.collectionId; + } + const rftCollectionAddress = collectionIdToAddress(collectionId); + const fractionalizer = await deployFractionalizer(web3, owner); + { + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, {Ethereum: fractionalizer.options.address}); + await submitTransactionAsync(alice, changeAdminTx); + } + await fractionalizer.methods.setRFTCollection(rftCollectionAddress).send(); + { + const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, false); + await executeTransaction(api, alice, tx); + } + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + + await fractionalizer.methods.setNftCollectionIsAllowed(nftCollectionAddress, true).send(); + await nftContract.methods.approve(fractionalizer.options.address, nftTokenId).send(); + + await expect(fractionalizer.methods.nft2rft(nftCollectionAddress, nftTokenId, 100n).call()) + .to.be.rejectedWith(/TransferNotAllowed$/g); + }); +}); \ No newline at end of file --- a/tests/src/eth/fungibleAbi.json +++ b/tests/src/eth/fungibleAbi.json @@ -301,5 +301,21 @@ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "uniqueCollectionType", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "verifyOwnerOrAdmin", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" } ] --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -526,5 +526,21 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "uniqueCollectionType", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "verifyOwnerOrAdmin", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" } ] --- a/tests/src/eth/reFungible.test.ts +++ b/tests/src/eth/reFungible.test.ts @@ -14,12 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {createCollectionExpectSuccess, UNIQUE} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, tokenIdToAddress} from './util/helpers'; -import reFungibleTokenAbi from './reFungibleTokenAbi.json'; +import {createCollectionExpectSuccess, UNIQUE, requirePallets, Pallets} from '../util/helpers'; +import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, tokenIdToAddress, uniqueRefungibleToken} from './util/helpers'; import {expect} from 'chai'; describe('Refungible: Information getting', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); @@ -84,7 +87,7 @@ await contract.methods.mint(caller, tokenId).send(); const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS}); + const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); @@ -108,7 +111,7 @@ await contract.methods.mint(caller, tokenId).send(); const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS}); + const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); @@ -120,6 +123,10 @@ }); describe('Refungible: Plain calls', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, owner); @@ -250,7 +257,7 @@ await contract.methods.mint(caller, tokenId).send(); const address = tokenIdToAddress(collectionId, tokenId); - const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS}); + const tokenContract = uniqueRefungibleToken(web3, address, caller); await tokenContract.methods.repartition(15).send(); { @@ -269,7 +276,7 @@ }, ]); }); - + expect(erc20Events).to.include.deep.members([ { address, @@ -345,12 +352,12 @@ await contract.methods.mint(caller, tokenId).send(); const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS}); + const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); - const events = await recordEvents(contract, async () => + const events = await recordEvents(contract, async () => await tokenContract.methods.transfer(receiver, 1).send()); expect(events).to.deep.equal([ { @@ -377,13 +384,13 @@ await contract.methods.mint(caller, tokenId).send(); const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, tokenAddress, {from: caller, ...GAS_ARGS}); + const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); await tokenContract.methods.repartition(2).send(); - - const events = await recordEvents(contract, async () => + + const events = await recordEvents(contract, async () => await tokenContract.methods.transfer(receiver, 1).send()); - + expect(events).to.deep.equal([ { address: collectionIdAddress, @@ -399,6 +406,10 @@ }); describe('RFT: Fees', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, caller); @@ -435,6 +446,10 @@ }); describe('Common metadata', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => { const collection = await createCollectionExpectSuccess({ name: 'token name', @@ -462,4 +477,4 @@ expect(symbol).to.equal('TOK'); }); -}); \ No newline at end of file +}); --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -482,6 +482,15 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "token", "type": "uint256" } + ], + "name": "tokenContractAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "uint256", "name": "index", "type": "uint256" } ], @@ -526,5 +535,21 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "uniqueCollectionType", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "verifyOwnerOrAdmin", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" } ] --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -14,9 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth} from './util/helpers'; -import reFungibleTokenAbi from './reFungibleTokenAbi.json'; +import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE, requirePallets, Pallets} from '../util/helpers'; +import {collectionIdFromAddress, collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from './util/helpers'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; @@ -24,6 +23,10 @@ const expect = chai.expect; describe('Refungible token: Information getting', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => { const alice = privateKeyWrapper('//Alice'); @@ -34,7 +37,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, caller); const totalSupply = await contract.methods.totalSupply().call(); expect(totalSupply).to.equal('200'); @@ -50,7 +53,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, caller); const balance = await contract.methods.balanceOf(caller).call(); expect(balance).to.equal('200'); @@ -66,7 +69,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, caller); const decimals = await contract.methods.decimals().call(); expect(decimals).to.equal('0'); @@ -75,6 +78,10 @@ // FIXME: Need erc721 for ReFubgible. describe('Check ERC721 token URI for ReFungible', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const helper = evmCollectionHelpers(web3, owner); @@ -82,7 +89,7 @@ const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const receiver = createEthAccount(web3); const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); - + const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); result = await contract.methods.mint( @@ -115,17 +122,17 @@ const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const receiver = createEthAccount(web3); const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); - + const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); result = await contract.methods.mint( receiver, nextTokenId, ).send(); - + // Set URL await contract.methods.setProperty(nextTokenId, 'url', Buffer.from('Token URI')).send(); - + const events = normalizeEvents(result.events); const address = collectionIdToAddress(collectionId); @@ -151,14 +158,14 @@ const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const receiver = createEthAccount(web3); const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); - + const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); result = await contract.methods.mint( receiver, nextTokenId, ).send(); - + const events = normalizeEvents(result.events); const address = collectionIdToAddress(collectionId); @@ -184,14 +191,14 @@ const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); const receiver = createEthAccount(web3); const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); - + const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); result = await contract.methods.mint( receiver, nextTokenId, ).send(); - + // Set suffix const suffix = '/some/suffix'; await contract.methods.setProperty(nextTokenId, 'suffix', Buffer.from(suffix)).send(); @@ -216,6 +223,10 @@ }); describe('Refungible: Plain calls', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => { const alice = privateKeyWrapper('//Alice'); @@ -229,7 +240,7 @@ const spender = createEthAccount(web3); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); { const result = await contract.methods.approve(spender, 100).send({from: owner}); @@ -270,7 +281,7 @@ const receiver = createEthAccount(web3); const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); await contract.methods.approve(spender, 100).send(); @@ -324,7 +335,7 @@ await transferBalanceToEth(api, alice, receiver); const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); { const result = await contract.methods.transfer(receiver, 50).send({from: owner}); @@ -367,14 +378,14 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); await contract.methods.repartition(200).send({from: owner}); expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(200); await contract.methods.transfer(receiver, 110).send({from: owner}); expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(90); expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(110); - + await expect(contract.methods.repartition(80).send({from: owner})).to.eventually.be.rejected; await contract.methods.transfer(receiver, 90).send({from: owner}); @@ -397,7 +408,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); const result = await contract.methods.repartition(200).send(); const events = normalizeEvents(result.events); @@ -426,7 +437,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); const result = await contract.methods.repartition(50).send(); const events = normalizeEvents(result.events); @@ -456,11 +467,11 @@ const address = tokenIdToAddress(collectionId, tokenId); - const tokenContract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: caller, ...GAS_ARGS}); + const tokenContract = uniqueRefungibleToken(web3, address, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); - const events = await recordEvents(contract, async () => + const events = await recordEvents(contract, async () => await tokenContract.methods.burnFrom(caller, 1).send()); expect(events).to.deep.equal([ { @@ -477,6 +488,10 @@ }); describe('Refungible: Fees', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { const alice = privateKeyWrapper('//Alice'); @@ -488,7 +503,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, 100).send({from: owner})); expect(cost < BigInt(0.2 * Number(UNIQUE))); @@ -505,7 +520,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); await contract.methods.approve(spender, 100).send({from: owner}); @@ -524,7 +539,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = uniqueRefungibleToken(web3, address, owner); const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, 100).send({from: owner})); expect(cost < BigInt(0.2 * Number(UNIQUE))); @@ -532,6 +547,10 @@ }); describe('Refungible: Substrate calls', () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + }); + itWeb3('Events emitted for approve()', async ({web3, api, privateKeyWrapper}) => { const alice = privateKeyWrapper('//Alice'); @@ -542,7 +561,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address); + const contract = uniqueRefungibleToken(web3, address); const events = await recordEvents(contract, async () => { expect(await approve(api, collectionId, tokenId, alice, {Ethereum: receiver}, 100n)).to.be.true; @@ -573,7 +592,7 @@ expect(await approve(api, collectionId, tokenId, alice, bob.address, 100n)).to.be.true; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address); + const contract = uniqueRefungibleToken(web3, address); const events = await recordEvents(contract, async () => { expect(await transferFrom(api, collectionId, tokenId, bob, alice, {Ethereum: receiver}, 51n)).to.be.true; @@ -611,7 +630,7 @@ const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId; const address = tokenIdToAddress(collectionId, tokenId); - const contract = new web3.eth.Contract(reFungibleTokenAbi as any, address); + const contract = uniqueRefungibleToken(web3, address); const events = await recordEvents(contract, async () => { expect(await transfer(api, collectionId, tokenId, alice, {Ethereum: receiver}, 51n)).to.be.true; @@ -630,3 +649,47 @@ ]); }); }); + +describe('ERC 1633 implementation', () => { + itWeb3('Parent NFT token address and id', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress: nftCollectionAddress} = await createNonfungibleCollection(api, web3, owner); + const nftContract = uniqueNFT(web3, nftCollectionAddress, owner); + const nftTokenId = await nftContract.methods.nextTokenId().call(); + await nftContract.methods.mint(owner, nftTokenId).send(); + const nftCollectionId = collectionIdFromAddress(nftCollectionAddress); + + const {collectionIdAddress, collectionId} = await createRefungibleCollection(api, web3, owner); + const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); + const refungibleTokenId = await refungibleContract.methods.nextTokenId().call(); + await refungibleContract.methods.mint(owner, refungibleTokenId).send(); + + const rftTokenAddress = tokenIdToAddress(collectionId, refungibleTokenId); + const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + await refungibleTokenContract.methods.setParentNFT(nftCollectionAddress, nftTokenId).send(); + + const tokenAddress = await refungibleTokenContract.methods.parentToken().call(); + const tokenId = await refungibleTokenContract.methods.parentTokenId().call(); + const nftTokenAddress = tokenIdToAddress(nftCollectionId, nftTokenId); + expect(tokenAddress).to.be.equal(nftTokenAddress); + expect(tokenId).to.be.equal(nftTokenId); + }); + + itWeb3('Default parent token address and id', async ({api, web3, privateKeyWrapper}) => { + const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + + const {collectionIdAddress, collectionId} = await createRefungibleCollection(api, web3, owner); + const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); + const refungibleTokenId = await refungibleContract.methods.nextTokenId().call(); + await refungibleContract.methods.mint(owner, refungibleTokenId).send(); + + const rftTokenAddress = tokenIdToAddress(collectionId, refungibleTokenId); + const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + + const tokenAddress = await refungibleTokenContract.methods.parentToken().call(); + const tokenId = await refungibleTokenContract.methods.parentTokenId().call(); + expect(tokenAddress).to.be.equal(rftTokenAddress); + expect(tokenId).to.be.equal(refungibleTokenId); + }); +}); --- a/tests/src/eth/reFungibleTokenAbi.json +++ b/tests/src/eth/reFungibleTokenAbi.json @@ -103,6 +103,20 @@ "type": "function" }, { + "inputs": [], + "name": "parentToken", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "parentTokenId", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" } ], @@ -113,6 +127,16 @@ }, { "inputs": [ + { "internalType": "address", "name": "collection", "type": "address" }, + { "internalType": "uint256", "name": "nftId", "type": "uint256" } + ], + "name": "setParentNFT", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ { "internalType": "bytes4", "name": "interfaceID", "type": "bytes4" } ], "name": "supportsInterface", --- a/tests/src/eth/scheduling.test.ts +++ b/tests/src/eth/scheduling.test.ts @@ -16,9 +16,13 @@ import {expect} from 'chai'; import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers'; -import {scheduleExpectSuccess, waitNewBlocks} from '../util/helpers'; +import {scheduleExpectSuccess, waitNewBlocks, requirePallets, Pallets} from '../util/helpers'; describe('Scheduing EVM smart contracts', () => { + before(async function() { + await requirePallets(this, [Pallets.Scheduler]); + }); + itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3, privateKeyWrapper}) => { const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const flipper = await deployFlipper(web3, deployer); @@ -51,4 +55,4 @@ expect(await flipper.methods.getValue().call()).to.be.equal(initialValue); } }); -}); \ No newline at end of file +}); --- a/tests/src/eth/util/helpers.ts +++ b/tests/src/eth/util/helpers.ts @@ -32,6 +32,7 @@ import fungibleAbi from '../fungibleAbi.json'; import nonFungibleAbi from '../nonFungibleAbi.json'; import refungibleAbi from '../reFungibleAbi.json'; +import refungibleTokenAbi from '../reFungibleTokenAbi.json'; import contractHelpersAbi from './contractHelpersAbi.json'; export const GAS_ARGS = {gas: 2500000}; @@ -101,6 +102,18 @@ ]); return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); } + +export function tokenIdFromAddress(address: string) { + if (!address.startsWith('0x')) + throw 'address not starts with "0x"'; + if (address.length > 42) + throw 'address length is more than 20 bytes'; + return { + collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)), + tokenId: Number('0x' + address.substring(address.length - 8)), + }; +} + export function tokenIdToCross(collection: number, token: number): CrossAccountId { return { Ethereum: tokenIdToAddress(collection, token), @@ -128,6 +141,44 @@ expect(result.success).to.be.true; } +export async function createRefungibleCollection(api: ApiPromise, web3: Web3, owner: string) { + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createRefungibleCollection('A', 'B', 'C') + .send(); + return await getCollectionAddressFromResult(api, result); +} + + +export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) { + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createNonfungibleCollection('A', 'B', 'C') + .send(); + return await getCollectionAddressFromResult(api, result); +} + +export function uniqueNFT(web3: Web3, address: string, owner: string) { + return new web3.eth.Contract(nonFungibleAbi as any, address, { + from: owner, + ...GAS_ARGS, + }); +} + +export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) { + return new web3.eth.Contract(refungibleAbi as any, collectionAddress, { + from: owner, + ...GAS_ARGS, + }); +} + +export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) { + return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, { + from: owner, + ...GAS_ARGS, + }); +} + export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { let i: any = it; if (opts.only) i = i.only; @@ -199,7 +250,12 @@ return Web3.utils.toChecksumAddress(subToEthLowercase(eth)); } -export function compileContract(name: string, src: string) { +export interface CompiledContract { + abi: any, + object: string, +} + +export function compileContract(name: string, src: string) : CompiledContract { const out = JSON.parse(solc.compile(JSON.stringify({ language: 'Solidity', sources: { --- a/tests/src/fungible.test.ts +++ b/tests/src/fungible.test.ts @@ -14,25 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {default as usingApi} from './substrate/substrate-api'; import {IKeyringPair} from '@polkadot/types/types'; -import { - getBalance, - createMultipleItemsExpectSuccess, - isTokenExists, - getLastTokenId, - getAllowance, - approve, - transferFrom, - createCollection, - transfer, - burnItem, - normalizeAccountId, - CrossAccountId, - createFungibleItemExpectSuccess, - U128_MAX, - burnFromExpectSuccess, -} from './util/helpers'; +import {U128_MAX} from './util/helpers'; + +import {usingPlaygrounds} from './util/playgrounds'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; @@ -44,141 +29,137 @@ describe('integration test: Fungible functionality:', () => { before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); }); }); it('Create fungible collection and token', async () => { - await usingApi(async api => { - const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}}); - expect(createCollectionResult.success).to.be.true; - const collectionId = createCollectionResult.collectionId; - const defaultTokenId = await getLastTokenId(api, collectionId); - const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address); - const aliceBalance = await getBalance(api, collectionId, alice, aliceTokenId); - const itemCountAfter = await getLastTokenId(api, collectionId); - - // What to expect - // tslint:disable-next-line:no-unused-expression + await usingPlaygrounds(async helper => { + const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'trest'}); + const defaultTokenId = await collection.getLastTokenId(); + expect(defaultTokenId).to.be.equal(0); + + await collection.mint(alice, {Substrate: alice.address}, U128_MAX); + const aliceBalance = await collection.getBalance({Substrate: alice.address}); + const itemCountAfter = await collection.getLastTokenId(); + expect(itemCountAfter).to.be.equal(defaultTokenId); expect(aliceBalance).to.be.equal(U128_MAX); }); }); it('RPC method tokenOnewrs for fungible collection and token', async () => { - await usingApi(async (api, privateKeyWrapper) => { + await usingPlaygrounds(async (helper, privateKey) => { const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; - const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString()))); + const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address})); + + const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + + await collection.mint(alice, {Substrate: alice.address}, U128_MAX); + + await collection.transfer(alice, {Substrate: bob.address}, 1000n); + await collection.transfer(alice, ethAcc, 900n); - const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}}); - const collectionId = createCollectionResult.collectionId; - const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address); - - await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n); - await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n); - for (let i = 0; i < 7; i++) { - await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1); + await collection.transfer(alice, facelessCrowd[i], 1n); } - - const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId); - const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s)); - const aliceID = normalizeAccountId(alice); - const bobId = normalizeAccountId(bob); + const owners = await collection.getTop10Owners(); + // What to expect - // tslint:disable-next-line:no-unused-expression - expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]); - expect(owners.length == 10).to.be.true; + expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]); + expect(owners.length).to.be.equal(10); - const eleven = privateKeyWrapper('11'); - expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true; - expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10); + const eleven = privateKey('//ALice+11'); + expect(await collection.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true; + expect((await collection.getTop10Owners()).length).to.be.equal(10); }); }); it('Transfer token', async () => { - await usingApi(async api => { + await usingPlaygrounds(async helper => { const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; - const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId; - const tokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address); + const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + await collection.mint(alice, {Substrate: alice.address}, 500n); - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n); - expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; - expect(await transfer(api, collectionId, tokenId, alice, ethAcc, 140n)).to.be.true; + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(500n); + expect(await collection.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true; + expect(await collection.transfer(alice, ethAcc, 140n)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(300n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n); - expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(140n); - await expect(transfer(api, collectionId, tokenId, alice, bob, 350n)).to.eventually.be.rejected; + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(300n); + expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(60n); + expect(await collection.getBalance(ethAcc)).to.be.equal(140n); + + await expect(collection.transfer(alice, {Substrate: bob.address}, 350n)).to.eventually.be.rejected; }); }); it('Tokens multiple creation', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId; - - const args = [ - {Fungible: {Value: 500n}}, - {Fungible: {Value: 400n}}, - {Fungible: {Value: 300n}}, - ]; - - await createMultipleItemsExpectSuccess(alice, collectionId, args); - expect(await getBalance(api, collectionId, alice, 0)).to.be.equal(1200n); - }); + await usingPlaygrounds(async helper => { + const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + + await collection.mintWithOneOwner(alice, {Substrate: alice.address}, [ + {value: 500n}, + {value: 400n}, + {value: 300n}, + ]); + + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1200n); + }); }); it('Burn some tokens ', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId; - const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address)); - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n); - expect(await burnItem(api, alice, collectionId, tokenId, 499n)).to.be.true; - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n); + await usingPlaygrounds(async helper => { + const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + await collection.mint(alice, {Substrate: alice.address}, 500n); + + expect(await collection.isTokenExists(0)).to.be.true; + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(500n); + expect(await collection.burnTokens(alice, 499n)).to.be.true; + expect(await collection.isTokenExists(0)).to.be.true; + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n); }); }); it('Burn all tokens ', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId; - const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address)); - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await burnItem(api, alice, collectionId, tokenId, 500n)).to.be.true; - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n); - expect((await api.rpc.unique.totalPieces(collectionId, tokenId)).value.toBigInt()).to.be.equal(0n); + await usingPlaygrounds(async helper => { + const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + await collection.mint(alice, {Substrate: alice.address}, 500n); + + expect(await collection.isTokenExists(0)).to.be.true; + expect(await collection.burnTokens(alice, 500n)).to.be.true; + expect(await collection.isTokenExists(0)).to.be.true; + + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(0n); + expect(await collection.getTotalPieces()).to.be.equal(0n); }); }); it('Set allowance for token', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId; + await usingPlaygrounds(async helper => { + const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; + await collection.mint(alice, {Substrate: alice.address}, 100n); + + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(100n); - const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 100n}, alice.address)); - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + expect(await collection.approveTokens(alice, {Substrate: bob.address}, 60n)).to.be.true; + expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(60n); + expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n); + + expect(await collection.transferFrom(bob, {Substrate: alice.address}, {Substrate: bob.address}, 20n)).to.be.true; + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(80n); + expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(20n); + expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(40n); - expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; - expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(0n); - - expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob, 20n)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n); - expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n); - - await burnFromExpectSuccess(bob, alice, collectionId, tokenId, 10n); - - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(70n); - expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(30n); - expect(await transferFrom(api, collectionId, tokenId, bob, alice, ethAcc, 10n)).to.be.true; - expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(10n); + await collection.burnTokensFrom(bob, {Substrate: alice.address}, 10n); + + expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(70n); + expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(30n); + expect(await collection.transferFrom(bob, {Substrate: alice.address}, ethAcc, 10n)).to.be.true; + expect(await collection.getBalance(ethAcc)).to.be.equal(10n); }); }); }); --- a/tests/src/interfaces/augment-api-query.ts +++ b/tests/src/interfaces/augment-api-query.ts @@ -266,7 +266,7 @@ * * Currently used to store RMRK data. **/ - tokenAuxProperties: AugmentedQuery Observable>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry; + tokenAuxProperties: AugmentedQuery Observable>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry; /** * Used to enumerate token's children. **/ --- a/tests/src/interfaces/default/types.ts +++ b/tests/src/interfaces/default/types.ts @@ -2521,7 +2521,8 @@ export interface UpDataStructsPropertyScope extends Enum { readonly isNone: boolean; readonly isRmrk: boolean; - readonly type: 'None' | 'Rmrk'; + readonly isEth: boolean; + readonly type: 'None' | 'Rmrk' | 'Eth'; } /** @name UpDataStructsRpcCollection */ --- a/tests/src/interfaces/lookup.ts +++ b/tests/src/interfaces/lookup.ts @@ -2978,7 +2978,7 @@ * Lookup397: up_data_structs::PropertyScope **/ UpDataStructsPropertyScope: { - _enum: ['None', 'Rmrk'] + _enum: ['None', 'Rmrk', 'Eth'] }, /** * Lookup399: pallet_nonfungible::pallet::Error --- a/tests/src/interfaces/types-lookup.ts +++ b/tests/src/interfaces/types-lookup.ts @@ -3126,7 +3126,8 @@ export interface UpDataStructsPropertyScope extends Enum { readonly isNone: boolean; readonly isRmrk: boolean; - readonly type: 'None' | 'Rmrk'; + readonly isEth: boolean; + readonly type: 'None' | 'Rmrk' | 'Eth'; } /** @name PalletNonfungibleError (399) */ --- a/tests/src/limits.test.ts +++ b/tests/src/limits.test.ts @@ -27,6 +27,8 @@ transferExpectSuccess, getFreeBalance, waitNewBlocks, burnItemExpectSuccess, + requirePallets, + Pallets, } from './util/helpers'; import {expect} from 'chai'; @@ -67,7 +69,9 @@ describe('Number of tokens per address (ReFungible)', () => { let alice: IKeyringPair; - before(async () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api, privateKeyWrapper) => { alice = privateKeyWrapper('//Alice'); }); @@ -367,7 +371,9 @@ let bob: IKeyringPair; let charlie: IKeyringPair; - before(async () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api, privateKeyWrapper) => { alice = privateKeyWrapper('//Alice'); bob = privateKeyWrapper('//Bob'); --- a/tests/src/nesting/nest.test.ts +++ b/tests/src/nesting/nest.test.ts @@ -17,6 +17,8 @@ transferExpectSuccess, transferFromExpectSuccess, setCollectionLimitsExpectSuccess, + requirePallets, + Pallets, } from '../util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; @@ -311,7 +313,9 @@ // ---------- Re-Fungible ---------- - it('ReFungible: allows an Owner to nest/unnest their token', async () => { + it('ReFungible: allows an Owner to nest/unnest their token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}}); @@ -333,7 +337,9 @@ }); }); - it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => { + it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address}); @@ -715,7 +721,9 @@ // ---------- Re-Fungible ---------- - it('ReFungible: disallows to nest token if nesting is disabled', async () => { + it('ReFungible: disallows to nest token if nesting is disabled', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}}); @@ -745,7 +753,9 @@ }); }); - it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => { + it('ReFungible: disallows a non-Owner to nest someone else\'s token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}}); @@ -773,7 +783,9 @@ }); }); - it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => { + it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await addToAllowListExpectSuccess(alice, collectionNFT, bob.address); @@ -800,7 +812,9 @@ }); }); - it('ReFungible: disallows to nest token to an unlisted collection', async () => { + it('ReFungible: disallows to nest token to an unlisted collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}}); --- a/tests/src/nesting/properties.test.ts +++ b/tests/src/nesting/properties.test.ts @@ -8,6 +8,8 @@ createItemExpectSuccess, getCreateCollectionResult, transferExpectSuccess, + requirePallets, + Pallets, } from '../util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; import {tokenIdToAddress} from '../eth/util/helpers'; @@ -64,7 +66,9 @@ await testMakeSureSuppliesRequired({type: 'NFT'}); }); - it('Makes sure collectionById supplies required fields for ReFungible', async () => { + it('Makes sure collectionById supplies required fields for ReFungible', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testMakeSureSuppliesRequired({type: 'ReFungible'}); }); }); @@ -120,7 +124,9 @@ it('Sets properties for a NFT collection', async () => { await testSetsPropertiesForCollection('NFT'); }); - it('Sets properties for a ReFungible collection', async () => { + it('Sets properties for a ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testSetsPropertiesForCollection('ReFungible'); }); @@ -178,7 +184,9 @@ it('Check valid names for NFT collection properties keys', async () => { await testCheckValidNames('NFT'); }); - it('Check valid names for ReFungible collection properties keys', async () => { + it('Check valid names for ReFungible collection properties keys', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testCheckValidNames('ReFungible'); }); @@ -209,7 +217,9 @@ it('Changes properties of a NFT collection', async () => { await testChangesProperties({type: 'NFT'}); }); - it('Changes properties of a ReFungible collection', async () => { + it('Changes properties of a ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testChangesProperties({type: 'ReFungible'}); }); @@ -238,7 +248,9 @@ it('Deletes properties of a NFT collection', async () => { await testDeleteProperties({type: 'NFT'}); }); - it('Deletes properties of a ReFungible collection', async () => { + it('Deletes properties of a ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testDeleteProperties({type: 'ReFungible'}); }); }); @@ -269,7 +281,9 @@ it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => { await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'}); }); - it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async () => { + it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'}); }); @@ -307,7 +321,9 @@ it('Fails to set properties that exceed the limits (NFT)', async () => { await testFailsSetPropertiesThatExeedLimits({type: 'NFT'}); }); - it('Fails to set properties that exceed the limits (ReFungible)', async () => { + it('Fails to set properties that exceed the limits (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'}); }); @@ -337,7 +353,9 @@ it('Fails to set more properties than it is allowed (NFT)', async () => { await testFailsSetMorePropertiesThanAllowed({type: 'NFT'}); }); - it('Fails to set more properties than it is allowed (ReFungible)', async () => { + it('Fails to set more properties than it is allowed (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'}); }); @@ -392,7 +410,9 @@ it('Fails to set properties with invalid names (NFT)', async () => { await testFailsSetPropertiesWithInvalidNames({type: 'NFT'}); }); - it('Fails to set properties with invalid names (ReFungible)', async () => { + it('Fails to set properties with invalid names (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'}); }); }); @@ -443,7 +463,9 @@ it('Sets access rights to properties of a collection (NFT)', async () => { await testSetsAccessRightsToProperties({type: 'NFT'}); }); - it('Sets access rights to properties of a collection (ReFungible)', async () => { + it('Sets access rights to properties of a collection (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testSetsAccessRightsToProperties({type: 'ReFungible'}); }); @@ -472,7 +494,9 @@ it('Changes access rights to properties of a NFT collection', async () => { await testChangesAccessRightsToProperty({type: 'NFT'}); }); - it('Changes access rights to properties of a ReFungible collection', async () => { + it('Changes access rights to properties of a ReFungible collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testChangesAccessRightsToProperty({type: 'ReFungible'}); }); }); @@ -502,7 +526,9 @@ it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => { await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'}); }); - it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async () => { + it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'}); }); @@ -531,7 +557,9 @@ it('Prevents from adding too many possible properties (NFT)', async () => { await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'}); }); - it('Prevents from adding too many possible properties (ReFungible)', async () => { + it('Prevents from adding too many possible properties (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'}); }); @@ -560,7 +588,9 @@ it('Prevents access rights to be modified if constant (NFT)', async () => { await testPreventAccessRightsModifiedIfConstant({type: 'NFT'}); }); - it('Prevents access rights to be modified if constant (ReFungible)', async () => { + it('Prevents access rights to be modified if constant (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'}); }); @@ -608,7 +638,9 @@ it('Prevents adding properties with invalid names (NFT)', async () => { await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'}); }); - it('Prevents adding properties with invalid names (ReFungible)', async () => { + it('Prevents adding properties with invalid names (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'}); }); }); @@ -651,7 +683,9 @@ it('Reads yet empty properties of a token (NFT)', async () => { await testReadsYetEmptyProperties({type: 'NFT'}); }); - it('Reads yet empty properties of a token (ReFungible)', async () => { + it('Reads yet empty properties of a token (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testReadsYetEmptyProperties({type: 'ReFungible'}); }); @@ -696,7 +730,9 @@ it('Assigns properties to a token according to permissions (NFT)', async () => { await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1); }); - it('Assigns properties to a token according to permissions (ReFungible)', async () => { + it('Assigns properties to a token according to permissions (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100); }); @@ -749,7 +785,9 @@ it('Changes properties of a token according to permissions (NFT)', async () => { await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1); }); - it('Changes properties of a token according to permissions (ReFungible)', async () => { + it('Changes properties of a token according to permissions (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100); }); @@ -802,7 +840,9 @@ it('Deletes properties of a token according to permissions (NFT)', async () => { await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1); }); - it('Deletes properties of a token according to permissions (ReFungible)', async () => { + it('Deletes properties of a token according to permissions (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100); }); @@ -1029,7 +1069,9 @@ it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => { await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1); }); - it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async () => { + it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100); }); @@ -1062,7 +1104,9 @@ it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => { await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1); }); - it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async () => { + it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100); }); @@ -1096,7 +1140,9 @@ it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => { await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1); }); - it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async () => { + it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100); }); @@ -1140,7 +1186,9 @@ it('Forbids adding too many properties to a token (NFT)', async () => { await testForbidsAddingTooManyProperties({type: 'NFT'}, 1); }); - it('Forbids adding too many properties to a token (ReFungible)', async () => { + it('Forbids adding too many properties to a token (ReFungible)', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100); }); }); @@ -1149,7 +1197,9 @@ let collection: number; let token: number; - before(async () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api, privateKeyWrapper) => { alice = privateKeyWrapper('//Alice'); bob = privateKeyWrapper('//Bob'); --- a/tests/src/nesting/rules-smoke.test.ts +++ b/tests/src/nesting/rules-smoke.test.ts @@ -1,7 +1,7 @@ import {expect} from 'chai'; import {tokenIdToAddress} from '../eth/util/helpers'; import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import {createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, CrossAccountId, getCreateCollectionResult} from '../util/helpers'; +import {createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, CrossAccountId, getCreateCollectionResult, requirePallets, Pallets} from '../util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; describe('nesting check', () => { @@ -47,7 +47,9 @@ }); }); - it('called for refungible', async () => { + it('called for refungible', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await expect(executeTransaction(api, alice, api.tx.unique.createItem(collection, nestTarget, {ReFungible: {}}))) --- a/tests/src/nesting/unnest.test.ts +++ b/tests/src/nesting/unnest.test.ts @@ -10,6 +10,8 @@ setCollectionPermissionsExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, + requirePallets, + Pallets, } from '../util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; @@ -80,7 +82,9 @@ }); }); - it('ReFungible: allows the owner to successfully unnest a token', async () => { + it('ReFungible: allows the owner to successfully unnest a token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async api => { const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); --- a/tests/src/nextSponsoring.test.ts +++ b/tests/src/nextSponsoring.test.ts @@ -27,6 +27,8 @@ transferExpectSuccess, normalizeAccountId, getNextSponsored, + requirePallets, + Pallets, } from './util/helpers'; chai.use(chaiAsPromised); @@ -89,7 +91,9 @@ }); }); - it('ReFungible', async () => { + it('ReFungible', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api: ApiPromise) => { const createMode = 'ReFungible'; --- a/tests/src/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -49,8 +49,6 @@ 'inflation', 'unique', 'nonfungible', - 'refungible', - 'scheduler', 'charging', ]; @@ -66,8 +64,16 @@ await usingApi(async api => { const chain = await api.rpc.system.chain(); - if (!chain.eq('UNIQUE')) { - requiredPallets.push(...['rmrkcore', 'rmrkequip']); + const refungible = 'refungible'; + const scheduler = 'scheduler'; + const rmrkPallets = ['rmrkcore', 'rmrkequip']; + + if (chain.eq('OPAL by UNIQUE')) { + requiredPallets.push(refungible, scheduler, ...rmrkPallets); + } else if (chain.eq('QUARTZ by UNIQUE')) { + // Insert Quartz additional pallets here + } else if (chain.eq('UNIQUE')) { + // Insert Unique additional pallets here } }); }); --- a/tests/src/refungible.test.ts +++ b/tests/src/refungible.test.ts @@ -14,28 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {default as usingApi, submitTransactionAsync} from './substrate/substrate-api'; import {IKeyringPair} from '@polkadot/types/types'; + +import { usingPlaygrounds } from './util/playgrounds'; import { - createCollectionExpectSuccess, - getBalance, - createMultipleItemsExpectSuccess, - isTokenExists, - getLastTokenId, - getAllowance, - approve, - transferFrom, - createCollection, - createRefungibleToken, - transfer, - burnItem, - repartitionRFT, - createCollectionWithPropsExpectSuccess, - getDetailedCollectionInfo, - normalizeAccountId, - CrossAccountId, - getCreateItemsResult, - getDestroyItemsResult, + getModuleNames, + Pallets, + requirePallets, } from './util/helpers'; import chai from 'chai'; @@ -46,249 +31,241 @@ let alice: IKeyringPair; let bob: IKeyringPair; -describe('integration test: Refungible functionality:', () => { - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); +describe('integration test: Refungible functionality:', async () => { + before(async function() { + await requirePallets(this, [Pallets.ReFungible]); + + await usingPlaygrounds(async (helper, privateKey) => { + alice = privateKey('//Alice'); + bob = privateKey('//Bob'); + if (!getModuleNames(helper.api!).includes(Pallets.ReFungible)) this.skip(); }); }); - + it('Create refungible collection and token', async () => { - await usingApi(async api => { - const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}}); - expect(createCollectionResult.success).to.be.true; - const collectionId = createCollectionResult.collectionId; + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + + const itemCountBefore = await collection.getLastTokenId(); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); - const itemCountBefore = await getLastTokenId(api, collectionId); - const result = await createRefungibleToken(api, alice, collectionId, 100n); + const itemCountAfter = await collection.getLastTokenId(); - const itemCountAfter = await getLastTokenId(api, collectionId); - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; + expect(token?.tokenId).to.be.gte(itemCountBefore); expect(itemCountAfter).to.be.equal(itemCountBefore + 1); - expect(collectionId).to.be.equal(result.collectionId); - expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); + expect(itemCountAfter.toString()).to.be.equal(token?.tokenId.toString()); }); }); it('RPC method tokenOnewrs for refungible collection and token', async () => { - await usingApi(async (api, privateKeyWrapper) => { + await usingPlaygrounds(async (helper, privateKey) => { const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; - const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString()))); - - const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}}); - const collectionId = createCollectionResult.collectionId; - - const result = await createRefungibleToken(api, alice, collectionId, 10_000n); - const aliceTokenId = result.itemId; - - - await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n); - await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n); + const facelessCrowd = Array(7).fill(0).map((_, i) => ({Substrate: privateKey(`//Alice+${i}`).address})); + + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + + const token = await collection.mintToken(alice, {Substrate: alice.address}, 10_000n); + + await token.transfer(alice, {Substrate: bob.address}, 1000n); + await token.transfer(alice, ethAcc, 900n); for (let i = 0; i < 7; i++) { - await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 50*(i+1)); + await token.transfer(alice, facelessCrowd[i], 50n * BigInt(i + 1)); } - - const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId); - const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s)); - - const aliceID = normalizeAccountId(alice); - const bobId = normalizeAccountId(bob); - + + const owners = await token.getTop10Owners(); + // What to expect - // tslint:disable-next-line:no-unused-expression - expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]); + expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]); expect(owners.length).to.be.equal(10); - const eleven = privateKeyWrapper('11'); - expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true; - expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10); + const eleven = privateKey('//ALice+11'); + expect(await token.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true; + expect((await token.getTop10Owners()).length).to.be.equal(10); }); }); it('Transfer token pieces', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); - expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; - - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n); - await expect(transfer(api, collectionId, tokenId, alice, bob, 41n)).to.eventually.be.rejected; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); + expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true; + + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n); + expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n); + + await expect(token.transfer(alice, {Substrate: bob.address}, 41n)).to.eventually.be.rejected; }); }); it('Create multiple tokens', async () => { - const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const args = [ - {ReFungible: {pieces: 1}}, - {ReFungible: {pieces: 2}}, - {ReFungible: {pieces: 100}}, - ]; - await createMultipleItemsExpectSuccess(alice, collectionId, args); - - await usingApi(async api => { - const tokenId = await getLastTokenId(api, collectionId); - expect(tokenId).to.be.equal(3); - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + // TODO: fix mintMultipleTokens + // await collection.mintMultipleTokens(alice, [ + // {owner: {Substrate: alice.address}, pieces: 1n}, + // {owner: {Substrate: alice.address}, pieces: 2n}, + // {owner: {Substrate: alice.address}, pieces: 100n}, + // ]); + await helper.rft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, [ + {pieces: 1n}, + {pieces: 2n}, + {pieces: 100n}, + ]); + const lastTokenId = await collection.getLastTokenId(); + expect(lastTokenId).to.be.equal(3); + expect(await collection.getTokenBalance(lastTokenId, {Substrate: alice.address})).to.be.equal(100n); }); }); it('Burn some pieces', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); - expect(await burnItem(api, alice, collectionId, tokenId, 99n)).to.be.true; - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n); + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); + expect(await collection.isTokenExists(token.tokenId)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); + expect((await token.burn(alice, 99n)).success).to.be.true; + expect(await collection.isTokenExists(token.tokenId)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(1n); }); }); it('Burn all pieces', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); - expect(await burnItem(api, alice, collectionId, tokenId, 100n)).to.be.true; - expect(await isTokenExists(api, collectionId, tokenId)).to.be.false; + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); + + expect(await collection.isTokenExists(token.tokenId)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); + + expect((await token.burn(alice, 100n)).success).to.be.true; + expect(await collection.isTokenExists(token.tokenId)).to.be.false; }); }); it('Burn some pieces for multiple users', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); - expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; + expect(await collection.isTokenExists(token.tokenId)).to.be.true; + + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); + expect(await token.transfer(alice, {Substrate: bob.address}, 60n)).to.be.true; - - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(40n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n); - expect(await burnItem(api, alice, collectionId, tokenId, 40n)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n); + expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n); + + expect((await token.burn(alice, 40n)).success).to.be.true; + + expect(await collection.isTokenExists(token.tokenId)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n); + + expect((await token.burn(bob, 59n)).success).to.be.true; + + expect(await token.getBalance({Substrate: bob.address})).to.be.equal(1n); + expect(await collection.isTokenExists(token.tokenId)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n); - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await burnItem(api, bob, collectionId, tokenId, 59n)).to.be.true; + expect((await token.burn(bob, 1n)).success).to.be.true; - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(1n); - expect(await isTokenExists(api, collectionId, tokenId)).to.be.true; - expect(await burnItem(api, bob, collectionId, tokenId, 1n)).to.be.true; - - expect(await isTokenExists(api, collectionId, tokenId)).to.be.false; + expect(await collection.isTokenExists(token.tokenId)).to.be.false; }); }); it('Set allowance for token', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); + + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n); + expect(await token.approve(alice, {Substrate: bob.address}, 60n)).to.be.true; + expect(await token.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(60n); - expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true; - expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n); - - expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob, 20n)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n); - expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n); + expect(await token.transferFrom(bob, {Substrate: alice.address}, {Substrate: bob.address}, 20n)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(80n); + expect(await token.getBalance({Substrate: bob.address})).to.be.equal(20n); + expect(await token.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(40n); }); }); it('Repartition', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); - expect(await repartitionRFT(api, collectionId, alice, tokenId, 200n)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(200n); - - expect(await transfer(api, collectionId, tokenId, alice, bob, 110n)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(90n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(110n); - - await expect(repartitionRFT(api, collectionId, alice, tokenId, 80n)).to.eventually.be.rejected; + expect(await token.repartition(alice, 200n)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(200n); + expect(await token.getTotalPieces()).to.be.equal(200n); + + expect(await token.transfer(alice, {Substrate: bob.address}, 110n)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(90n); + expect(await token.getBalance({Substrate: bob.address})).to.be.equal(110n); + + await expect(token.repartition(alice, 80n)).to.eventually.be.rejected; + + expect(await token.transfer(alice, {Substrate: bob.address}, 90n)).to.be.true; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n); + expect(await token.getBalance({Substrate: bob.address})).to.be.equal(200n); - expect(await transfer(api, collectionId, tokenId, alice, bob, 90n)).to.be.true; - expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n); - expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(200n); + expect(await token.repartition(bob, 150n)).to.be.true; + await expect(token.transfer(bob, {Substrate: alice.address}, 160n)).to.eventually.be.rejected; - expect(await repartitionRFT(api, collectionId, bob, tokenId, 150n)).to.be.true; - await expect(transfer(api, collectionId, tokenId, bob, alice, 160n)).to.eventually.be.rejected; }); }); it('Repartition with increased amount', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; - - const tx = api.tx.unique.repartition(collectionId, tokenId, 200n); - const events = await submitTransactionAsync(alice, tx); - const substrateEvents = getCreateItemsResult(events); - expect(substrateEvents).to.include.deep.members([ - { - success: true, - collectionId, - itemId: tokenId, - recipient: {Substrate: alice.address}, - amount: 100, - }, - ]); + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); + await token.repartition(alice, 200n); + const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event); + expect(chainEvents).to.include.deep.members([{ + method: 'ItemCreated', + section: 'common', + index: '0x4202', + data: [ + collection.collectionId.toString(), + token.tokenId.toString(), + {Substrate: alice.address}, + '100', + ], + }]); }); }); it('Repartition with decreased amount', async () => { - await usingApi(async api => { - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n)).itemId; - - const tx = api.tx.unique.repartition(collectionId, tokenId, 50n); - const events = await submitTransactionAsync(alice, tx); - const substrateEvents = getDestroyItemsResult(events); - expect(substrateEvents).to.include.deep.members([ - { - success: true, - collectionId, - itemId: tokenId, - owner: {Substrate: alice.address}, - amount: 50, - }, - ]); - }); - }); -}); - -describe('Test Refungible properties:', () => { - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async helper => { + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}, 100n); + await token.repartition(alice, 50n); + const chainEvents = helper.chainLog.slice(-1)[0].events.map((x: any) => x.event); + expect(chainEvents).to.include.deep.members([{ + method: 'ItemDestroyed', + section: 'common', + index: '0x4203', + data: [ + collection.collectionId.toString(), + token.tokenId.toString(), + {Substrate: alice.address}, + '50', + ], + }]); }); }); - it('Сreate new collection with properties', async () => { - await usingApi(async api => { + it('Create new collection with properties', async () => { + await usingPlaygrounds(async helper => { const properties = [{key: 'key1', value: 'val1'}]; - const propertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]; - const collectionId = await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'ReFungible'}, - properties: properties, - propPerm: propertyPermissions, - }); - const collection = (await getDetailedCollectionInfo(api, collectionId))!; - expect(collection.properties.toHuman()).to.be.deep.equal(properties); - expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions); + const tokenPropertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]; + const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test', properties, tokenPropertyPermissions}); + const info = await collection.getData(); + expect(info?.raw.properties).to.be.deep.equal(properties); + expect(info?.raw.tokenPropertyPermissions).to.be.deep.equal(tokenPropertyPermissions); }); }); }); + --- a/tests/src/removeCollectionAdmin.test.ts +++ b/tests/src/removeCollectionAdmin.test.ts @@ -16,123 +16,112 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api'; -import {createCollectionExpectSuccess, destroyCollectionExpectSuccess, getAdminList, normalizeAccountId, queryCollectionExpectSuccess} from './util/helpers'; +import {usingPlaygrounds} from './util/playgrounds'; chai.use(chaiAsPromised); const expect = chai.expect; describe('Integration Test removeCollectionAdmin(collection_id, account_id):', () => { it('Remove collection admin.', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const collectionId = await createCollectionExpectSuccess(); - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - const collection = await queryCollectionExpectSuccess(api, collectionId); - expect(collection.owner.toString()).to.be.deep.eq(alice.address); + await usingPlaygrounds(async (helper, privateKey) => { + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + const collectionInfo = await collection.getData(); + expect(collectionInfo?.raw.owner.toString()).to.be.deep.eq(alice.address); // first - add collection admin Bob - const addAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await submitTransactionAsync(alice, addAdminTx); + await collection.addAdmin(alice, {Substrate: bob.address}); - const adminListAfterAddAdmin = await getAdminList(api, collectionId); - expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address)); + const adminListAfterAddAdmin = await collection.getAdmins(); + expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)}); // then remove bob from admins of collection - const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await submitTransactionAsync(alice, removeAdminTx); + await collection.removeAdmin(alice, {Substrate: bob.address}); - const adminListAfterRemoveAdmin = await getAdminList(api, collectionId); - expect(adminListAfterRemoveAdmin).not.to.be.deep.contains(normalizeAccountId(bob.address)); + const adminListAfterRemoveAdmin = await collection.getAdmins(); + expect(adminListAfterRemoveAdmin).not.to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)}); }); }); it('Remove admin from collection that has no admins', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const collectionId = await createCollectionExpectSuccess(); + await usingPlaygrounds(async (helper, privateKey) => { + const alice = privateKey('//Alice'); + const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); - const adminListBeforeAddAdmin = await getAdminList(api, collectionId); + const adminListBeforeAddAdmin = await collection.getAdmins(); expect(adminListBeforeAddAdmin).to.have.lengthOf(0); - const tx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(alice.address)); - await submitTransactionAsync(alice, tx); + // await expect(collection.removeAdmin(alice, {Substrate: alice.address})).to.be.rejectedWith('Unable to remove collection admin'); + await collection.removeAdmin(alice, {Substrate: alice.address}); }); }); }); describe('Negative Integration Test removeCollectionAdmin(collection_id, account_id):', () => { it('Can\'t remove collection admin from not existing collection', async () => { - await usingApi(async (api, privateKeyWrapper) => { + await usingPlaygrounds(async (helper, privateKey) => { // tslint:disable-next-line: no-bitwise const collectionId = (1 << 32) - 1; - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); - const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected; + await expect(helper.collection.removeAdmin(alice, collectionId, {Substrate: bob.address})).to.be.rejected; // Verifying that nothing bad happened (network is live, new collections can be created, etc.) - await createCollectionExpectSuccess(); + await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); }); }); it('Can\'t remove collection admin from deleted collection', async () => { - await usingApi(async (api, privateKeyWrapper) => { - // tslint:disable-next-line: no-bitwise - const collectionId = await createCollectionExpectSuccess(); - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); - await destroyCollectionExpectSuccess(collectionId); + expect(await collection.burn(alice)).to.be.true; - const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected; + await expect(helper.collection.removeAdmin(alice, collection.collectionId, {Substrate: bob.address})).to.be.rejected; // Verifying that nothing bad happened (network is live, new collections can be created, etc.) - await createCollectionExpectSuccess(); + await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); }); }); it('Regular user can\'t remove collection admin', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const collectionId = await createCollectionExpectSuccess(); - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - const charlie = privateKeyWrapper('//Charlie'); + await usingPlaygrounds(async (helper, privateKey) => { + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); - const addAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await submitTransactionAsync(alice, addAdminTx); + await collection.addAdmin(alice, {Substrate: bob.address}); - const changeOwnerTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await expect(submitTransactionExpectFailAsync(charlie, changeOwnerTx)).to.be.rejected; + await expect(collection.removeAdmin(charlie, {Substrate: bob.address})).to.be.rejected; // Verifying that nothing bad happened (network is live, new collections can be created, etc.) - await createCollectionExpectSuccess(); + await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); }); }); it('Admin can\'t remove collection admin.', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const collectionId = await createCollectionExpectSuccess(); - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - const charlie = privateKeyWrapper('//Charlie'); - - const addBobAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await submitTransactionAsync(alice, addBobAdminTx); - const addCharlieAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(charlie.address)); - await submitTransactionAsync(alice, addCharlieAdminTx); + await usingPlaygrounds(async (helper, privateKey) => { + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const charlie = privateKey('//Charlie'); + const collection = await helper.nft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); + + await collection.addAdmin(alice, {Substrate: bob.address}); + await collection.addAdmin(alice, {Substrate: charlie.address}); - const adminListAfterAddAdmin = await getAdminList(api, collectionId); - expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(bob.address)); - expect(adminListAfterAddAdmin).to.be.deep.contains(normalizeAccountId(charlie.address)); + const adminListAfterAddAdmin = await collection.getAdmins(); + expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)}); + expect(adminListAfterAddAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(charlie.address)}); - const removeAdminTx = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(bob.address)); - await expect(submitTransactionExpectFailAsync(charlie, removeAdminTx)).to.be.rejected; + await expect(collection.removeAdmin(charlie, {Substrate: bob.address})).to.be.rejected; - const adminListAfterRemoveAdmin = await getAdminList(api, collectionId); - expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(bob.address)); - expect(adminListAfterRemoveAdmin).to.be.deep.contains(normalizeAccountId(charlie.address)); + const adminListAfterRemoveAdmin = await collection.getAdmins(); + expect(adminListAfterRemoveAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(bob.address)}); + expect(adminListAfterRemoveAdmin).to.be.deep.contains({Substrate: helper.address.normalizeSubstrate(charlie.address)}); }); }); }); --- a/tests/src/rmrk/acceptNft.test.ts +++ b/tests/src/rmrk/acceptNft.test.ts @@ -8,14 +8,19 @@ } from './util/tx'; import {NftIdTuple} from './util/fetch'; import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; +import {requirePallets, Pallets} from '../util/helpers'; describe('integration test: accept NFT', () => { let api: any; - before(async () => { api = await getApiConnection(); }); - + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; const bob = '//Bob'; - + const createTestCollection = async (issuerUri: string) => { return await createCollection( api, --- a/tests/src/rmrk/addResource.test.ts +++ b/tests/src/rmrk/addResource.test.ts @@ -12,6 +12,7 @@ addNftComposableResource, } from './util/tx'; import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup'; +import {requirePallets, Pallets} from '../util/helpers'; describe('integration test: add NFT resource', () => { const Alice = '//Alice'; @@ -24,8 +25,9 @@ const nonexistentId = 99999; let api: any; - before(async () => { + before(async function() { api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); }); it('add resource', async () => { --- a/tests/src/rmrk/addTheme.test.ts +++ b/tests/src/rmrk/addTheme.test.ts @@ -3,10 +3,14 @@ import {createBase, addTheme} from './util/tx'; import {expectTxFailure} from './util/helpers'; import {getThemeNames} from './util/fetch'; +import {requirePallets, Pallets} from '../util/helpers'; describe('integration test: add Theme to Base', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkEquip]); + }); const alice = '//Alice'; const bob = '//Bob'; --- a/tests/src/rmrk/burnNft.test.ts +++ b/tests/src/rmrk/burnNft.test.ts @@ -5,6 +5,7 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import {requirePallets, Pallets} from '../util/helpers'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -14,10 +15,12 @@ const Bob = '//Bob'; let api: any; - before(async () => { + before(async function() { api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); }); + it('burn nft', async () => { await createCollection( api, --- a/tests/src/rmrk/changeCollectionIssuer.test.ts +++ b/tests/src/rmrk/changeCollectionIssuer.test.ts @@ -1,4 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {expectTxFailure} from './util/helpers'; import { changeIssuer, @@ -10,10 +11,13 @@ const Bob = '//Bob'; let api: any; - before(async () => { + before(async function() { api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); }); + + it('change collection issuer', async () => { await createCollection( api, --- a/tests/src/rmrk/createBase.test.ts +++ b/tests/src/rmrk/createBase.test.ts @@ -1,9 +1,13 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {createCollection, createBase} from './util/tx'; describe('integration test: create new Base', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function() { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]); + }); const alice = '//Alice'; --- a/tests/src/rmrk/createCollection.test.ts +++ b/tests/src/rmrk/createCollection.test.ts @@ -1,9 +1,15 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {createCollection} from './util/tx'; describe('Integration test: create new collection', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; --- a/tests/src/rmrk/deleteCollection.test.ts +++ b/tests/src/rmrk/deleteCollection.test.ts @@ -1,11 +1,13 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, deleteCollection} from './util/tx'; describe('integration test: delete collection', () => { let api: any; - before(async () => { + before(async function () { api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); }); const Alice = '//Alice'; --- a/tests/src/rmrk/equipNft.test.ts +++ b/tests/src/rmrk/equipNft.test.ts @@ -1,6 +1,7 @@ import {ApiPromise} from '@polkadot/api'; import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {getNft, getParts, NftIdTuple} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import { @@ -122,8 +123,10 @@ describe.skip('integration test: Equip NFT', () => { let api: any; - before(async () => { + + before(async function () { api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore, Pallets.RmrkEquip]); }); it('equip nft', async () => { --- a/tests/src/rmrk/getOwnedNfts.test.ts +++ b/tests/src/rmrk/getOwnedNfts.test.ts @@ -1,11 +1,17 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {getOwnedNfts} from './util/fetch'; import {mintNft, createCollection} from './util/tx'; describe('integration test: get owned NFTs', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + const alice = '//Alice'; --- a/tests/src/rmrk/lockCollection.test.ts +++ b/tests/src/rmrk/lockCollection.test.ts @@ -1,4 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, lockCollection, mintNft} from './util/tx'; @@ -8,8 +9,9 @@ const Max = 5; let api: any; - before(async () => { + before(async function () { api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); }); it('lock collection', async () => { --- a/tests/src/rmrk/mintNft.test.ts +++ b/tests/src/rmrk/mintNft.test.ts @@ -1,12 +1,18 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {getNft} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import {createCollection, mintNft} from './util/tx'; describe('integration test: mint new NFT', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + const alice = '//Alice'; const bob = '//Bob'; --- a/tests/src/rmrk/rejectNft.test.ts +++ b/tests/src/rmrk/rejectNft.test.ts @@ -8,10 +8,16 @@ } from './util/tx'; import {getChildren, NftIdTuple} from './util/fetch'; import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; +import {requirePallets, Pallets} from '../util/helpers'; describe('integration test: reject NFT', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); + + const alice = '//Alice'; const bob = '//Bob'; --- a/tests/src/rmrk/removeResource.test.ts +++ b/tests/src/rmrk/removeResource.test.ts @@ -1,6 +1,7 @@ import {expect} from 'chai'; import privateKey from '../substrate/privateKey'; import {executeTransaction, getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {getNft, NftIdTuple} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import { @@ -16,9 +17,10 @@ describe('Integration test: remove nft resource', () => { let api: any; let ss58Format: string; - before(async () => { + before(async function() { api = await getApiConnection(); ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format; + await requirePallets(this, [Pallets.RmrkCore]); }); const Alice = '//Alice'; --- a/tests/src/rmrk/rmrkIsolation.test.ts +++ b/tests/src/rmrk/rmrkIsolation.test.ts @@ -6,7 +6,9 @@ getCreateCollectionResult, getDetailedCollectionInfo, getGenericResult, + requirePallets, normalizeAccountId, + Pallets, } from '../util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; import {ApiPromise} from '@polkadot/api'; @@ -59,11 +61,10 @@ describe('RMRK External Integration Test', async () => { const it_rmrk = (await isUnique() ? it : it.skip); - before(async () => { + before(async function() { await usingApi(async (api, privateKeyWrapper) => { alice = privateKeyWrapper('//Alice'); - - + await requirePallets(this, [Pallets.RmrkCore]); }); }); --- a/tests/src/rmrk/sendNft.test.ts +++ b/tests/src/rmrk/sendNft.test.ts @@ -3,10 +3,14 @@ import {createCollection, mintNft, sendNft} from './util/tx'; import {NftIdTuple} from './util/fetch'; import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; +import {requirePallets, Pallets} from '../util/helpers'; describe('integration test: send NFT', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); const maxNftId = 0xFFFFFFFF; --- a/tests/src/rmrk/setCollectionProperty.test.ts +++ b/tests/src/rmrk/setCollectionProperty.test.ts @@ -1,4 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, setPropertyCollection} from './util/tx'; @@ -7,8 +8,9 @@ const Bob = '//Bob'; let api: any; - before(async () => { + before(async function () { api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); }); it('set collection property', async () => { --- a/tests/src/rmrk/setEquippableList.test.ts +++ b/tests/src/rmrk/setEquippableList.test.ts @@ -1,10 +1,14 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, createBase, setEquippableList} from './util/tx'; describe("integration test: set slot's Equippable List", () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); const alice = '//Alice'; const bob = '//Bob'; --- a/tests/src/rmrk/setNftProperty.test.ts +++ b/tests/src/rmrk/setNftProperty.test.ts @@ -1,11 +1,15 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {NftIdTuple} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx'; describe('integration test: set NFT property', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); const alice = '//Alice'; const bob = '//Bob'; --- a/tests/src/rmrk/setResourcePriorities.test.ts +++ b/tests/src/rmrk/setResourcePriorities.test.ts @@ -1,10 +1,14 @@ import {getApiConnection} from '../substrate/substrate-api'; +import {requirePallets, Pallets} from '../util/helpers'; import {expectTxFailure} from './util/helpers'; import {mintNft, createCollection, setResourcePriorities} from './util/tx'; describe('integration test: set NFT resource priorities', () => { let api: any; - before(async () => { api = await getApiConnection(); }); + before(async function () { + api = await getApiConnection(); + await requirePallets(this, [Pallets.RmrkCore]); + }); const alice = '//Alice'; const bob = '//Bob'; --- a/tests/src/setCollectionSponsor.test.ts +++ b/tests/src/setCollectionSponsor.test.ts @@ -23,6 +23,8 @@ setCollectionSponsorExpectFailure, addCollectionAdminExpectSuccess, getCreatedCollectionCount, + requirePallets, + Pallets, } from './util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; @@ -50,7 +52,9 @@ const collectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); await setCollectionSponsorExpectSuccess(collectionId, bob.address); }); - it('Set ReFungible collection sponsor', async () => { + it('Set ReFungible collection sponsor', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const collectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await setCollectionSponsorExpectSuccess(collectionId, bob.address); }); --- a/tests/src/transfer.test.ts +++ b/tests/src/transfer.test.ts @@ -35,11 +35,14 @@ getBalance as getTokenBalance, transferFromExpectSuccess, transferFromExpectFail, + requirePallets, + Pallets, } from './util/helpers'; import { subToEth, itWeb3, } from './eth/util/helpers'; +import {request} from 'https'; let alice: IKeyringPair; let bob: IKeyringPair; @@ -89,56 +92,61 @@ }); }); - it('User can transfer owned token', async () => { - await usingApi(async (api, privateKeyWrapper) => { - // nft - const nftCollectionId = await createCollectionExpectSuccess(); - const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); - await transferExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 'NFT'); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); - await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob, 1, 'Fungible'); - // reFungible - const reFungibleCollectionId = await - createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); - await transferExpectSuccess( - reFungibleCollectionId, - newReFungibleTokenId, - alice, - bob, - 100, - 'ReFungible', - ); - }); + it('[nft] User can transfer owned token', async () => { + const nftCollectionId = await createCollectionExpectSuccess(); + const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); + await transferExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 'NFT'); + }); + + it('[fungible] User can transfer owned token', async () => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); + await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob, 1, 'Fungible'); + }); + + it('[refungible] User can transfer owned token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); + await transferExpectSuccess( + reFungibleCollectionId, + newReFungibleTokenId, + alice, + bob, + 100, + 'ReFungible', + ); + }); + + it('[nft] Collection admin can transfer owned token', async () => { + const nftCollectionId = await createCollectionExpectSuccess(); + await addCollectionAdminExpectSuccess(alice, nftCollectionId, bob.address); + const newNftTokenId = await createItemExpectSuccess(bob, nftCollectionId, 'NFT', bob.address); + await transferExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, 1, 'NFT'); + }); + + it('[fungible] Collection admin can transfer owned token', async () => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + await addCollectionAdminExpectSuccess(alice, fungibleCollectionId, bob.address); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible', bob.address); + await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, 1, 'Fungible'); }); - it('Collection admin can transfer owned token', async () => { - await usingApi(async (api, privateKeyWrapper) => { - // nft - const nftCollectionId = await createCollectionExpectSuccess(); - await addCollectionAdminExpectSuccess(alice, nftCollectionId, bob.address); - const newNftTokenId = await createItemExpectSuccess(bob, nftCollectionId, 'NFT', bob.address); - await transferExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, 1, 'NFT'); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - await addCollectionAdminExpectSuccess(alice, fungibleCollectionId, bob.address); - const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible', bob.address); - await transferExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, 1, 'Fungible'); - // reFungible - const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - await addCollectionAdminExpectSuccess(alice, reFungibleCollectionId, bob.address); - const newReFungibleTokenId = await createItemExpectSuccess(bob, reFungibleCollectionId, 'ReFungible', bob.address); - await transferExpectSuccess( - reFungibleCollectionId, - newReFungibleTokenId, - bob, - alice, - 100, - 'ReFungible', - ); - }); + it('[refungible] Collection admin can transfer owned token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + await addCollectionAdminExpectSuccess(alice, reFungibleCollectionId, bob.address); + const newReFungibleTokenId = await createItemExpectSuccess(bob, reFungibleCollectionId, 'ReFungible', bob.address); + await transferExpectSuccess( + reFungibleCollectionId, + newReFungibleTokenId, + bob, + alice, + 100, + 'ReFungible', + ); }); }); @@ -150,33 +158,49 @@ charlie = privateKeyWrapper('//Charlie'); }); }); - it('Transfer with not existed collection_id', async () => { + + it('[nft] Transfer with not existed collection_id', async () => { await usingApi(async (api) => { - // nft const nftCollectionCount = await getCreatedCollectionCount(api); await transferExpectFailure(nftCollectionCount + 1, 1, alice, bob, 1); - // fungible + }); + }); + + it('[fungible] Transfer with not existed collection_id', async () => { + await usingApi(async (api) => { const fungibleCollectionCount = await getCreatedCollectionCount(api); await transferExpectFailure(fungibleCollectionCount + 1, 0, alice, bob, 1); - // reFungible + }); + }); + + it('[refungible] Transfer with not existed collection_id', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + await usingApi(async (api) => { const reFungibleCollectionCount = await getCreatedCollectionCount(api); await transferExpectFailure(reFungibleCollectionCount + 1, 1, alice, bob, 1); }); }); - it('Transfer with deleted collection_id', async () => { - // nft + + it('[nft] Transfer with deleted collection_id', async () => { const nftCollectionId = await createCollectionExpectSuccess(); const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); await burnItemExpectSuccess(alice, nftCollectionId, newNftTokenId); await destroyCollectionExpectSuccess(nftCollectionId); await transferExpectFailure(nftCollectionId, newNftTokenId, alice, bob, 1); - // fungible + }); + + it('[fungible] Transfer with deleted collection_id', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); await burnItemExpectSuccess(alice, fungibleCollectionId, newFungibleTokenId, 10); await destroyCollectionExpectSuccess(fungibleCollectionId); await transferExpectFailure(fungibleCollectionId, newFungibleTokenId, alice, bob, 1); - // reFungible + }); + + it('[refungible] Transfer with deleted collection_id', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); @@ -190,16 +214,21 @@ 1, ); }); - it('Transfer with not existed item_id', async () => { - // nft + + it('[nft] Transfer with not existed item_id', async () => { const nftCollectionId = await createCollectionExpectSuccess(); await transferExpectFailure(nftCollectionId, 2, alice, bob, 1); - // fungible + }); + + it('[fungible] Transfer with not existed item_id', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); await transferExpectFailure(fungibleCollectionId, 2, alice, bob, 1); - // reFungible - const reFungibleCollectionId = await - createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + }); + + it('[refungible] Transfer with not existed item_id', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await transferExpectFailure( reFungibleCollectionId, 2, @@ -208,18 +237,24 @@ 1, ); }); - it('Transfer with deleted item_id', async () => { - // nft + + it('[nft] Transfer with deleted item_id', async () => { const nftCollectionId = await createCollectionExpectSuccess(); const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); await burnItemExpectSuccess(alice, nftCollectionId, newNftTokenId, 1); await transferExpectFailure(nftCollectionId, newNftTokenId, alice, bob, 1); - // fungible + }); + + it('[fungible] Transfer with deleted item_id', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); await burnItemExpectSuccess(alice, fungibleCollectionId, newFungibleTokenId, 10); await transferExpectFailure(fungibleCollectionId, newFungibleTokenId, alice, bob, 1); - // reFungible + }); + + it('[refungible] Transfer with deleted item_id', async function() { + await requirePallets(this, [Pallets.ReFungible]); + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); @@ -232,18 +267,23 @@ 1, ); }); - it('Transfer with recipient that is not owner', async () => { - // nft + + it('[nft] Transfer with recipient that is not owner', async () => { const nftCollectionId = await createCollectionExpectSuccess(); const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); await transferExpectFailure(nftCollectionId, newNftTokenId, charlie, bob, 1); - // fungible + }); + + it('[fungible] Transfer with recipient that is not owner', async () => { const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); await transferExpectFailure(fungibleCollectionId, newFungibleTokenId, charlie, bob, 1); - // reFungible - const reFungibleCollectionId = await - createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + }); + + it('[refungible] Transfer with recipient that is not owner', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); await transferExpectFailure( reFungibleCollectionId, @@ -276,7 +316,9 @@ }); }); - it('RFT', async () => { + it('RFT', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async (api: ApiPromise) => { const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); --- a/tests/src/transferFrom.test.ts +++ b/tests/src/transferFrom.test.ts @@ -31,6 +31,8 @@ burnItemExpectSuccess, setCollectionLimitsExpectSuccess, getCreatedCollectionCount, + requirePallets, + Pallets, } from './util/helpers'; chai.use(chaiAsPromised); @@ -49,36 +51,36 @@ }); }); - it('Execute the extrinsic and check nftItemList - owner of token', async () => { - await usingApi(async () => { - // nft - const nftCollectionId = await createCollectionExpectSuccess(); - const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); - await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address); + it('[nft] Execute the extrinsic and check nftItemList - owner of token', async () => { + const nftCollectionId = await createCollectionExpectSuccess(); + const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); + await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address); - await transferFromExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, charlie, 1, 'NFT'); + await transferFromExpectSuccess(nftCollectionId, newNftTokenId, bob, alice, charlie, 1, 'NFT'); + }); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); - await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address); - await transferFromExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1, 'Fungible'); + it('[fungible] Execute the extrinsic and check nftItemList - owner of token', async () => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); + await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address); + await transferFromExpectSuccess(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1, 'Fungible'); + }); - // reFungible - const reFungibleCollectionId = await - createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); - await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 100); - await transferFromExpectSuccess( - reFungibleCollectionId, - newReFungibleTokenId, - bob, - alice, - charlie, - 100, - 'ReFungible', - ); - }); + it('[refungible] Execute the extrinsic and check nftItemList - owner of token', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); + await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address, 100); + await transferFromExpectSuccess( + reFungibleCollectionId, + newReFungibleTokenId, + bob, + alice, + charlie, + 100, + 'ReFungible', + ); }); it('Should reduce allowance if value is big', async () => { @@ -119,20 +121,28 @@ }); }); - it('transferFrom for a collection that does not exist', async () => { + it('[nft] transferFrom for a collection that does not exist', async () => { await usingApi(async (api: ApiPromise) => { - // nft const nftCollectionCount = await getCreatedCollectionCount(api); await approveExpectFail(nftCollectionCount + 1, 1, alice, bob); await transferFromExpectFail(nftCollectionCount + 1, 1, bob, alice, charlie, 1); + }); + }); - // fungible + it('[fungible] transferFrom for a collection that does not exist', async () => { + await usingApi(async (api: ApiPromise) => { const fungibleCollectionCount = await getCreatedCollectionCount(api); await approveExpectFail(fungibleCollectionCount + 1, 0, alice, bob); await transferFromExpectFail(fungibleCollectionCount + 1, 0, bob, alice, charlie, 1); - // reFungible + }); + }); + + it('[refungible] transferFrom for a collection that does not exist', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + await usingApi(async (api: ApiPromise) => { const reFungibleCollectionCount = await getCreatedCollectionCount(api); await approveExpectFail(reFungibleCollectionCount + 1, 1, alice, bob); @@ -158,67 +168,70 @@ }); }); */ - it('transferFrom for not approved address', async () => { - await usingApi(async () => { - // nft - const nftCollectionId = await createCollectionExpectSuccess(); - const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); + it('[nft] transferFrom for not approved address', async () => { + const nftCollectionId = await createCollectionExpectSuccess(); + const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); - await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 1); + await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 1); + }); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); - await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1); - // reFungible - const reFungibleCollectionId = await - createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); - await transferFromExpectFail( - reFungibleCollectionId, - newReFungibleTokenId, - bob, - alice, - charlie, - 1, - ); - }); + it('[fungible] transferFrom for not approved address', async () => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); + await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 1); + }); + + it('[refungible] transferFrom for not approved address', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + const reFungibleCollectionId = await + createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); + await transferFromExpectFail( + reFungibleCollectionId, + newReFungibleTokenId, + bob, + alice, + charlie, + 1, + ); + }); + + it('[nft] transferFrom incorrect token count', async () => { + const nftCollectionId = await createCollectionExpectSuccess(); + const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); + await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address); + + await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 2); }); - it('transferFrom incorrect token count', async () => { - await usingApi(async () => { - // nft - const nftCollectionId = await createCollectionExpectSuccess(); - const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); - await approveExpectSuccess(nftCollectionId, newNftTokenId, alice, bob.address); + it('[fungible] transferFrom incorrect token count', async () => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); + await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address); + await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 2); + }); - await transferFromExpectFail(nftCollectionId, newNftTokenId, bob, alice, charlie, 2); + it('[refungible] transferFrom incorrect token count', async function() { + await requirePallets(this, [Pallets.ReFungible]); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); - await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, alice, bob.address); - await transferFromExpectFail(fungibleCollectionId, newFungibleTokenId, bob, alice, charlie, 2); - // reFungible - const reFungibleCollectionId = await - createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); - await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address); - await transferFromExpectFail( - reFungibleCollectionId, - newReFungibleTokenId, - bob, - alice, - charlie, - 2, - ); - }); + const reFungibleCollectionId = await + createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); + await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, alice, bob.address); + await transferFromExpectFail( + reFungibleCollectionId, + newReFungibleTokenId, + bob, + alice, + charlie, + 2, + ); }); - it('execute transferFrom from account that is not owner of collection', async () => { + it('[nft] execute transferFrom from account that is not owner of collection', async () => { await usingApi(async (api, privateKeyWrapper) => { const dave = privateKeyWrapper('//Dave'); - // nft const nftCollectionId = await createCollectionExpectSuccess(); const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT'); try { @@ -230,8 +243,13 @@ } // await transferFromExpectFail(nftCollectionId, newNftTokenId, Dave, Alice, Charlie, 1); + }); + }); - // fungible + it('[fungible] execute transferFrom from account that is not owner of collection', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const dave = privateKeyWrapper('//Dave'); + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); const newFungibleTokenId = await createItemExpectSuccess(alice, fungibleCollectionId, 'Fungible'); try { @@ -241,7 +259,14 @@ // tslint:disable-next-line:no-unused-expression expect(e).to.be.exist; } - // reFungible + }); + }); + + it('[refungible] execute transferFrom from account that is not owner of collection', async function() { + await requirePallets(this, [Pallets.ReFungible]); + + await usingApi(async (api, privateKeyWrapper) => { + const dave = privateKeyWrapper('//Dave'); const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); @@ -276,7 +301,9 @@ }); }); - it('transferFrom burnt token before approve ReFungible', async () => { + it('transferFrom burnt token before approve ReFungible', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async () => { const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); await setCollectionLimitsExpectSuccess(alice, reFungibleCollectionId, {ownerCanTransfer: true}); @@ -308,7 +335,9 @@ }); }); - it('transferFrom burnt token after approve ReFungible', async () => { + it('transferFrom burnt token after approve ReFungible', async function() { + await requirePallets(this, [Pallets.ReFungible]); + await usingApi(async () => { const reFungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); const newReFungibleTokenId = await createItemExpectSuccess(alice, reFungibleCollectionId, 'ReFungible'); --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -28,6 +28,7 @@ import {hexToStr, strToUTF16, utf16ToStr} from './util'; import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup'; import {UpDataStructsTokenChild} from '../interfaces'; +import {Context} from 'mocha'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -38,6 +39,66 @@ Ethereum: string, }; + +export enum Pallets { + Inflation = 'inflation', + RmrkCore = 'rmrkcore', + RmrkEquip = 'rmrkequip', + ReFungible = 'refungible', + Fungible = 'fungible', + NFT = 'nonfungible', + Scheduler = 'scheduler', +} + +export async function isUnique(): Promise { + return usingApi(async api => { + const chain = await api.rpc.system.chain(); + + return chain.eq('UNIQUE'); + }); +} + +export async function isQuartz(): Promise { + return usingApi(async api => { + const chain = await api.rpc.system.chain(); + + return chain.eq('QUARTZ'); + }); +} + +let modulesNames: any; +export function getModuleNames(api: ApiPromise): string[] { + if (typeof modulesNames === 'undefined') + modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()); + return modulesNames; +} + +export async function missingRequiredPallets(requiredPallets: string[]): Promise { + return await usingApi(async api => { + const pallets = getModuleNames(api); + + return requiredPallets.filter(p => !pallets.includes(p)); + }); +} + +export async function checkPalletsPresence(requiredPallets: string[]): Promise { + return (await missingRequiredPallets(requiredPallets)).length == 0; +} + +export async function requirePallets(mocha: Context, requiredPallets: string[]) { + const missingPallets = await missingRequiredPallets(requiredPallets); + + if (missingPallets.length > 0) { + const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`; + const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; + const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`; + + console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg); + + mocha.skip(); + } +} + export function normalizeAccountId(input: string | AccountId | CrossAccountId | IKeyringPair): CrossAccountId { if (typeof input === 'string') { if (input.length >= 47) { --- /dev/null +++ b/tests/src/util/playgrounds/index.ts @@ -0,0 +1,93 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +import {IKeyringPair} from '@polkadot/types/types'; +import {UniqueHelper} from './unique'; +import config from '../../config'; +import '../../interfaces/augment-api-events'; +import * as defs from '../../interfaces/definitions'; +import {ApiPromise, WsProvider} from '@polkadot/api'; + + +class SilentLogger { + log(msg: any, level: any): void {} + level = { + ERROR: 'ERROR' as const, + WARNING: 'WARNING' as const, + INFO: 'INFO' as const, + }; +} + + +class DevUniqueHelper extends UniqueHelper { + async connect(wsEndpoint: string, listeners?: any): Promise { + const wsProvider = new WsProvider(wsEndpoint); + this.api = new ApiPromise({ + provider: wsProvider, + signedExtensions: { + ContractHelpers: { + extrinsic: {}, + payload: {}, + }, + FakeTransactionFinalizer: { + extrinsic: {}, + payload: {}, + }, + }, + rpc: { + unique: defs.unique.rpc, + rmrk: defs.rmrk.rpc, + eth: { + feeHistory: { + description: 'Dummy', + params: [], + type: 'u8', + }, + maxPriorityFeePerGas: { + description: 'Dummy', + params: [], + type: 'u8', + }, + }, + }, + }); + await this.api.isReadyOrError; + this.network = await UniqueHelper.detectNetwork(this.api); + } +} + +export const usingPlaygrounds = async (code: (helper: UniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise) => { + // TODO: Remove, this is temporary: Filter unneeded API output + // (Jaco promised it will be removed in the next version) + const consoleErr = console.error; + const consoleLog = console.log; + const consoleWarn = console.warn; + + const outFn = (printer: any) => (...args: any[]) => { + for (const arg of args) { + if (typeof arg !== 'string') + continue; + if (arg.includes('1000:: Normal connection closure' || arg === 'Normal connection closure')) + return; + } + printer(...args); + }; + + console.error = outFn(consoleErr.bind(console)); + console.log = outFn(consoleLog.bind(console)); + console.warn = outFn(consoleWarn.bind(console)); + const helper = new DevUniqueHelper(new SilentLogger()); + + try { + await helper.connect(config.substrateUrl); + const ss58Format = helper.chain.getChainProperties().ss58Format; + const privateKey = (seed: string) => helper.util.fromSeed(seed, ss58Format); + await code(helper, privateKey); + } + finally { + await helper.disconnect(); + console.error = consoleErr; + console.log = consoleLog; + console.warn = consoleWarn; + } +}; \ No newline at end of file --- /dev/null +++ b/tests/src/util/playgrounds/unique.ts @@ -0,0 +1,2490 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable function-call-argument-newline */ +/* eslint-disable no-prototype-builtins */ + +import {ApiPromise, WsProvider, Keyring} from '@polkadot/api'; +import {ApiInterfaceEvents} from '@polkadot/api/types'; +import {IKeyringPair} from '@polkadot/types/types'; +import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto'; + + +const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => { + const address = {} as ICrossAccountId; + if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate; + if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum; + return address; +}; + + +const nesting = { + toChecksumAddress(address: string): string { + if (typeof address === 'undefined') return ''; + + if(!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`); + + address = address.toLowerCase().replace(/^0x/i,''); + const addressHash = keccakAsHex(address).replace(/^0x/i,''); + const checksumAddress = ['0x']; + + for (let i = 0; i < address.length; i++) { + // If ith character is 8 to f then make it uppercase + if (parseInt(addressHash[i], 16) > 7) { + checksumAddress.push(address[i].toUpperCase()); + } else { + checksumAddress.push(address[i]); + } + } + return checksumAddress.join(''); + }, + tokenIdToAddress(collectionId: number, tokenId: number) { + return this.toChecksumAddress(`0xf8238ccfff8ed887463fd5e0${collectionId.toString(16).padStart(8, '0')}${tokenId.toString(16).padStart(8, '0')}`); + }, +}; + + +interface IChainEvent { + data: any; + method: string; + section: string; +} + +interface ITransactionResult { + status: 'Fail' | 'Success'; + result: { + events: { + event: IChainEvent + }[]; + }, + moduleError?: string; +} + +interface ILogger { + log: (msg: any, level?: string) => void; + level: { + ERROR: 'ERROR'; + WARNING: 'WARNING'; + INFO: 'INFO'; + [key: string]: string; + } +} + +interface IUniqueHelperLog { + executedAt: number; + executionTime: number; + type: 'extrinsic' | 'rpc'; + status: 'Fail' | 'Success'; + call: string; + params: any[]; + moduleError?: string; + events?: any; +} + +interface IApiListeners { + connected?: (...args: any[]) => any; + disconnected?: (...args: any[]) => any; + error?: (...args: any[]) => any; + ready?: (...args: any[]) => any; + decorated?: (...args: any[]) => any; +} + +interface ICrossAccountId { + Substrate?: TSubstrateAccount; + Ethereum?: TEthereumAccount; +} + +interface ICrossAccountIdLower { + substrate?: TSubstrateAccount; + ethereum?: TEthereumAccount; +} + +interface ICollectionLimits { + accountTokenOwnershipLimit?: number | null; + sponsoredDataSize?: number | null; + sponsoredDataRateLimit?: {blocks: number} | {sponsoringDisabled: null} | null; + tokenLimit?: number | null; + sponsorTransferTimeout?: number | null; + sponsorApproveTimeout?: number | null; + ownerCanTransfer?: boolean | null; + ownerCanDestroy?: boolean | null; + transfersEnabled?: boolean | null; +} + +interface INestingPermissions { + tokenOwner?: boolean; + collectionAdmin?: boolean; + restricted?: number[] | null; +} + +interface ICollectionPermissions { + access?: 'Normal' | 'AllowList'; + mintMode?: boolean; + nesting?: INestingPermissions; +} + +interface IProperty { + key: string; + value: string; +} + +interface ITokenPropertyPermission { + key: string; + permission: { + mutable: boolean; + tokenOwner: boolean; + collectionAdmin: boolean; + } +} + +interface IToken { + collectionId: number; + tokenId: number; +} + +interface ICollectionCreationOptions { + name: string | number[]; + description: string | number[]; + tokenPrefix: string | number[]; + mode?: { + nft?: null; + refungible?: null; + fungible?: number; + } + permissions?: ICollectionPermissions; + properties?: IProperty[]; + tokenPropertyPermissions?: ITokenPropertyPermission[]; + limits?: ICollectionLimits; + pendingSponsor?: TSubstrateAccount; +} + +interface IChainProperties { + ss58Format: number; + tokenDecimals: number[]; + tokenSymbol: string[] +} + +type TSubstrateAccount = string; +type TEthereumAccount = string; +type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated'; +type TUniqueNetworks = 'opal' | 'quartz' | 'unique'; +type TSigner = IKeyringPair; // | 'string' + +class UniqueUtil { + static transactionStatus = { + NOT_READY: 'NotReady', + FAIL: 'Fail', + SUCCESS: 'Success', + }; + + static chainLogType = { + EXTRINSIC: 'extrinsic', + RPC: 'rpc', + }; + + static getNestingTokenAddress(collectionId: number, tokenId: number) { + return nesting.tokenIdToAddress(collectionId, tokenId); + } + + static getDefaultLogger(): ILogger { + return { + log(msg: any, level = 'INFO') { + console[level.toLocaleLowerCase() === 'error' ? 'error' : 'log'](...(Array.isArray(msg) ? msg : [msg])); + }, + level: { + ERROR: 'ERROR', + WARNING: 'WARNING', + INFO: 'INFO', + }, + }; + } + + static vec2str(arr: string[] | number[]) { + return arr.map(x => String.fromCharCode(parseInt(x.toString()))).join(''); + } + + static str2vec(string: string) { + if (typeof string !== 'string') return string; + return Array.from(string).map(x => x.charCodeAt(0)); + } + + static fromSeed(seed: string, ss58Format = 42) { + const keyring = new Keyring({type: 'sr25519', ss58Format}); + return keyring.addFromUri(seed); + } + + static normalizeSubstrateAddress(address: string, ss58Format = 42) { + return encodeAddress(decodeAddress(address), ss58Format); + } + + static extractCollectionIdFromCreationResult(creationResult: ITransactionResult, label = 'new collection') { + if (creationResult.status !== this.transactionStatus.SUCCESS) { + throw Error(`Unable to create collection for ${label}`); + } + + let collectionId = null; + creationResult.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'common') && (method === 'CollectionCreated')) { + collectionId = parseInt(data[0].toString(), 10); + } + }); + + if (collectionId === null) { + throw Error(`No CollectionCreated event for ${label}`); + } + + return collectionId; + } + + static extractTokensFromCreationResult(creationResult: ITransactionResult, label = 'new tokens') { + if (creationResult.status !== this.transactionStatus.SUCCESS) { + throw Error(`Unable to create tokens for ${label}`); + } + let success = false; + const tokens = [] as any; + creationResult.result.events.forEach(({event: {data, method, section}}) => { + if (method === 'ExtrinsicSuccess') { + success = true; + } else if ((section === 'common') && (method === 'ItemCreated')) { + tokens.push({ + collectionId: parseInt(data[0].toString(), 10), + tokenId: parseInt(data[1].toString(), 10), + owner: data[2].toJSON(), + }); + } + }); + return {success, tokens}; + } + + static extractTokensFromBurnResult(burnResult: ITransactionResult, label = 'burned tokens') { + if (burnResult.status !== this.transactionStatus.SUCCESS) { + throw Error(`Unable to burn tokens for ${label}`); + } + let success = false; + const tokens = [] as any; + burnResult.result.events.forEach(({event: {data, method, section}}) => { + if (method === 'ExtrinsicSuccess') { + success = true; + } else if ((section === 'common') && (method === 'ItemDestroyed')) { + tokens.push({ + collectionId: parseInt(data[0].toString(), 10), + tokenId: parseInt(data[1].toString(), 10), + owner: data[2].toJSON(), + }); + } + }); + return {success, tokens}; + } + + static findCollectionInEvents(events: {event: IChainEvent}[], collectionId: number, expectedSection: string, expectedMethod: string, label?: string) { + let eventId = null; + events.forEach(({event: {data, method, section}}) => { + if ((section === expectedSection) && (method === expectedMethod)) { + eventId = parseInt(data[0].toString(), 10); + } + }); + + if (eventId === null) { + throw Error(`No ${expectedMethod} event for ${label}`); + } + return eventId === collectionId; + } + + static isTokenTransferSuccess(events: {event: IChainEvent}[], collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n) { + const normalizeAddress = (address: string | ICrossAccountId) => { + if(typeof address === 'string') return address; + const obj = {} as any; + Object.keys(address).forEach(k => { + obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum']; + }); + if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)}; + if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()}; + return address; + }; + let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any; + events.forEach(({event: {data, method, section}}) => { + if ((section === 'common') && (method === 'Transfer')) { + const hData = (data as any).toJSON(); + transfer = { + collectionId: hData[0], + tokenId: hData[1], + from: normalizeAddress(hData[2]), + to: normalizeAddress(hData[3]), + amount: BigInt(hData[4]), + }; + } + }); + let isSuccess = parseInt(collectionId.toString()) === transfer.collectionId && parseInt(tokenId.toString()) === transfer.tokenId; + isSuccess = isSuccess && JSON.stringify(normalizeAddress(fromAddressObj)) === JSON.stringify(transfer.from); + isSuccess = isSuccess && JSON.stringify(normalizeAddress(toAddressObj)) === JSON.stringify(transfer.to); + isSuccess = isSuccess && amount === transfer.amount; + return isSuccess; + } +} + + +class ChainHelperBase { + transactionStatus = UniqueUtil.transactionStatus; + chainLogType = UniqueUtil.chainLogType; + util: typeof UniqueUtil; + logger: ILogger; + api: ApiPromise | null; + forcedNetwork: TUniqueNetworks | null; + network: TUniqueNetworks | null; + chainLog: IUniqueHelperLog[]; + + constructor(logger?: ILogger) { + this.util = UniqueUtil; + if (typeof logger == 'undefined') logger = this.util.getDefaultLogger(); + this.logger = logger; + this.api = null; + this.forcedNetwork = null; + this.network = null; + this.chainLog = []; + } + + clearChainLog(): void { + this.chainLog = []; + } + + forceNetwork(value: TUniqueNetworks): void { + this.forcedNetwork = value; + } + + async connect(wsEndpoint: string, listeners?: IApiListeners) { + if (this.api !== null) throw Error('Already connected'); + const {api, network} = await ChainHelperBase.createConnection(wsEndpoint, listeners, this.forcedNetwork); + this.api = api; + this.network = network; + } + + async disconnect() { + if (this.api === null) return; + await this.api.disconnect(); + this.api = null; + this.network = null; + } + + static async detectNetwork(api: ApiPromise): Promise { + const spec = (await api.query.system.lastRuntimeUpgrade()).toJSON() as any; + if(['quartz', 'unique'].indexOf(spec.specName) > -1) return spec.specName; + return 'opal'; + } + + static async detectNetworkByWsEndpoint(wsEndpoint: string): Promise { + const api = new ApiPromise({provider: new WsProvider(wsEndpoint)}); + await api.isReady; + + const network = await this.detectNetwork(api); + + await api.disconnect(); + + return network; + } + + static async createConnection(wsEndpoint: string, listeners?: IApiListeners, network?: TUniqueNetworks | null): Promise<{ + api: ApiPromise; + network: TUniqueNetworks; + }> { + if(typeof network === 'undefined' || network === null) network = 'opal'; + const supportedRPC = { + opal: { + unique: require('@unique-nft/opal-testnet-types/definitions').unique.rpc, + }, + quartz: { + unique: require('@unique-nft/quartz-mainnet-types/definitions').unique.rpc, + }, + unique: { + unique: require('@unique-nft/unique-mainnet-types/definitions').unique.rpc, + }, + }; + if(!supportedRPC.hasOwnProperty(network)) network = await this.detectNetworkByWsEndpoint(wsEndpoint); + const rpc = supportedRPC[network]; + + // TODO: investigate how to replace rpc in runtime + // api._rpcCore.addUserInterfaces(rpc); + + const api = new ApiPromise({provider: new WsProvider(wsEndpoint), rpc}); + + await api.isReadyOrError; + + if (typeof listeners === 'undefined') listeners = {}; + for (const event of ['connected', 'disconnected', 'error', 'ready', 'decorated']) { + if (!listeners.hasOwnProperty(event) || typeof listeners[event as TApiAllowedListeners] === 'undefined') continue; + api.on(event as ApiInterfaceEvents, listeners[event as TApiAllowedListeners] as (...args: any[]) => any); + } + + return {api, network}; + } + + getTransactionStatus(data: {events: {event: IChainEvent}[], status: any}) { + const {events, status} = data; + if (status.isReady) { + return this.transactionStatus.NOT_READY; + } + if (status.isBroadcast) { + return this.transactionStatus.NOT_READY; + } + if (status.isInBlock || status.isFinalized) { + const errors = events.filter(e => e.event.data.method === 'ExtrinsicFailed'); + if (errors.length > 0) { + return this.transactionStatus.FAIL; + } + if (events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) { + return this.transactionStatus.SUCCESS; + } + } + + return this.transactionStatus.FAIL; + } + + signTransaction(sender: TSigner, transaction: any, label = 'transaction', options = null) { + const sign = (callback: any) => { + if(options !== null) return transaction.signAndSend(sender, options, callback); + return transaction.signAndSend(sender, callback); + }; + return new Promise(async (resolve, reject) => { + try { + const unsub = await sign((result: any) => { + const status = this.getTransactionStatus(result); + + if (status === this.transactionStatus.SUCCESS) { + this.logger.log(`${label} successful`); + unsub(); + resolve({result, status}); + } else if (status === this.transactionStatus.FAIL) { + let moduleError = null; + + if (result.hasOwnProperty('dispatchError')) { + const dispatchError = result['dispatchError']; + + if (dispatchError && dispatchError.isModule) { + const modErr = dispatchError.asModule; + const errorMeta = dispatchError.registry.findMetaError(modErr); + + moduleError = `${errorMeta.section}.${errorMeta.name}`; + } + else { + this.logger.log(result, this.logger.level.ERROR); + } + } + + this.logger.log(`Something went wrong with ${label}. Status: ${status}`, this.logger.level.ERROR); + unsub(); + reject({status, moduleError, result}); + } + }); + } catch (e) { + this.logger.log(e, this.logger.level.ERROR); + reject(e); + } + }); + } + + constructApiCall(apiCall: string, params: any[]) { + if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`); + let call = this.api as any; + for(const part of apiCall.slice(4).split('.')) { + call = call[part]; + } + return call(...params); + } + + async executeExtrinsic(sender: TSigner, extrinsic: string, params: any[], expectSuccess=false, failureMessage='expected success') { + if(this.api === null) throw Error('API not initialized'); + if(!extrinsic.startsWith('api.tx.')) throw Error(`${extrinsic} is not transaction`); + + const startTime = (new Date()).getTime(); + let result: ITransactionResult; + let events = []; + try { + result = await this.signTransaction(sender, this.constructApiCall(extrinsic, params), extrinsic) as ITransactionResult; + events = result.result.events.map((x: any) => x.toHuman()); + } + catch(e) { + if(!(e as object).hasOwnProperty('status')) throw e; + result = e as ITransactionResult; + } + + const endTime = (new Date()).getTime(); + + const log = { + executedAt: endTime, + executionTime: endTime - startTime, + type: this.chainLogType.EXTRINSIC, + status: result.status, + call: extrinsic, + params, + } as IUniqueHelperLog; + + if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError; + if(events.length > 0) log.events = events; + + this.chainLog.push(log); + + if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(failureMessage); + return result; + } + + async callRpc(rpc: string, params?: any[]) { + if(typeof params === 'undefined') params = []; + if(this.api === null) throw Error('API not initialized'); + if(!rpc.startsWith('api.rpc.') && !rpc.startsWith('api.query.')) throw Error(`${rpc} is not RPC call`); + + const startTime = (new Date()).getTime(); + let result; + let error = null; + const log = { + type: this.chainLogType.RPC, + call: rpc, + params, + } as IUniqueHelperLog; + + try { + result = await this.constructApiCall(rpc, params); + } + catch(e) { + error = e; + } + + const endTime = (new Date()).getTime(); + + log.executedAt = endTime; + log.status = (error === null ? this.transactionStatus.SUCCESS : this.transactionStatus.FAIL) as 'Fail' | 'Success'; + log.executionTime = endTime - startTime; + + this.chainLog.push(log); + + if(error !== null) throw error; + + return result; + } + + getSignerAddress(signer: IKeyringPair | string): string { + if(typeof signer === 'string') return signer; + return signer.address; + } +} + + +class HelperGroup { + helper: UniqueHelper; + + constructor(uniqueHelper: UniqueHelper) { + this.helper = uniqueHelper; + } +} + + +class CollectionGroup extends HelperGroup { + /** + * Get number of blocks when sponsored transaction is available. + * + * @param collectionId ID of collection + * @param tokenId ID of token + * @param addressObj address for which the sponsorship is checked + * @example await getTokenNextSponsored(1, 2, {Substrate: '5DfhbVfww7ThF8q6f3...'}); + * @returns number of blocks or null if sponsorship hasn't been set + */ + async getTokenNextSponsored(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise { + return (await this.helper.callRpc('api.rpc.unique.nextSponsored', [collectionId, addressObj, tokenId])).toJSON(); + } + + /** + * Get the number of created collections. + * + * @returns number of created collections + */ + async getTotalCount(): Promise { + return (await this.helper.callRpc('api.rpc.unique.collectionStats')).created.toNumber(); + } + + /** + * Get information about the collection with additional data, including the number of tokens it contains, its administrators, the normalized address of the collection's owner, and decoded name and description. + * + * @param collectionId ID of collection + * @example await getData(2) + * @returns collection information object + */ + async getData(collectionId: number): Promise<{ + id: number; + name: string; + description: string; + tokensCount: number; + admins: ICrossAccountId[]; + normalizedOwner: TSubstrateAccount; + raw: any + } | null> { + const collection = await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId]); + const humanCollection = collection.toHuman(), collectionData = { + id: collectionId, name: null, description: null, tokensCount: 0, admins: [], + raw: humanCollection, + } as any, jsonCollection = collection.toJSON(); + if (humanCollection === null) return null; + collectionData.raw.limits = jsonCollection.limits; + collectionData.raw.permissions = jsonCollection.permissions; + collectionData.normalizedOwner = this.helper.address.normalizeSubstrate(collectionData.raw.owner); + for (const key of ['name', 'description']) { + collectionData[key] = this.helper.util.vec2str(humanCollection[key]); + } + + collectionData.tokensCount = (['RFT', 'NFT'].includes(humanCollection.mode)) ? await this.helper[humanCollection.mode.toLocaleLowerCase() as 'nft' | 'rft'].getLastTokenId(collectionId) : 0; + collectionData.admins = await this.getAdmins(collectionId); + + return collectionData; + } + + /** + * Get the normalized addresses of the collection's administrators. + * + * @param collectionId ID of collection + * @example await getAdmins(1) + * @returns array of administrators + */ + async getAdmins(collectionId: number): Promise { + const normalized = []; + for(const admin of (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman()) { + if(admin.Substrate) normalized.push({Substrate: this.helper.address.normalizeSubstrate(admin.Substrate)}); + else normalized.push(admin); + } + return normalized; + } + + /** + * Get the effective limits of the collection instead of null for default values + * + * @param collectionId ID of collection + * @example await getEffectiveLimits(2) + * @returns object of collection limits + */ + async getEffectiveLimits(collectionId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.effectiveCollectionLimits', [collectionId])).toJSON(); + } + + /** + * Burns the collection if the signer has sufficient permissions and collection is empty. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param label extra label for log + * @example await helper.collection.burn(aliceKeyring, 3); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async burn(signer: TSigner, collectionId: number, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.destroyCollection', [collectionId], + true, `Unable to burn collection for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionDestroyed', label); + } + + /** + * Sets the sponsor for the collection (Requires the Substrate address). + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param sponsorAddress Sponsor substrate address + * @param label extra label for log + * @example setSponsor(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...") + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async setSponsor(signer: TSigner, collectionId: number, sponsorAddress: TSubstrateAccount, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.setCollectionSponsor', [collectionId, sponsorAddress], + true, `Unable to set collection sponsor for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet', label); + } + + /** + * Confirms consent to sponsor the collection on behalf of the signer. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param label extra label for log + * @example confirmSponsorship(aliceKeyring, 10) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async confirmSponsorship(signer: TSigner, collectionId: number, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.confirmSponsorship', [collectionId], + true, `Unable to confirm collection sponsorship for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed', label); + } + + /** + * Sets the limits of the collection. At least one limit must be specified for a correct call. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param limits collection limits object + * @param label extra label for log + * @example + * await setLimits( + * aliceKeyring, + * 10, + * { + * sponsorTransferTimeout: 0, + * ownerCanDestroy: false + * } + * ) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async setLimits(signer: TSigner, collectionId: number, limits: ICollectionLimits, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.setCollectionLimits', [collectionId, limits], + true, `Unable to set collection limits for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet', label); + } + + /** + * Changes the owner of the collection to the new Substrate address. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param ownerAddress substrate address of new owner + * @param label extra label for log + * @example changeOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...") + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async changeOwner(signer: TSigner, collectionId: number, ownerAddress: TSubstrateAccount, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.changeCollectionOwner', [collectionId, ownerAddress], + true, `Unable to change collection owner for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged', label); + } + + /** + * Adds a collection administrator. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param adminAddressObj Administrator address (substrate or ethereum) + * @param label extra label for log + * @example addAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async addAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.addCollectionAdmin', [collectionId, adminAddressObj], + true, `Unable to add collection admin for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded', label); + } + + /** + * Removes a collection administrator. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param adminAddressObj Administrator address (substrate or ethereum) + * @param label extra label for log + * @example removeAdmin(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async removeAdmin(signer: TSigner, collectionId: number, adminAddressObj: ICrossAccountId, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.removeCollectionAdmin', [collectionId, adminAddressObj], + true, `Unable to remove collection admin for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved', label); + } + + /** + * Sets onchain permissions for selected collection. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param permissions collection permissions object + * @param label extra label for log + * @example setPermissions(aliceKeyring, 10, {access:'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true}}); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async setPermissions(signer: TSigner, collectionId: number, permissions: ICollectionPermissions, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.setCollectionPermissions', [collectionId, permissions], + true, `Unable to set collection permissions for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet', label); + } + + /** + * Enables nesting for selected collection. If `restricted` set, you can nest only tokens from specified collections. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param permissions nesting permissions object + * @param label extra label for log + * @example enableNesting(aliceKeyring, 10, {collectionAdmin: true, tokenOwner: true}); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async enableNesting(signer: TSigner, collectionId: number, permissions: INestingPermissions, label?: string): Promise { + return await this.setPermissions(signer, collectionId, {nesting: permissions}, label); + } + + /** + * Disables nesting for selected collection. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param label extra label for log + * @example disableNesting(aliceKeyring, 10); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async disableNesting(signer: TSigner, collectionId: number, label?: string): Promise { + return await this.setPermissions(signer, collectionId, {nesting: {tokenOwner: false, collectionAdmin: false}}, label); + } + + /** + * Sets onchain properties to the collection. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param properties array of property objects + * @param label extra label for log + * @example setProperties(aliceKeyring, 10, [{key: "gender", value: "male"}]); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async setProperties(signer: TSigner, collectionId: number, properties: IProperty[], label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.setCollectionProperties', [collectionId, properties], + true, `Unable to set collection properties for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertySet', label); + } + + /** + * Deletes onchain properties from the collection. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param propertyKeys array of property keys to delete + * @param label + * @example deleteProperties(aliceKeyring, 10, ["gender", "age"]); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async deleteProperties(signer: TSigner, collectionId: number, propertyKeys: string[], label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.deleteCollectionProperties', [collectionId, propertyKeys], + true, `Unable to delete collection properties for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPropertyDeleted', label); + } + + /** + * Changes the owner of the token. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param addressObj address of a new owner + * @param amount amount of tokens to be transfered. For NFT must be set to 1n + * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}) + * @returns true if the token success, otherwise false + */ + async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=1n): Promise { + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.transfer', [addressObj, collectionId, tokenId, amount], + true, `Unable to transfer token #${tokenId} from collection #${collectionId}`, + ); + + return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, {Substrate: typeof signer === 'string' ? signer : signer.address}, addressObj, amount); + } + + /** + * + * Change ownership of a token(s) on behalf of the owner. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param fromAddressObj address on behalf of which the token will be sent + * @param toAddressObj new token owner + * @param amount amount of tokens to be transfered. For NFT must be set to 1n + * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg"}, {Ethereum: "0x9F0583DbB85..."}) + * @returns true if the token success, otherwise false + */ + async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=1n): Promise { + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.transferFrom', [fromAddressObj, toAddressObj, collectionId, tokenId, amount], + true, `Unable to transfer token #${tokenId} from collection #${collectionId}`, + ); + return this.helper.util.isTokenTransferSuccess(result.result.events, collectionId, tokenId, fromAddressObj, toAddressObj, amount); + } + + /** + * + * Destroys a concrete instance of NFT/RFT or burns a specified amount of fungible tokens. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param label + * @param amount amount of tokens to be burned. For NFT must be set to 1n + * @example burnToken(aliceKeyring, 10, 5); + * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null``` + */ + async burnToken(signer: TSigner, collectionId: number, tokenId: number, label?: string, amount=1n): Promise<{ + success: boolean, + token: number | null + }> { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const burnResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.burnItem', [collectionId, tokenId, amount], + true, `Unable to burn token for ${label}`, + ); + const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label); + if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens'); + return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null}; + } + + /** + * Destroys a concrete instance of NFT on behalf of the owner + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param fromAddressObj address on behalf of which the token will be burnt + * @param tokenId ID of token + * @param label + * @param amount amount of tokens to be burned. For NFT must be set to 1n + * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."}) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, label?: string, amount=1n): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const burnResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount], + true, `Unable to burn token from for ${label}`, + ); + const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult, label); + return burnedTokens.success && burnedTokens.tokens.length > 0; + } + + /** + * Set, change, or remove approved address to transfer the ownership of the NFT. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param toAddressObj + * @param label + * @param amount amount of token to be approved. For NFT must be set to 1n + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=1n) { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const approveResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.approve', [toAddressObj, collectionId, tokenId, amount], + true, `Unable to approve token for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(approveResult.result.events, collectionId, 'common', 'Approved', label); + } + + /** + * Get the amount of token pieces approved to transfer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param toAccountObj + * @param fromAccountObj + * @example getTokenApprovedPieces(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5ERZNF88Mm7UGfPP3mdG..."}) + * @returns number of approved to transfer pieces + */ + async getTokenApprovedPieces(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId, fromAccountObj: ICrossAccountId): Promise { + return (await this.helper.callRpc('api.rpc.unique.allowance', [collectionId, fromAccountObj, toAccountObj, tokenId])).toBigInt(); + } + + /** + * Get the last created token id + * @param collectionId ID of collection + * @example getLastTokenId(10); + * @returns id of the last created token + */ + async getLastTokenId(collectionId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.lastTokenId', [collectionId])).toNumber(); + } + + /** + * Check if token exists + * @param collectionId ID of collection + * @param tokenId ID of token + * @example isTokenExists(10, 20); + * @returns true if the token exists, otherwise false + */ + async isTokenExists(collectionId: number, tokenId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.tokenExists', [collectionId, tokenId])).toJSON(); + } +} + +class NFTnRFT extends CollectionGroup { + /** + * Get tokens owned by account + * + * @param collectionId ID of collection + * @param addressObj tokens owner + * @example getTokensByAddress(10, {Substrate: "5DyN4Y92vZCjv38fg..."}) + * @returns array of token ids owned by account + */ + async getTokensByAddress(collectionId: number, addressObj: ICrossAccountId): Promise { + return (await this.helper.callRpc('api.rpc.unique.accountTokens', [collectionId, addressObj])).toJSON(); + } + + /** + * Get token data + * @param collectionId ID of collection + * @param tokenId ID of token + * @param blockHashAt + * @param propertyKeys + * @example getToken(10, 5); + * @returns human readable token data + */ + async getToken(collectionId: number, tokenId: number, blockHashAt?: string, propertyKeys?: string[]): Promise<{ + properties: IProperty[]; + owner: ICrossAccountId; + normalizedOwner: ICrossAccountId; + }| null> { + let tokenData; + if(typeof blockHashAt === 'undefined') { + tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId]); + } + else { + if(typeof propertyKeys === 'undefined') { + const collection = (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman(); + if(!collection) return null; + propertyKeys = collection.tokenPropertyPermissions.map((x: ITokenPropertyPermission) => x.key); + } + tokenData = await this.helper.callRpc('api.rpc.unique.tokenData', [collectionId, tokenId, propertyKeys, blockHashAt]); + } + tokenData = tokenData.toHuman(); + if (tokenData === null || tokenData.owner === null) return null; + const owner = {} as any; + for (const key of Object.keys(tokenData.owner)) { + owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key]; + } + tokenData.normalizedOwner = crossAccountIdFromLower(owner); + return tokenData; + } + + /** + * Set permissions to change token properties + * @param signer keyring of signer + * @param collectionId ID of collection + * @param permissions permissions to change a property by the collection owner or admin + * @param label + * @example setTokenPropertyPermissions( + * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}] + * ) + * @returns true if extrinsic success otherwise false + */ + async setTokenPropertyPermissions(signer: TSigner, collectionId: number, permissions: ITokenPropertyPermission[], label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.setTokenPropertyPermissions', [collectionId, permissions], + true, `Unable to set token property permissions for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'PropertyPermissionSet', label); + } + + /** + * Set token properties + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param properties + * @param label + * @example setTokenProperties(aliceKeyring, 10, 5, [{key: "gender", value: "female"}, {key: "age", value: "23"}]) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async setTokenProperties(signer: TSigner, collectionId: number, tokenId: number, properties: IProperty[], label?: string): Promise { + if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.setTokenProperties', [collectionId, tokenId, properties], + true, `Unable to set token properties for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertySet', label); + } + + /** + * Delete the provided properties of a token + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param propertyKeys property keys to be deleted + * @param label + * @example deleteTokenProperties(aliceKeyring, 10, 5, ["gender", "age"]) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async deleteTokenProperties(signer: TSigner, collectionId: number, tokenId: number, propertyKeys: string[], label?: string): Promise { + if(typeof label === 'undefined') label = `token #${tokenId} from collection #${collectionId}`; + const result = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.deleteTokenProperties', [collectionId, tokenId, propertyKeys], + true, `Unable to delete token properties for ${label}`, + ); + + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'TokenPropertyDeleted', label); + } + + /** + * Mint new collection + * @param signer keyring of signer + * @param collectionOptions basic collection options and properties + * @param mode NFT or RFT type of a collection + * @param errorLabel + * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT") + * @returns object of the created collection + */ + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT', errorLabel = 'Unable to mint collection'): Promise { + collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object + collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null}; + for (const key of ['name', 'description', 'tokenPrefix']) { + if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string); + } + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createCollectionEx', [collectionOptions], + true, errorLabel, + ); + return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel)); + } + + getCollectionObject(collectionId: number): any { + return null; + } + + getTokenObject(collectionId: number, tokenId: number): any { + return null; + } +} + + +class NFTGroup extends NFTnRFT { + /** + * Get collection object + * @param collectionId ID of collection + * @example getCollectionObject(2); + * @returns instance of UniqueNFTCollection + */ + getCollectionObject(collectionId: number): UniqueNFTCollection { + return new UniqueNFTCollection(collectionId, this.helper); + } + + /** + * Get token object + * @param collectionId ID of collection + * @param tokenId ID of token + * @example getTokenObject(10, 5); + * @returns instance of UniqueNFTToken + */ + getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken { + return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId)); + } + + /** + * Get token's owner + * @param collectionId ID of collection + * @param tokenId ID of token + * @param blockHashAt + * @example getTokenOwner(10, 5); + * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."} + */ + async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise { + let owner; + if (typeof blockHashAt === 'undefined') { + owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]); + } else { + owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]); + } + return crossAccountIdFromLower(owner.toJSON()); + } + + /** + * Is token approved to transfer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param toAccountObj address to be approved + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async isTokenApproved(collectionId: number, tokenId: number, toAccountObj: ICrossAccountId): Promise { + return (await this.getTokenApprovedPieces(collectionId, tokenId, toAccountObj, await this.getTokenOwner(collectionId, tokenId))) === 1n; + } + + /** + * Changes the owner of the token. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param addressObj address of a new owner + * @example transferToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise { + return await super.transferToken(signer, collectionId, tokenId, addressObj, 1n); + } + + /** + * + * Change ownership of a NFT on behalf of the owner. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param fromAddressObj address on behalf of which the token will be sent + * @param toAddressObj new token owner + * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Ethereum: "0x9F0583DbB85..."}) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId): Promise { + return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n); + } + + /** + * Recursively find the address that owns the token + * @param collectionId ID of collection + * @param tokenId ID of token + * @param blockHashAt + * @example getTokenTopmostOwner(10, 5); + * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."} + */ + async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise { + let owner; + if (typeof blockHashAt === 'undefined') { + owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]); + } else { + owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]); + } + + if (owner === null) return null; + + owner = owner.toHuman(); + + return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner; + } + + /** + * Get tokens nested in the provided token + * @param collectionId ID of collection + * @param tokenId ID of token + * @param blockHashAt + * @example getTokenChildren(10, 5); + * @returns tokens whose depth of nesting is <= 5 + */ + async getTokenChildren(collectionId: number, tokenId: number, blockHashAt?: string): Promise { + let children; + if(typeof blockHashAt === 'undefined') { + children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId]); + } else { + children = await this.helper.callRpc('api.rpc.unique.tokenChildren', [collectionId, tokenId, blockHashAt]); + } + + return children.toJSON().map((x: any) => { + return {collectionId: x.collection, tokenId: x.token}; + }); + } + + /** + * Nest one token into another + * @param signer keyring of signer + * @param tokenObj token to be nested + * @param rootTokenObj token to be parent + * @param label + * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, label='nest token'): Promise { + const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)}; + const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress); + if(!result) { + throw Error(`Unable to nest token for ${label}`); + } + return result; + } + + /** + * Remove token from nested state + * @param signer keyring of signer + * @param tokenObj token to unnest + * @param rootTokenObj parent of a token + * @param toAddressObj address of a new token owner + * @param label + * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."}); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId, label='unnest token'): Promise { + const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)}; + const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj); + if(!result) { + throw Error(`Unable to unnest token for ${label}`); + } + return result; + } + + /** + * Mint new collection + * @param signer keyring of signer + * @param collectionOptions Collection options + * @param label + * @example + * mintCollection(aliceKeyring, { + * name: 'New', + * description: 'New collection', + * tokenPrefix: 'NEW', + * }) + * @returns object of the created collection + */ + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise { + return await super.mintCollection(signer, collectionOptions, 'NFT', `Unable to mint NFT collection for ${label}`) as UniqueNFTCollection; + } + + /** + * Mint new token + * @param signer keyring of signer + * @param data token data + * @param label + * @returns created token object + */ + async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${data.collectionId}`; + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, { + nft: { + properties: data.properties, + }, + }], + true, `Unable to mint NFT token for ${label}`, + ); + const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label); + if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens'); + if (createdTokens.tokens.length < 1) throw Error('No tokens minted'); + return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId); + } + + /** + * Mint multiple NFT tokens + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokens array of tokens with owner and properties + * @param label + * @example + * mintMultipleTokens(aliceKeyring, 10, [{ + * owner: {Substrate: "5DyN4Y92vZCjv38fg..."}, + * properties: [{key: "gender", value: "male"},{key: "age", value: "45"}], + * },{ + * owner: {Ethereum: "0x9F0583DbB855d..."}, + * properties: [{key: "gender", value: "female"},{key: "age", value: "22"}], + * }]); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}], + true, `Unable to mint NFT tokens for ${label}`, + ); + const collection = this.getCollectionObject(collectionId); + return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + } + + /** + * Mint multiple NFT tokens with one owner + * @param signer keyring of signer + * @param collectionId ID of collection + * @param owner tokens owner + * @param tokens array of tokens with owner and properties + * @param label + * @example + * mintMultipleTokensWithOneOwner(aliceKeyring, 10, "5DyN4Y92vZCjv38fg...", [{ + * properties: [{ + * key: "gender", + * value: "female", + * },{ + * key: "age", + * value: "33", + * }], + * }]); + * @returns array of newly created tokens + */ + async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[], label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const rawTokens = []; + for (const token of tokens) { + const raw = {NFT: {properties: token.properties}}; + rawTokens.push(raw); + } + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens], + true, `Unable to mint NFT tokens for ${label}`, + ); + const collection = this.getCollectionObject(collectionId); + return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + } + + /** + * Destroys a concrete instance of NFT. + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param label + * @example burnToken(aliceKeyring, 10, 5); + * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null``` + */ + async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string): Promise<{ success: boolean; token: number | null; }> { + return await super.burnToken(signer, collectionId, tokenId, label, 1n); + } + + /** + * Set, change, or remove approved address to transfer the ownership of the NFT. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param toAddressObj address to approve + * @param label + * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string) { + return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, 1n); + } +} + + +class RFTGroup extends NFTnRFT { + /** + * Get collection object + * @param collectionId ID of collection + * @example getCollectionObject(2); + * @returns instance of UniqueRFTCollection + */ + getCollectionObject(collectionId: number): UniqueRFTCollection { + return new UniqueRFTCollection(collectionId, this.helper); + } + + /** + * Get token object + * @param collectionId ID of collection + * @param tokenId ID of token + * @example getTokenObject(10, 5); + * @returns instance of UniqueNFTToken + */ + getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken { + return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId)); + } + + /** + * Get top 10 token owners with the largest number of pieces + * @param collectionId ID of collection + * @param tokenId ID of token + * @example getTokenTop10Owners(10, 5); + * @returns array of top 10 owners + */ + async getTokenTop10Owners(collectionId: number, tokenId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower); + } + + /** + * Get number of pieces owned by address + * @param collectionId ID of collection + * @param tokenId ID of token + * @param addressObj address token owner + * @example getTokenBalance(10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}); + * @returns number of pieces ownerd by address + */ + async getTokenBalance(collectionId: number, tokenId: number, addressObj: ICrossAccountId): Promise { + return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, tokenId])).toBigInt(); + } + + /** + * Transfer pieces of token to another address + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param addressObj address of a new owner + * @param amount number of pieces to be transfered + * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2000n) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferToken(signer: TSigner, collectionId: number, tokenId: number, addressObj: ICrossAccountId, amount=100n): Promise { + return await super.transferToken(signer, collectionId, tokenId, addressObj, amount); + } + + /** + * Change ownership of some pieces of RFT on behalf of the owner. + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param fromAddressObj address on behalf of which the token will be sent + * @param toAddressObj new token owner + * @param amount number of pieces to be transfered + * @example transferTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, {Substrate: "5DfhbVfww7ThF8q6f3i..."}, 2000n) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n): Promise { + return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, amount); + } + + /** + * Mint new collection + * @param signer keyring of signer + * @param collectionOptions Collection options + * @param label + * @example + * mintCollection(aliceKeyring, { + * name: 'New', + * description: 'New collection', + * tokenPrefix: 'NEW', + * }) + * @returns object of the created collection + */ + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, label = 'new collection'): Promise { + return await super.mintCollection(signer, collectionOptions, 'RFT', `Unable to mint RFT collection for ${label}`) as UniqueRFTCollection; + } + + /** + * Mint new token + * @param signer keyring of signer + * @param data token data + * @param label + * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n}); + * @returns created token object + */ + async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${data.collectionId}`; + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, { + refungible: { + pieces: data.pieces, + properties: data.properties, + }, + }], + true, `Unable to mint RFT token for ${label}`, + ); + const createdTokens = this.helper.util.extractTokensFromCreationResult(creationResult, label); + if (createdTokens.tokens.length > 1) throw Error('Minted multiple tokens'); + if (createdTokens.tokens.length < 1) throw Error('No tokens minted'); + return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId); + } + + async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string): Promise { + throw Error('Not implemented'); + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createMultipleItemsEx', [collectionId, {RefungibleMultipleOwners: tokens}], + true, `Unable to mint RFT tokens for ${label}`, + ); + const collection = this.getCollectionObject(collectionId); + return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + } + + /** + * Mint multiple RFT tokens with one owner + * @param signer keyring of signer + * @param collectionId ID of collection + * @param owner tokens owner + * @param tokens array of tokens with properties and pieces + * @param label + * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]); + * @returns array of newly created RFT tokens + */ + async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[], label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const rawTokens = []; + for (const token of tokens) { + const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}}; + rawTokens.push(raw); + } + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens], + true, `Unable to mint RFT tokens for ${label}`, + ); + const collection = this.getCollectionObject(collectionId); + return this.helper.util.extractTokensFromCreationResult(creationResult, label).tokens.map((x: IToken) => collection.getTokenObject(x.tokenId)); + } + + /** + * Destroys a concrete instance of RFT. + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param label + * @param amount number of pieces to be burnt + * @example burnToken(aliceKeyring, 10, 5); + * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null``` + */ + async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, label?: string, amount=100n): Promise<{ success: boolean; token: number | null; }> { + return await super.burnToken(signer, collectionId, tokenId, label, amount); + } + + /** + * Set, change, or remove approved address to transfer the ownership of the RFT. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param toAddressObj address to approve + * @param label + * @param amount number of pieces to be approved + * @example approveToken(aliceKeyring, 10, 5, {Substrate: "5GHoZe9c73RYbVzq..."}, "", 10000n); + * @returns true if the token success, otherwise false + */ + async approveToken(signer: IKeyringPair, collectionId: number, tokenId: number, toAddressObj: ICrossAccountId, label?: string, amount=100n) { + return super.approveToken(signer, collectionId, tokenId, toAddressObj, label, amount); + } + + /** + * Get total number of pieces + * @param collectionId ID of collection + * @param tokenId ID of token + * @example getTokenTotalPieces(10, 5); + * @returns number of pieces + */ + async getTokenTotalPieces(collectionId: number, tokenId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, tokenId])).unwrap().toBigInt(); + } + + /** + * Change number of token pieces. Signer must be the owner of all token pieces. + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param amount new number of pieces + * @param label + * @example repartitionToken(aliceKeyring, 10, 5, 12345n); + * @returns true if the repartion was success, otherwise false + */ + async repartitionToken(signer: TSigner, collectionId: number, tokenId: number, amount: bigint, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const currentAmount = await this.getTokenTotalPieces(collectionId, tokenId); + const repartitionResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.repartition', [collectionId, tokenId, amount], + true, `Unable to repartition RFT token for ${label}`, + ); + if(currentAmount < amount) return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemCreated', label); + return this.helper.util.findCollectionInEvents(repartitionResult.result.events, collectionId, 'common', 'ItemDestroyed', label); + } +} + + +class FTGroup extends CollectionGroup { + /** + * Get collection object + * @param collectionId ID of collection + * @example getCollectionObject(2); + * @returns instance of UniqueFTCollection + */ + getCollectionObject(collectionId: number): UniqueFTCollection { + return new UniqueFTCollection(collectionId, this.helper); + } + + /** + * Mint new fungible collection + * @param signer keyring of signer + * @param collectionOptions Collection options + * @param decimalPoints number of token decimals + * @param errorLabel + * @example + * mintCollection(aliceKeyring, { + * name: 'New', + * description: 'New collection', + * tokenPrefix: 'NEW', + * }, 18) + * @returns newly created fungible collection + */ + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0, errorLabel = 'Unable to mint collection'): Promise { + collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object + if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions'); + collectionOptions.mode = {fungible: decimalPoints}; + for (const key of ['name', 'description', 'tokenPrefix']) { + if (typeof collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] === 'string') collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] = this.helper.util.str2vec(collectionOptions[key as 'name' | 'description' | 'tokenPrefix'] as string); + } + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createCollectionEx', [collectionOptions], + true, errorLabel, + ); + return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult, errorLabel)); + } + + /** + * Mint tokens + * @param signer keyring of signer + * @param collectionId ID of collection + * @param owner address owner of new tokens + * @param amount amount of tokens to be meanted + * @param label + * @example mintTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq"}, 1000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async mintTokens(signer: TSigner, collectionId: number, owner: ICrossAccountId | string, amount: bigint, label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createItem', [collectionId, (typeof owner === 'string') ? {Substrate: owner} : owner, { + fungible: { + value: amount, + }, + }], + true, `Unable to mint fungible tokens for ${label}`, + ); + return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label); + } + + /** + * Mint multiple Fungible tokens with one owner + * @param signer keyring of signer + * @param collectionId ID of collection + * @param owner tokens owner + * @param tokens array of tokens with properties and pieces + * @param label + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string): Promise { + if(typeof label === 'undefined') label = `collection #${collectionId}`; + const rawTokens = []; + for (const token of tokens) { + const raw = {Fungible: {Value: token.value}}; + rawTokens.push(raw); + } + const creationResult = await this.helper.executeExtrinsic( + signer, + 'api.tx.unique.createMultipleItems', [collectionId, owner, rawTokens], + true, `Unable to mint RFT tokens for ${label}`, + ); + return this.helper.util.findCollectionInEvents(creationResult.result.events, collectionId, 'common', 'ItemCreated', label); + } + + /** + * Get the top 10 owners with the largest balance for the Fungible collection + * @param collectionId ID of collection + * @example getTop10Owners(10); + * @returns array of ```ICrossAccountId``` + */ + async getTop10Owners(collectionId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower); + } + + /** + * Get account balance + * @param collectionId ID of collection + * @param addressObj address of owner + * @example getBalance(10, {Substrate: "5GHoZe9c73RYbVzq..."}) + * @returns amount of fungible tokens owned by address + */ + async getBalance(collectionId: number, addressObj: ICrossAccountId): Promise { + return (await this.helper.callRpc('api.rpc.unique.balance', [collectionId, addressObj, 0])).toBigInt(); + } + + /** + * Transfer tokens to address + * @param signer keyring of signer + * @param collectionId ID of collection + * @param toAddressObj address recepient + * @param amount amount of tokens to be sent + * @example transfer(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transfer(signer: TSigner, collectionId: number, toAddressObj: ICrossAccountId, amount: bigint) { + return await super.transferToken(signer, collectionId, 0, toAddressObj, amount); + } + + /** + * Transfer some tokens on behalf of the owner. + * @param signer keyring of signer + * @param collectionId ID of collection + * @param fromAddressObj address on behalf of which tokens will be sent + * @param toAddressObj address where token to be sent + * @param amount number of tokens to be sent + * @example transferFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, {Substrate: "5DfhbVfww7ThF8q6f3ij..."}, 10000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) { + return await super.transferTokenFrom(signer, collectionId, 0, fromAddressObj, toAddressObj, amount); + } + + /** + * Destroy some amount of tokens + * @param signer keyring of signer + * @param collectionId ID of collection + * @param amount amount of tokens to be destroyed + * @param label + * @example burnTokens(aliceKeyring, 10, 1000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async burnTokens(signer: IKeyringPair, collectionId: number, amount=100n, label?: string): Promise { + return (await super.burnToken(signer, collectionId, 0, label, amount)).success; + } + + /** + * Burn some tokens on behalf of the owner. + * @param signer keyring of signer + * @param collectionId ID of collection + * @param fromAddressObj address on behalf of which tokens will be burnt + * @param amount amount of tokens to be burnt + * @param label + * @example burnTokensFrom(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=100n, label?: string): Promise { + return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount); + } + + /** + * Get total collection supply + * @param collectionId + * @returns + */ + async getTotalPieces(collectionId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.totalPieces', [collectionId, 0])).unwrap().toBigInt(); + } + + /** + * Set, change, or remove approved address to transfer tokens. + * + * @param signer keyring of signer + * @param collectionId ID of collection + * @param toAddressObj address to be approved + * @param amount amount of tokens to be approved + * @param label + * @example approveTokens(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, 1000n) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async approveTokens(signer: IKeyringPair, collectionId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) { + return super.approveToken(signer, collectionId, 0, toAddressObj, label, amount); + } + + /** + * Get amount of fungible tokens approved to transfer + * @param collectionId ID of collection + * @param fromAddressObj owner of tokens + * @param toAddressObj the address approved for the transfer of tokens on behalf of the owner + * @returns number of tokens approved for the transfer + */ + async getApprovedTokens(collectionId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return super.getTokenApprovedPieces(collectionId, 0, toAddressObj, fromAddressObj); + } +} + + +class ChainGroup extends HelperGroup { + /** + * Get system properties of a chain + * @example getChainProperties(); + * @returns ss58Format, token decimals, and token symbol + */ + getChainProperties(): IChainProperties { + const properties = (this.helper.api as any).registry.getChainProperties().toJSON(); + return { + ss58Format: properties.ss58Format.toJSON(), + tokenDecimals: properties.tokenDecimals.toJSON(), + tokenSymbol: properties.tokenSymbol.toJSON(), + }; + } + + /** + * Get chain header + * @example getLatestBlockNumber(); + * @returns the number of the last block + */ + async getLatestBlockNumber(): Promise { + return (await this.helper.callRpc('api.rpc.chain.getHeader')).number.toNumber(); + } + + /** + * Get block hash by block number + * @param blockNumber number of block + * @example getBlockHashByNumber(12345); + * @returns hash of a block + */ + async getBlockHashByNumber(blockNumber: number): Promise { + const blockHash = (await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])).toJSON(); + if(blockHash === '0x0000000000000000000000000000000000000000000000000000000000000000') return null; + return blockHash; + } + + /** + * Get account nonce + * @param address substrate address + * @example getNonce("5GrwvaEF5zXb26Fz..."); + * @returns number, account's nonce + */ + async getNonce(address: TSubstrateAccount): Promise { + return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber(); + } +} + + +class BalanceGroup extends HelperGroup { + /** + * Representation of the native token in the smallest unit + * @example getOneTokenNominal() + * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ. + */ + getOneTokenNominal(): bigint { + const chainProperties = this.helper.chain.getChainProperties(); + return 10n ** BigInt((chainProperties.tokenDecimals || [18])[0]); + } + + /** + * Get substrate address balance + * @param address substrate address + * @example getSubstrate("5GrwvaEF5zXb26Fz...") + * @returns amount of tokens on address + */ + async getSubstrate(address: TSubstrateAccount): Promise { + return (await this.helper.callRpc('api.query.system.account', [address])).data.free.toBigInt(); + } + + /** + * Get ethereum address balance + * @param address ethereum address + * @example getEthereum("0x9F0583DbB855d...") + * @returns amount of tokens on address + */ + async getEthereum(address: TEthereumAccount): Promise { + return (await this.helper.callRpc('api.rpc.eth.getBalance', [address])).toBigInt(); + } + + /** + * Transfer tokens to substrate address + * @param signer keyring of signer + * @param address substrate address of a recepient + * @param amount amount of tokens to be transfered + * @example transferToSubstrate(aliceKeyring, "5GrwvaEF5zXb26Fz...", 100_000_000_000n); + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async transferToSubstrate(signer: TSigner, address: TSubstrateAccount, amount: bigint | string): Promise { + const result = await this.helper.executeExtrinsic(signer, 'api.tx.balances.transfer', [address, amount], true, `Unable to transfer balance from ${this.helper.getSignerAddress(signer)} to ${address}`); + + let transfer = {from: null, to: null, amount: 0n} as any; + result.result.events.forEach(({event: {data, method, section}}) => { + if ((section === 'balances') && (method === 'Transfer')) { + transfer = { + from: this.helper.address.normalizeSubstrate(data[0]), + to: this.helper.address.normalizeSubstrate(data[1]), + amount: BigInt(data[2]), + }; + } + }); + let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from; + isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to; + isSuccess = isSuccess && BigInt(amount) === transfer.amount; + return isSuccess; + } +} + + +class AddressGroup extends HelperGroup { + /** + * Normalizes the address to the specified ss58 format, by default ```42```. + * @param address substrate address + * @param ss58Format format for address conversion, by default ```42``` + * @example normalizeSubstrate("unjKJQJrRd238pkUZZvzDQrfKuM39zBSnQ5zjAGAGcdRhaJTx") // returns 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY + * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation + */ + normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount { + return this.helper.util.normalizeSubstrateAddress(address, ss58Format); + } + + /** + * Get address in the connected chain format + * @param address substrate address + * @example normalizeSubstrateToChainFormat("5GrwvaEF5zXb26Fz...") // returns unjKJQJrRd238pkUZZ... for Unique Network + * @returns address in chain format + */ + async normalizeSubstrateToChainFormat(address: TSubstrateAccount): Promise { + const info = this.helper.chain.getChainProperties(); + return encodeAddress(decodeAddress(address), info.ss58Format); + } + + /** + * Get substrate mirror of an ethereum address + * @param ethAddress ethereum address + * @param toChainFormat false for normalized account + * @example ethToSubstrate('0x9F0583DbB855d...') + * @returns substrate mirror of a provided ethereum address + */ + async ethToSubstrate(ethAddress: TEthereumAccount, toChainFormat=false): Promise { + if(!toChainFormat) return evmToAddress(ethAddress); + const info = this.helper.chain.getChainProperties(); + return evmToAddress(ethAddress, info.ss58Format); + } + + /** + * Get ethereum mirror of a substrate address + * @param subAddress substrate account + * @example substrateToEth("5DnSF6RRjwteE3BrC...") + * @returns ethereum mirror of a provided substrate address + */ + substrateToEth(subAddress: TSubstrateAccount): TEthereumAccount { + return nesting.toChecksumAddress('0x' + Array.from(addressToEvm(subAddress), i => i.toString(16).padStart(2, '0')).join('')); + } +} + + +export class UniqueHelper extends ChainHelperBase { + chain: ChainGroup; + balance: BalanceGroup; + address: AddressGroup; + collection: CollectionGroup; + nft: NFTGroup; + rft: RFTGroup; + ft: FTGroup; + + constructor(logger?: ILogger) { + super(logger); + this.chain = new ChainGroup(this); + this.balance = new BalanceGroup(this); + this.address = new AddressGroup(this); + this.collection = new CollectionGroup(this); + this.nft = new NFTGroup(this); + this.rft = new RFTGroup(this); + this.ft = new FTGroup(this); + } +} + + +class UniqueCollectionBase { + helper: UniqueHelper; + collectionId: number; + + constructor(collectionId: number, uniqueHelper: UniqueHelper) { + this.collectionId = collectionId; + this.helper = uniqueHelper; + } + + async getData() { + return await this.helper.collection.getData(this.collectionId); + } + + async getLastTokenId() { + return await this.helper.collection.getLastTokenId(this.collectionId); + } + + async isTokenExists(tokenId: number) { + return await this.helper.collection.isTokenExists(this.collectionId, tokenId); + } + + async getAdmins() { + return await this.helper.collection.getAdmins(this.collectionId); + } + + async getEffectiveLimits() { + return await this.helper.collection.getEffectiveLimits(this.collectionId); + } + + async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount, label?: string) { + return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress, label); + } + + async confirmSponsorship(signer: TSigner, label?: string) { + return await this.helper.collection.confirmSponsorship(signer, this.collectionId, label); + } + + async setLimits(signer: TSigner, limits: ICollectionLimits, label?: string) { + return await this.helper.collection.setLimits(signer, this.collectionId, limits, label); + } + + async changeOwner(signer: TSigner, ownerAddress: TSubstrateAccount, label?: string) { + return await this.helper.collection.changeOwner(signer, this.collectionId, ownerAddress, label); + } + + async addAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) { + return await this.helper.collection.addAdmin(signer, this.collectionId, adminAddressObj, label); + } + + async removeAdmin(signer: TSigner, adminAddressObj: ICrossAccountId, label?: string) { + return await this.helper.collection.removeAdmin(signer, this.collectionId, adminAddressObj, label); + } + + async setProperties(signer: TSigner, properties: IProperty[], label?: string) { + return await this.helper.collection.setProperties(signer, this.collectionId, properties, label); + } + + async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) { + return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys, label); + } + + async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) { + return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj); + } + + async setPermissions(signer: TSigner, permissions: ICollectionPermissions, label?: string) { + return await this.helper.collection.setPermissions(signer, this.collectionId, permissions, label); + } + + async enableNesting(signer: TSigner, permissions: INestingPermissions, label?: string) { + return await this.helper.collection.enableNesting(signer, this.collectionId, permissions, label); + } + + async disableNesting(signer: TSigner, label?: string) { + return await this.helper.collection.disableNesting(signer, this.collectionId, label); + } + + async burn(signer: TSigner, label?: string) { + return await this.helper.collection.burn(signer, this.collectionId, label); + } +} + + +class UniqueNFTCollection extends UniqueCollectionBase { + getTokenObject(tokenId: number) { + return new UniqueNFTToken(tokenId, this); + } + + async getTokensByAddress(addressObj: ICrossAccountId) { + return await this.helper.nft.getTokensByAddress(this.collectionId, addressObj); + } + + async getToken(tokenId: number, blockHashAt?: string) { + return await this.helper.nft.getToken(this.collectionId, tokenId, blockHashAt); + } + + async getTokenOwner(tokenId: number, blockHashAt?: string) { + return await this.helper.nft.getTokenOwner(this.collectionId, tokenId, blockHashAt); + } + + async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) { + return await this.helper.nft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt); + } + + async getTokenChildren(tokenId: number, blockHashAt?: string) { + return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt); + } + + async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) { + return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj); + } + + async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.helper.nft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj); + } + + async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, label?: string) { + return await this.helper.nft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label); + } + + async isTokenApproved(tokenId: number, toAddressObj: ICrossAccountId) { + return await this.helper.nft.isTokenApproved(this.collectionId, tokenId, toAddressObj); + } + + async mintToken(signer: TSigner, owner: ICrossAccountId, properties?: IProperty[], label?: string) { + return await this.helper.nft.mintToken(signer, {collectionId: this.collectionId, owner, properties}, label); + } + + async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[], label?: string) { + return await this.helper.nft.mintMultipleTokens(signer, this.collectionId, tokens, label); + } + + async burnToken(signer: TSigner, tokenId: number, label?: string) { + return await this.helper.nft.burnToken(signer, this.collectionId, tokenId, label); + } + + async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) { + return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties, label); + } + + async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) { + return await this.helper.nft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label); + } + + async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) { + return await this.helper.nft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label); + } + + async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken, label?: string) { + return await this.helper.nft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj, label); + } + + async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) { + return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj, label); + } +} + + +class UniqueRFTCollection extends UniqueCollectionBase { + getTokenObject(tokenId: number) { + return new UniqueRFTToken(tokenId, this); + } + + async getTokensByAddress(addressObj: ICrossAccountId) { + return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj); + } + + async getTop10TokenOwners(tokenId: number) { + return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId); + } + + async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) { + return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj); + } + + async getTokenTotalPieces(tokenId: number) { + return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId); + } + + async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=100n) { + return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount); + } + + async transferTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) { + return await this.helper.rft.transferTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, toAddressObj, amount); + } + + async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=100n, label?: string) { + return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, label, amount); + } + + async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj); + } + + async repartitionToken(signer: TSigner, tokenId: number, amount: bigint, label?: string) { + return await this.helper.rft.repartitionToken(signer, this.collectionId, tokenId, amount, label); + } + + async mintToken(signer: TSigner, owner: ICrossAccountId, pieces=100n, properties?: IProperty[], label?: string) { + return await this.helper.rft.mintToken(signer, {collectionId: this.collectionId, owner, pieces, properties}, label); + } + + async mintMultipleTokens(signer: TSigner, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[], label?: string) { + return await this.helper.rft.mintMultipleTokens(signer, this.collectionId, tokens, label); + } + + async burnToken(signer: TSigner, tokenId: number, amount=100n, label?: string) { + return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, label, amount); + } + + async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[], label?: string) { + return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties, label); + } + + async deleteTokenProperties(signer: TSigner, tokenId: number, propertyKeys: string[], label?: string) { + return await this.helper.rft.deleteTokenProperties(signer, this.collectionId, tokenId, propertyKeys, label); + } + + async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[], label?: string) { + return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions, label); + } +} + + +class UniqueFTCollection extends UniqueCollectionBase { + async mint(signer: TSigner, owner: ICrossAccountId, amount: bigint, label?: string) { + return await this.helper.ft.mintTokens(signer, this.collectionId, owner, amount, label); + } + + async mintWithOneOwner(signer: TSigner, owner: ICrossAccountId, tokens: {value: bigint}[], label?: string) { + return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, owner, tokens, label); + } + + async getBalance(addressObj: ICrossAccountId) { + return await this.helper.ft.getBalance(this.collectionId, addressObj); + } + + async getTop10Owners() { + return await this.helper.ft.getTop10Owners(this.collectionId); + } + + async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount: bigint) { + return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount); + } + + async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount: bigint) { + return await this.helper.ft.transferFrom(signer, this.collectionId, fromAddressObj, toAddressObj, amount); + } + + async burnTokens(signer: TSigner, amount: bigint, label?: string) { + return await this.helper.ft.burnTokens(signer, this.collectionId, amount, label); + } + + async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount: bigint, label?: string) { + return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount, label); + } + + async getTotalPieces() { + return await this.helper.ft.getTotalPieces(this.collectionId); + } + + async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) { + return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount, label); + } + + async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj); + } +} + + +class UniqueTokenBase implements IToken { + collection: UniqueNFTCollection | UniqueRFTCollection; + collectionId: number; + tokenId: number; + + constructor(tokenId: number, collection: UniqueNFTCollection | UniqueRFTCollection) { + this.collection = collection; + this.collectionId = collection.collectionId; + this.tokenId = tokenId; + } + + async getNextSponsored(addressObj: ICrossAccountId) { + return await this.collection.getTokenNextSponsored(this.tokenId, addressObj); + } + + async setProperties(signer: TSigner, properties: IProperty[], label?: string) { + return await this.collection.setTokenProperties(signer, this.tokenId, properties, label); + } + + async deleteProperties(signer: TSigner, propertyKeys: string[], label?: string) { + return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys, label); + } +} + + +class UniqueNFTToken extends UniqueTokenBase { + collection: UniqueNFTCollection; + + constructor(tokenId: number, collection: UniqueNFTCollection) { + super(tokenId, collection); + this.collection = collection; + } + + async getData(blockHashAt?: string) { + return await this.collection.getToken(this.tokenId, blockHashAt); + } + + async getOwner(blockHashAt?: string) { + return await this.collection.getTokenOwner(this.tokenId, blockHashAt); + } + + async getTopmostOwner(blockHashAt?: string) { + return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt); + } + + async getChildren(blockHashAt?: string) { + return await this.collection.getTokenChildren(this.tokenId, blockHashAt); + } + + async nest(signer: TSigner, toTokenObj: IToken, label?: string) { + return await this.collection.nestToken(signer, this.tokenId, toTokenObj, label); + } + + async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId, label?: string) { + return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj, label); + } + + async transfer(signer: TSigner, addressObj: ICrossAccountId) { + return await this.collection.transferToken(signer, this.tokenId, addressObj); + } + + async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj); + } + + async approve(signer: TSigner, toAddressObj: ICrossAccountId, label?: string) { + return await this.collection.approveToken(signer, this.tokenId, toAddressObj, label); + } + + async isApproved(toAddressObj: ICrossAccountId) { + return await this.collection.isTokenApproved(this.tokenId, toAddressObj); + } + + async burn(signer: TSigner, label?: string) { + return await this.collection.burnToken(signer, this.tokenId, label); + } +} + +class UniqueRFTToken extends UniqueTokenBase { + collection: UniqueRFTCollection; + + constructor(tokenId: number, collection: UniqueRFTCollection) { + super(tokenId, collection); + this.collection = collection; + } + + async getTop10Owners() { + return await this.collection.getTop10TokenOwners(this.tokenId); + } + + async getBalance(addressObj: ICrossAccountId) { + return await this.collection.getTokenBalance(this.tokenId, addressObj); + } + + async getTotalPieces() { + return await this.collection.getTokenTotalPieces(this.tokenId); + } + + async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=100n) { + return await this.collection.transferToken(signer, this.tokenId, addressObj, amount); + } + + async transferFrom(signer: TSigner, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId, amount=100n) { + return await this.collection.transferTokenFrom(signer, this.tokenId, fromAddressObj, toAddressObj, amount); + } + + async approve(signer: TSigner, toAddressObj: ICrossAccountId, amount=100n, label?: string) { + return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount, label); + } + + async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) { + return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj); + } + + async repartition(signer: TSigner, amount: bigint, label?: string) { + return await this.collection.repartitionToken(signer, this.tokenId, amount, label); + } + + async burn(signer: TSigner, amount=100n, label?: string) { + return await this.collection.burnToken(signer, this.tokenId, amount, label); + } +} \ No newline at end of file --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -963,6 +963,13 @@ dependencies: "@types/chai" "*" +"@types/chai-like@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/chai-like/-/chai-like-1.1.1.tgz#c454039b0a2f92664fb5b7b7a2a66c3358783ae7" + integrity sha512-s46EZsupBuVhLn66DbRee5B0SELLmL4nFXVrBiV29BxLGm9Sh7Bful623j3AfiQRu2zAP4cnlZ3ETWB3eWc4bA== + dependencies: + "@types/chai" "*" + "@types/chai@*", "@types/chai@^4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" @@ -1545,6 +1552,11 @@ dependencies: check-error "^1.0.2" +chai-like@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chai-like/-/chai-like-1.1.1.tgz#8c558a414c34514e814d497c772547ceb7958f64" + integrity sha512-VKa9z/SnhXhkT1zIjtPACFWSoWsqVoaz1Vg+ecrKo5DCKVlgL30F/pEyEvXPBOVwCgLZcWUleCM/C1okaKdTTA== + chai@^4.3.6: version "4.3.6" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" -- gitstuff