difftreelog
Merge branch 'develop' into CI-41-forckless-update-parachain-relay-nodata
in: master
155 files changed
.docker/Dockerfile-parachaindiffbeforeafterboth--- 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" && \
.docker/docker-compose-master.yamldiffbeforeafterboth--- /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"
.docker/docker-compose.tmp-master.j2diffbeforeafterboth--- /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 }}"
.gitattributesdiffbeforeafterboth--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,3 @@
* text=auto
*.sh text eol=lf
+*.ts linguist-detectable=false
\ No newline at end of file
.github/workflows/build-test-master.ymldiffbeforeafterboth--- /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
.github/workflows/node_build_test.ymldiffbeforeafterboth--- 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
Cargo.lockdiffbeforeafterboth--- 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 = [
README.mddiffbeforeafterboth--- 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!
client/rpc/Cargo.tomldiffbeforeafterboth--- 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" }
node/cli/Cargo.tomldiffbeforeafterboth--- 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'
node/cli/src/chain_spec.rsdiffbeforeafterboth--- 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;
node/cli/src/command.rsdiffbeforeafterboth--- 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) => {
node/cli/src/service.rsdiffbeforeafterboth--- 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::{
node/rpc/Cargo.tomldiffbeforeafterboth--- 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" }
node/rpc/src/lib.rsdiffbeforeafterboth--- 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,
pallets/common/src/erc.rsdiffbeforeafterboth--- 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<bool> {
+ 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<string> {
+ let mode = match self.collection.mode {
+ CollectionMode::Fungible(_) => "Fungible",
+ CollectionMode::NFT => "NFT",
+ CollectionMode::ReFungible => "ReFungible",
+ };
+ Ok(mode.into())
+ }
}
fn check_is_owner_or_admin<T: Config>(
@@ -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.
pallets/common/src/lib.rsdiffbeforeafterboth--- 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<T>,
+ sender: &T::CrossAccountId,
+ scope: PropertyScope,
+ property_permission: PropertyKeyPermission,
+ ) -> DispatchResult {
collection.check_is_owner_or_admin(sender)?;
let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);
@@ -1125,7 +1146,11 @@
CollectionPropertyPermissions::<T>::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(<Error<T>>::from)?;
@@ -1148,8 +1173,29 @@
sender: &T::CrossAccountId,
property_permissions: Vec<PropertyKeyPermission>,
) -> 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<T>,
+ sender: &T::CrossAccountId,
+ scope: PropertyScope,
+ property_permissions: Vec<PropertyKeyPermission>,
+ ) -> 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(<Error<T>>::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,
pallets/fungible/src/common.rsdiffbeforeafterboth--- 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
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -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
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- 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 @@
<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), false, &Unlimited)?;
let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
}: {<Pallet<T>>::delete_token_properties(&collection, &owner, item, to_delete.into_iter(), &Unlimited)?}
+
+ 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)}
}
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -118,6 +118,10 @@
<SelfWeightOf<T>>::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 {
+ <SelfWeightOf<T>>::token_owner()
+ }
}
fn map_create_data<T: Config>(
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -784,6 +784,23 @@
<PalletCommon<T>>::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<T>,
+ sender: &T::CrossAccountId,
+ scope: PropertyScope,
+ property_permissions: Vec<PropertyKeyPermission>,
+ ) -> DispatchResult {
+ <PalletCommon<T>>::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.
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -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
pallets/nonfungible/src/weights.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -3,7 +3,7 @@
//! Autogenerated weights for pallet_nonfungible
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-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))
+ }
}
pallets/refungible/src/benchmarking.rsdiffbeforeafterboth--- 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<CrossAccountId: Ord>(
users: impl IntoIterator<Item = (CrossAccountId, u128)>,
) -> CreateRefungibleExData<CrossAccountId> {
- let const_data = create_data::<CUSTOM_DATA_LIMIT>();
CreateRefungibleExData {
- const_data,
users: users
.into_iter()
.collect::<BTreeMap<_, _>>()
@@ -44,6 +45,7 @@
properties: Default::default(),
}
}
+
fn create_max_item<T: Config>(
collection: &RefungibleHandle<T>,
sender: &T::CrossAccountId,
@@ -59,11 +61,12 @@
) -> Result<RefungibleHandle<T>, DispatchError> {
create_collection_raw(
owner,
- CollectionMode::NFT,
+ CollectionMode::ReFungible,
<Pallet<T>>::init_collection,
RefungibleHandle::cast,
)
}
+
benchmarks! {
create_item {
bench_init!{
@@ -277,4 +280,21 @@
};
let item = create_max_item(&collection, &sender, [(owner.clone(), 100)])?;
}: {<Pallet<T>>::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)])?;
+
+ }: {<Pallet<T>>::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)])?;
+ }: {<Pallet<T>>::token_owner(collection.id, item)}
}
pallets/refungible/src/common.rsdiffbeforeafterboth--- 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 {
+ <SelfWeightOf<T>>::token_owner()
+ }
}
fn map_create_data<T: Config>(
pallets/refungible/src/erc.rsdiffbeforeafterboth--- 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<T: Config>(
+pub fn balance<T: Config>(
collection: &RefungibleHandle<T>,
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<T: Config>(
+pub fn ensure_single_owner<T: Config>(
collection: &RefungibleHandle<T>,
token: TokenId,
owner_balance: u128,
@@ -788,6 +788,16 @@
.map_err(dispatch_to_evm::<T>)?;
Ok(true)
}
+
+ /// Returns EVM address for refungible token
+ ///
+ /// @param token ID of the token
+ fn token_contract_address(&self, token: uint256) -> Result<address> {
+ Ok(T::EvmTokenAddressMapping::token_to_address(
+ self.id,
+ token.try_into().map_err(|_| "token id overflow")?,
+ ))
+ }
}
#[solidity_interface(
pallets/refungible/src/erc_token.rsdiffbeforeafterboth--- 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<T: Config>(pub RefungibleHandle<T>, pub TokenId);
+#[solidity_interface(name = "ERC1633")]
+impl<T: Config> RefungibleTokenHandle<T> {
+ fn parent_token(&self) -> Result<address> {
+ self.consume_store_reads(2)?;
+ let props = <TokenProperties<T>>::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<uint256> {
+ self.consume_store_reads(2)?;
+ let props = <TokenProperties<T>>::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<T: Config> RefungibleTokenHandle<T> {
+ #[solidity(rename_selector = "setParentNFT")]
+ #[weight(<CommonWeights<T>>::token_owner() + <SelfWeightOf<T>>::set_parent_nft_unchecked())]
+ fn set_parent_nft(
+ &mut self,
+ caller: caller,
+ collection: address,
+ nft_id: uint256,
+ ) -> Result<bool> {
+ 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()?;
+
+ <Pallet<T>>::set_parent_nft(&self.0, self.1, caller, nft_collection, nft_token)
+ .map_err(dispatch_to_evm::<T>)?;
+
+ 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(<StructureWeight<T>>::find_parent());
<Pallet<T>>::transfer(self, &caller, &to, self.1, amount, &budget)
- .map_err(|_| "transfer error")?;
+ .map_err(dispatch_to_evm::<T>)?;
Ok(true)
}
@@ -239,7 +305,10 @@
}
}
-#[solidity_interface(name = "UniqueRefungibleToken", is(ERC20, ERC20UniqueExtensions,))]
+#[solidity_interface(
+ name = "UniqueRefungibleToken",
+ is(ERC20, ERC20UniqueExtensions, ERC1633, ERC1633UniqueExtensions)
+)]
impl<T: Config> RefungibleTokenHandle<T> where T::AccountId: From<[u8; 32]> {}
generate_stubgen!(gen_impl, UniqueRefungibleTokenCall<()>, true);
pallets/refungible/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet18//!19//! The Refungible pallet provides functionality for handling refungible collections and tokens.20//!21//! - [`Config`]22//! - [`RefungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Refungible pallet provides functions for:29//!30//! - RFT collection creation and removal31//! - Minting and burning of RFT tokens32//! - Partition and repartition of RFT tokens33//! - Retrieving number of pieces of RFT token34//! - Retrieving account balances35//! - Transfering RFT token pieces36//! - Burning RFT token pieces37//! - Setting and checking allowance for RFT tokens38//!39//! ### Terminology40//!41//! - **RFT token:** Non fungible token that was partitioned to pieces. If an account owns all42//! of the RFT token pieces than it owns the RFT token and can repartition it.43//!44//! - **RFT Collection:** A collection of RFT tokens. All RFT tokens are part of a collection.45//! Each collection has its own settings and set of permissions.46//!47//! - **RFT token piece:** A fungible part of an RFT token.48//!49//! - **Balance:** RFT token pieces owned by an account50//!51//! - **Allowance:** Maximum number of RFT token pieces that one account is allowed to52//! transfer from the balance of another account53//!54//! - **Burning:** The process of “deleting” a token from a collection or removing token pieces from55//! an account balance.56//!57//! ### Implementations58//!59//! The Refungible pallet provides implementations for the following traits. If these traits provide60//! the functionality that you need, then you can avoid coupling with the Refungible pallet.61//!62//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight63//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing64//! with collections65//! - [`RefungibleExtensions`](pallet_common::RefungibleExtensions): Functions specific for refungible66//! collection67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create RFT collection. RFT collection can be configured to allow or deny access for73//! some accounts.74//! - `destroy_collection` - Destroy exising RFT collection. There should be no tokens in the collection.75//! - `burn` - Burn some amount of RFT token pieces owned by account. Burns the RFT token if no pieces left.76//! - `transfer` - Transfer some amount of RFT token pieces. Transfers should be enabled for RFT collection.77//! Nests the RFT token if RFT token pieces are sent to another token.78//! - `create_item` - Mint RFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account to transfer balance from sender's account.80//! - `repartition` - Repartition token to selected number of pieces. Sender should own all existing pieces.81//!82//! ## Assumptions83//!84//! * Total number of pieces for one token shouldn't exceed `up_data_structs::MAX_REFUNGIBLE_PIECES`.85//! * Total number of tokens of all types shouldn't be greater than `up_data_structs::MAX_TOKEN_PREFIX_LENGTH`.86//! * Sender should be in collection's allow list to perform operations on tokens.8788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use codec::{Encode, Decode, MaxEncodedLen};94use core::ops::Deref;95use evm_coder::ToLog;96use frame_support::{97 BoundedVec, ensure, fail, storage::with_transaction, transactional, pallet_prelude::ConstU32,98};99use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};100use pallet_evm_coder_substrate::WithRecorder;101use pallet_common::{102 CommonCollectionOperations, Error as CommonError, Event as CommonEvent,103 eth::collection_id_to_address, Pallet as PalletCommon,104};105use pallet_structure::Pallet as PalletStructure;106use scale_info::TypeInfo;107use sp_core::H160;108use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};109use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};110use up_data_structs::{111 AccessMode, budget::Budget, CollectionId, CreateCollectionData, CustomDataLimit,112 mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, MAX_ITEMS_PER_BATCH, TokenId, Property,113 PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,114 TrySetProperty, CollectionPropertiesVec,115};116use frame_support::BoundedBTreeMap;117use derivative::Derivative;118119pub use pallet::*;120#[cfg(feature = "runtime-benchmarks")]121pub mod benchmarking;122pub mod common;123pub mod erc;124pub mod erc_token;125pub mod weights;126127#[derive(Derivative, Clone)]128pub struct CreateItemData<CrossAccountId> {129 #[derivative(Debug(format_with = "bounded::map_debug"))]130 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,131 #[derivative(Debug(format_with = "bounded::vec_debug"))]132 pub properties: CollectionPropertiesVec,133}134pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;135136/// Token data, stored independently from other data used to describe it137/// for the convenience of database access. Notably contains the token metadata.138#[struct_versioning::versioned(version = 2, upper)]139#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]140#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]141pub struct ItemData {142 pub const_data: BoundedVec<u8, CustomDataLimit>,143144 #[version(..2)]145 pub variable_data: BoundedVec<u8, CustomDataLimit>,146}147148#[frame_support::pallet]149pub mod pallet {150 use super::*;151 use frame_support::{152 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,153 traits::StorageVersion,154 };155 use frame_system::pallet_prelude::*;156 use up_data_structs::{CollectionId, TokenId};157 use super::weights::WeightInfo;158159 #[pallet::error]160 pub enum Error<T> {161 /// Not Refungible item data used to mint in Refungible collection.162 NotRefungibleDataUsedToMintFungibleCollectionToken,163 /// Maximum refungibility exceeded.164 WrongRefungiblePieces,165 /// Refungible token can't be repartitioned by user who isn't owns all pieces.166 RepartitionWhileNotOwningAllPieces,167 /// Refungible token can't nest other tokens.168 RefungibleDisallowsNesting,169 /// Setting item properties is not allowed.170 SettingPropertiesNotAllowed,171 }172173 #[pallet::config]174 pub trait Config:175 frame_system::Config + pallet_common::Config + pallet_structure::Config176 {177 type WeightInfo: WeightInfo;178 }179180 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);181182 #[pallet::pallet]183 #[pallet::storage_version(STORAGE_VERSION)]184 #[pallet::generate_store(pub(super) trait Store)]185 pub struct Pallet<T>(_);186187 /// Total amount of minted tokens in a collection.188 #[pallet::storage]189 pub type TokensMinted<T: Config> =190 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;191192 /// Amount of tokens burnt in a collection.193 #[pallet::storage]194 pub type TokensBurnt<T: Config> =195 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;196197 /// Token data, used to partially describe a token.198 // TODO: remove199 #[pallet::storage]200 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]201 pub type TokenData<T: Config> = StorageNMap<202 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),203 Value = ItemData,204 QueryKind = ValueQuery,205 >;206207 /// Amount of pieces a refungible token is split into.208 #[pallet::storage]209 #[pallet::getter(fn token_properties)]210 pub type TokenProperties<T: Config> = StorageNMap<211 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),212 Value = up_data_structs::Properties,213 QueryKind = ValueQuery,214 OnEmpty = up_data_structs::TokenProperties,215 >;216217 /// Total amount of pieces for token218 #[pallet::storage]219 pub type TotalSupply<T: Config> = StorageNMap<220 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),221 Value = u128,222 QueryKind = ValueQuery,223 >;224225 /// Used to enumerate tokens owned by account.226 #[pallet::storage]227 pub type Owned<T: Config> = StorageNMap<228 Key = (229 Key<Twox64Concat, CollectionId>,230 Key<Blake2_128Concat, T::CrossAccountId>,231 Key<Twox64Concat, TokenId>,232 ),233 Value = bool,234 QueryKind = ValueQuery,235 >;236237 /// Amount of tokens (not pieces) partially owned by an account within a collection.238 #[pallet::storage]239 pub type AccountBalance<T: Config> = StorageNMap<240 Key = (241 Key<Twox64Concat, CollectionId>,242 // Owner243 Key<Blake2_128Concat, T::CrossAccountId>,244 ),245 Value = u32,246 QueryKind = ValueQuery,247 >;248249 /// Amount of token pieces owned by account.250 #[pallet::storage]251 pub type Balance<T: Config> = StorageNMap<252 Key = (253 Key<Twox64Concat, CollectionId>,254 Key<Twox64Concat, TokenId>,255 // Owner256 Key<Blake2_128Concat, T::CrossAccountId>,257 ),258 Value = u128,259 QueryKind = ValueQuery,260 >;261262 /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.263 #[pallet::storage]264 pub type Allowance<T: Config> = StorageNMap<265 Key = (266 Key<Twox64Concat, CollectionId>,267 Key<Twox64Concat, TokenId>,268 // Owner269 Key<Blake2_128, T::CrossAccountId>,270 // Spender271 Key<Blake2_128Concat, T::CrossAccountId>,272 ),273 Value = u128,274 QueryKind = ValueQuery,275 >;276277 #[pallet::hooks]278 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {279 fn on_runtime_upgrade() -> Weight {280 let storage_version = StorageVersion::get::<Pallet<T>>();281 if storage_version < StorageVersion::new(2) {282 <TokenData<T>>::remove_all(None);283 }284 StorageVersion::new(2).put::<Pallet<T>>();285286 0287 }288 }289}290291pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);292impl<T: Config> RefungibleHandle<T> {293 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {294 Self(inner)295 }296 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {297 self.0298 }299 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {300 &mut self.0301 }302}303304impl<T: Config> Deref for RefungibleHandle<T> {305 type Target = pallet_common::CollectionHandle<T>;306307 fn deref(&self) -> &Self::Target {308 &self.0309 }310}311312impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {313 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {314 self.0.recorder()315 }316 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {317 self.0.into_recorder()318 }319}320321impl<T: Config> Pallet<T> {322 /// Get number of RFT tokens in collection323 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {324 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)325 }326327 /// Check that RFT token exists328 ///329 /// - `token`: Token ID.330 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {331 <TotalSupply<T>>::contains_key((collection.id, token))332 }333334 pub fn set_scoped_token_property(335 collection_id: CollectionId,336 token_id: TokenId,337 scope: PropertyScope,338 property: Property,339 ) -> DispatchResult {340 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {341 properties.try_scoped_set(scope, property.key, property.value)342 })343 .map_err(<CommonError<T>>::from)?;344345 Ok(())346 }347348 pub fn set_scoped_token_properties(349 collection_id: CollectionId,350 token_id: TokenId,351 scope: PropertyScope,352 properties: impl Iterator<Item = Property>,353 ) -> DispatchResult {354 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {355 stored_properties.try_scoped_set_from_iter(scope, properties)356 })357 .map_err(<CommonError<T>>::from)?;358359 Ok(())360 }361}362363// unchecked calls skips any permission checks364impl<T: Config> Pallet<T> {365 /// Create RFT collection366 ///367 /// `init_collection` will take non-refundable deposit for collection creation.368 ///369 /// - `data`: Contains settings for collection limits and permissions.370 pub fn init_collection(371 owner: T::CrossAccountId,372 data: CreateCollectionData<T::AccountId>,373 ) -> Result<CollectionId, DispatchError> {374 <PalletCommon<T>>::init_collection(owner, data, false)375 }376377 /// Destroy RFT collection378 ///379 /// `destroy_collection` will throw error if collection contains any tokens.380 /// Only owner can destroy collection.381 pub fn destroy_collection(382 collection: RefungibleHandle<T>,383 sender: &T::CrossAccountId,384 ) -> DispatchResult {385 let id = collection.id;386387 if Self::collection_has_tokens(id) {388 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());389 }390391 // =========392393 PalletCommon::destroy_collection(collection.0, sender)?;394395 <TokensMinted<T>>::remove(id);396 <TokensBurnt<T>>::remove(id);397 <TotalSupply<T>>::remove_prefix((id,), None);398 <Balance<T>>::remove_prefix((id,), None);399 <Allowance<T>>::remove_prefix((id,), None);400 <Owned<T>>::remove_prefix((id,), None);401 <AccountBalance<T>>::remove_prefix((id,), None);402 Ok(())403 }404405 fn collection_has_tokens(collection_id: CollectionId) -> bool {406 <TotalSupply<T>>::iter_prefix((collection_id,))407 .next()408 .is_some()409 }410411 pub fn burn_token_unchecked(412 collection: &RefungibleHandle<T>,413 owner: &T::CrossAccountId,414 token_id: TokenId,415 ) -> DispatchResult {416 let burnt = <TokensBurnt<T>>::get(collection.id)417 .checked_add(1)418 .ok_or(ArithmeticError::Overflow)?;419420 <TokensBurnt<T>>::insert(collection.id, burnt);421 <TokenProperties<T>>::remove((collection.id, token_id));422 <TotalSupply<T>>::remove((collection.id, token_id));423 <Balance<T>>::remove_prefix((collection.id, token_id), None);424 <Allowance<T>>::remove_prefix((collection.id, token_id), None);425426 <PalletEvm<T>>::deposit_log(427 ERC721Events::Transfer {428 from: *owner.as_eth(),429 to: H160::default(),430 token_id: token_id.into(),431 }432 .to_log(collection_id_to_address(collection.id)),433 );434 Ok(())435 }436437 /// Burn RFT token pieces438 ///439 /// `burn` will decrease total amount of token pieces and amount owned by sender.440 /// `burn` can be called even if there are multiple owners of the RFT token.441 /// If sender wouldn't have any pieces left after `burn` than she will stop being442 /// one of the owners of the token. If there is no account that owns any pieces of443 /// the token than token will be burned too.444 ///445 /// - `amount`: Amount of token pieces to burn.446 /// - `token`: Token who's pieces should be burned447 /// - `collection`: Collection that contains the token448 pub fn burn(449 collection: &RefungibleHandle<T>,450 owner: &T::CrossAccountId,451 token: TokenId,452 amount: u128,453 ) -> DispatchResult {454 let total_supply = <TotalSupply<T>>::get((collection.id, token))455 .checked_sub(amount)456 .ok_or(<CommonError<T>>::TokenValueTooLow)?;457458 // This was probally last owner of this token?459 if total_supply == 0 {460 // Ensure user actually owns this amount461 ensure!(462 <Balance<T>>::get((collection.id, token, owner)) == amount,463 <CommonError<T>>::TokenValueTooLow464 );465 let account_balance = <AccountBalance<T>>::get((collection.id, owner))466 .checked_sub(1)467 // Should not occur468 .ok_or(ArithmeticError::Underflow)?;469470 // =========471472 <Owned<T>>::remove((collection.id, owner, token));473 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);474 <AccountBalance<T>>::insert((collection.id, owner), account_balance);475 Self::burn_token_unchecked(collection, owner, token)?;476 <PalletEvm<T>>::deposit_log(477 ERC20Events::Transfer {478 from: *owner.as_eth(),479 to: H160::default(),480 value: amount.into(),481 }482 .to_log(collection_id_to_address(collection.id)),483 );484 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(485 collection.id,486 token,487 owner.clone(),488 amount,489 ));490 return Ok(());491 }492493 let balance = <Balance<T>>::get((collection.id, token, owner))494 .checked_sub(amount)495 .ok_or(<CommonError<T>>::TokenValueTooLow)?;496 let account_balance = if balance == 0 {497 <AccountBalance<T>>::get((collection.id, owner))498 .checked_sub(1)499 // Should not occur500 .ok_or(ArithmeticError::Underflow)?501 } else {502 0503 };504505 // =========506507 if balance == 0 {508 <Owned<T>>::remove((collection.id, owner, token));509 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);510 <Balance<T>>::remove((collection.id, token, owner));511 <AccountBalance<T>>::insert((collection.id, owner), account_balance);512513 if let Some(user) = Self::token_owner(collection.id, token) {514 <PalletEvm<T>>::deposit_log(515 ERC721Events::Transfer {516 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,517 to: *user.as_eth(),518 token_id: token.into(),519 }520 .to_log(collection_id_to_address(collection.id)),521 );522 }523 } else {524 <Balance<T>>::insert((collection.id, token, owner), balance);525 }526 <TotalSupply<T>>::insert((collection.id, token), total_supply);527528 <PalletEvm<T>>::deposit_log(529 ERC20Events::Transfer {530 from: *owner.as_eth(),531 to: H160::default(),532 value: amount.into(),533 }534 .to_log(T::EvmTokenAddressMapping::token_to_address(535 collection.id,536 token,537 )),538 );539 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(540 collection.id,541 token,542 owner.clone(),543 amount,544 ));545 Ok(())546 }547548 #[transactional]549 fn modify_token_properties(550 collection: &RefungibleHandle<T>,551 sender: &T::CrossAccountId,552 token_id: TokenId,553 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,554 is_token_create: bool,555 nesting_budget: &dyn Budget,556 ) -> DispatchResult {557 let is_collection_admin = || collection.is_owner_or_admin(sender);558 let is_token_owner = || -> Result<bool, DispatchError> {559 let balance = collection.balance(sender.clone(), token_id);560 let total_pieces: u128 =561 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);562 if balance != total_pieces {563 return Ok(false);564 }565566 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(567 sender.clone(),568 collection.id,569 token_id,570 None,571 nesting_budget,572 )?;573574 Ok(is_bundle_owner)575 };576577 for (key, value) in properties {578 let permission = <PalletCommon<T>>::property_permissions(collection.id)579 .get(&key)580 .cloned()581 .unwrap_or_else(PropertyPermission::none);582583 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))584 .get(&key)585 .is_some();586587 match permission {588 PropertyPermission { mutable: false, .. } if is_property_exists => {589 return Err(<CommonError<T>>::NoPermission.into());590 }591592 PropertyPermission {593 collection_admin,594 token_owner,595 ..596 } => {597 //TODO: investigate threats during public minting.598 let is_token_create =599 is_token_create && (collection_admin || token_owner) && value.is_some();600 if !(is_token_create601 || (collection_admin && is_collection_admin())602 || (token_owner && is_token_owner()?))603 {604 fail!(<CommonError<T>>::NoPermission);605 }606 }607 }608609 match value {610 Some(value) => {611 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {612 properties.try_set(key.clone(), value)613 })614 .map_err(<CommonError<T>>::from)?;615616 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(617 collection.id,618 token_id,619 key,620 ));621 }622 None => {623 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {624 properties.remove(&key)625 })626 .map_err(<CommonError<T>>::from)?;627628 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(629 collection.id,630 token_id,631 key,632 ));633 }634 }635 }636637 Ok(())638 }639640 pub fn set_token_properties(641 collection: &RefungibleHandle<T>,642 sender: &T::CrossAccountId,643 token_id: TokenId,644 properties: impl Iterator<Item = Property>,645 is_token_create: bool,646 nesting_budget: &dyn Budget,647 ) -> DispatchResult {648 Self::modify_token_properties(649 collection,650 sender,651 token_id,652 properties.map(|p| (p.key, Some(p.value))),653 is_token_create,654 nesting_budget,655 )656 }657658 pub fn set_token_property(659 collection: &RefungibleHandle<T>,660 sender: &T::CrossAccountId,661 token_id: TokenId,662 property: Property,663 nesting_budget: &dyn Budget,664 ) -> DispatchResult {665 let is_token_create = false;666667 Self::set_token_properties(668 collection,669 sender,670 token_id,671 [property].into_iter(),672 is_token_create,673 nesting_budget,674 )675 }676677 pub fn delete_token_properties(678 collection: &RefungibleHandle<T>,679 sender: &T::CrossAccountId,680 token_id: TokenId,681 property_keys: impl Iterator<Item = PropertyKey>,682 nesting_budget: &dyn Budget,683 ) -> DispatchResult {684 let is_token_create = false;685686 Self::modify_token_properties(687 collection,688 sender,689 token_id,690 property_keys.into_iter().map(|key| (key, None)),691 is_token_create,692 nesting_budget,693 )694 }695696 pub fn delete_token_property(697 collection: &RefungibleHandle<T>,698 sender: &T::CrossAccountId,699 token_id: TokenId,700 property_key: PropertyKey,701 nesting_budget: &dyn Budget,702 ) -> DispatchResult {703 Self::delete_token_properties(704 collection,705 sender,706 token_id,707 [property_key].into_iter(),708 nesting_budget,709 )710 }711712 /// Transfer RFT token pieces from one account to another.713 ///714 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.715 ///716 /// - `from`: Owner of token pieces to transfer.717 /// - `to`: Recepient of transfered token pieces.718 /// - `amount`: Amount of token pieces to transfer.719 /// - `token`: Token whos pieces should be transfered720 /// - `collection`: Collection that contains the token721 pub fn transfer(722 collection: &RefungibleHandle<T>,723 from: &T::CrossAccountId,724 to: &T::CrossAccountId,725 token: TokenId,726 amount: u128,727 nesting_budget: &dyn Budget,728 ) -> DispatchResult {729 ensure!(730 collection.limits.transfers_enabled(),731 <CommonError<T>>::TransferNotAllowed732 );733734 if collection.permissions.access() == AccessMode::AllowList {735 collection.check_allowlist(from)?;736 collection.check_allowlist(to)?;737 }738 <PalletCommon<T>>::ensure_correct_receiver(to)?;739740 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));741 let updated_balance_from = initial_balance_from742 .checked_sub(amount)743 .ok_or(<CommonError<T>>::TokenValueTooLow)?;744 let mut create_target = false;745 let from_to_differ = from != to;746 let updated_balance_to = if from != to {747 let old_balance = <Balance<T>>::get((collection.id, token, to));748 if old_balance == 0 {749 create_target = true;750 }751 Some(752 old_balance753 .checked_add(amount)754 .ok_or(ArithmeticError::Overflow)?,755 )756 } else {757 None758 };759760 let account_balance_from = if updated_balance_from == 0 {761 Some(762 <AccountBalance<T>>::get((collection.id, from))763 .checked_sub(1)764 // Should not occur765 .ok_or(ArithmeticError::Underflow)?,766 )767 } else {768 None769 };770 // Account data is created in token, AccountBalance should be increased771 // But only if from != to as we shouldn't check overflow in this case772 let account_balance_to = if create_target && from_to_differ {773 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))774 .checked_add(1)775 .ok_or(ArithmeticError::Overflow)?;776 ensure!(777 account_balance_to < collection.limits.account_token_ownership_limit(),778 <CommonError<T>>::AccountTokenLimitExceeded,779 );780781 Some(account_balance_to)782 } else {783 None784 };785786 // =========787788 <PalletStructure<T>>::nest_if_sent_to_token(789 from.clone(),790 to,791 collection.id,792 token,793 nesting_budget,794 )?;795796 if let Some(updated_balance_to) = updated_balance_to {797 // from != to798 if updated_balance_from == 0 {799 <Balance<T>>::remove((collection.id, token, from));800 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);801 } else {802 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);803 }804 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);805 if let Some(account_balance_from) = account_balance_from {806 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);807 <Owned<T>>::remove((collection.id, from, token));808 }809 if let Some(account_balance_to) = account_balance_to {810 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);811 <Owned<T>>::insert((collection.id, to, token), true);812 }813 }814815 <PalletEvm<T>>::deposit_log(816 ERC20Events::Transfer {817 from: *from.as_eth(),818 to: *to.as_eth(),819 value: amount.into(),820 }821 .to_log(T::EvmTokenAddressMapping::token_to_address(822 collection.id,823 token,824 )),825 );826827 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(828 collection.id,829 token,830 from.clone(),831 to.clone(),832 amount,833 ));834835 let total_supply = <TotalSupply<T>>::get((collection.id, token));836837 if amount == total_supply {838 // if token was fully owned by `from` and will be fully owned by `to` after transfer839 <PalletEvm<T>>::deposit_log(840 ERC721Events::Transfer {841 from: *from.as_eth(),842 to: *to.as_eth(),843 token_id: token.into(),844 }845 .to_log(collection_id_to_address(collection.id)),846 );847 } else if let Some(updated_balance_to) = updated_balance_to {848 // if `from` not equals `to`. This condition is needed to avoid sending event849 // when `from` fully owns token and sends part of token pieces to itself.850 if initial_balance_from == total_supply {851 // if token was fully owned by `from` and will be only partially owned by `to`852 // and `from` after transfer853 <PalletEvm<T>>::deposit_log(854 ERC721Events::Transfer {855 from: *from.as_eth(),856 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,857 token_id: token.into(),858 }859 .to_log(collection_id_to_address(collection.id)),860 );861 } else if updated_balance_to == total_supply {862 // if token was partially owned by `from` and will be fully owned by `to` after transfer863 <PalletEvm<T>>::deposit_log(864 ERC721Events::Transfer {865 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,866 to: *to.as_eth(),867 token_id: token.into(),868 }869 .to_log(collection_id_to_address(collection.id)),870 );871 }872 }873874 Ok(())875 }876877 /// Batched operation to create multiple RFT tokens.878 ///879 /// Same as `create_item` but creates multiple tokens.880 ///881 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.882 pub fn create_multiple_items(883 collection: &RefungibleHandle<T>,884 sender: &T::CrossAccountId,885 data: Vec<CreateItemData<T::CrossAccountId>>,886 nesting_budget: &dyn Budget,887 ) -> DispatchResult {888 if !collection.is_owner_or_admin(sender) {889 ensure!(890 collection.permissions.mint_mode(),891 <CommonError<T>>::PublicMintingNotAllowed892 );893 collection.check_allowlist(sender)?;894895 for item in data.iter() {896 for user in item.users.keys() {897 collection.check_allowlist(user)?;898 }899 }900 }901902 for item in data.iter() {903 for (owner, _) in item.users.iter() {904 <PalletCommon<T>>::ensure_correct_receiver(owner)?;905 }906 }907908 // Total pieces per tokens909 let totals = data910 .iter()911 .map(|data| {912 Ok(data913 .users914 .iter()915 .map(|u| u.1)916 .try_fold(0u128, |acc, v| acc.checked_add(*v))917 .ok_or(ArithmeticError::Overflow)?)918 })919 .collect::<Result<Vec<_>, DispatchError>>()?;920 for total in &totals {921 ensure!(922 *total <= MAX_REFUNGIBLE_PIECES,923 <Error<T>>::WrongRefungiblePieces924 );925 }926927 let first_token_id = <TokensMinted<T>>::get(collection.id);928 let tokens_minted = first_token_id929 .checked_add(data.len() as u32)930 .ok_or(ArithmeticError::Overflow)?;931 ensure!(932 tokens_minted < collection.limits.token_limit(),933 <CommonError<T>>::CollectionTokenLimitExceeded934 );935936 let mut balances = BTreeMap::new();937 for data in &data {938 for owner in data.users.keys() {939 let balance = balances940 .entry(owner)941 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));942 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;943944 ensure!(945 *balance <= collection.limits.account_token_ownership_limit(),946 <CommonError<T>>::AccountTokenLimitExceeded,947 );948 }949 }950951 for (i, token) in data.iter().enumerate() {952 let token_id = TokenId(first_token_id + i as u32 + 1);953 for (to, _) in token.users.iter() {954 <PalletStructure<T>>::check_nesting(955 sender.clone(),956 to,957 collection.id,958 token_id,959 nesting_budget,960 )?;961 }962 }963964 // =========965966 with_transaction(|| {967 for (i, data) in data.iter().enumerate() {968 let token_id = first_token_id + i as u32 + 1;969 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);970971 for (user, amount) in data.users.iter() {972 if *amount == 0 {973 continue;974 }975 <Balance<T>>::insert((collection.id, token_id, &user), amount);976 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);977 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(978 user,979 collection.id,980 TokenId(token_id),981 );982 }983984 if let Err(e) = Self::set_token_properties(985 collection,986 sender,987 TokenId(token_id),988 data.properties.clone().into_iter(),989 true,990 nesting_budget,991 ) {992 return TransactionOutcome::Rollback(Err(e));993 }994 }995 TransactionOutcome::Commit(Ok(()))996 })?;997998 <TokensMinted<T>>::insert(collection.id, tokens_minted);9991000 for (account, balance) in balances {1001 <AccountBalance<T>>::insert((collection.id, account), balance);1002 }10031004 for (i, token) in data.into_iter().enumerate() {1005 let token_id = first_token_id + i as u32 + 1;10061007 let receivers = token1008 .users1009 .into_iter()1010 .filter(|(_, amount)| *amount > 0)1011 .collect::<Vec<_>>();10121013 if let [(user, _)] = receivers.as_slice() {1014 // if there is exactly one receiver1015 <PalletEvm<T>>::deposit_log(1016 ERC721Events::Transfer {1017 from: H160::default(),1018 to: *user.as_eth(),1019 token_id: token_id.into(),1020 }1021 .to_log(collection_id_to_address(collection.id)),1022 );1023 } else if let [_, ..] = receivers.as_slice() {1024 // if there is more than one receiver1025 <PalletEvm<T>>::deposit_log(1026 ERC721Events::Transfer {1027 from: H160::default(),1028 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1029 token_id: token_id.into(),1030 }1031 .to_log(collection_id_to_address(collection.id)),1032 );1033 }10341035 for (user, amount) in receivers.into_iter() {1036 <PalletEvm<T>>::deposit_log(1037 ERC20Events::Transfer {1038 from: H160::default(),1039 to: *user.as_eth(),1040 value: amount.into(),1041 }1042 .to_log(T::EvmTokenAddressMapping::token_to_address(1043 collection.id,1044 TokenId(token_id),1045 )),1046 );1047 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1048 collection.id,1049 TokenId(token_id),1050 user,1051 amount,1052 ));1053 }1054 }1055 Ok(())1056 }10571058 pub fn set_allowance_unchecked(1059 collection: &RefungibleHandle<T>,1060 sender: &T::CrossAccountId,1061 spender: &T::CrossAccountId,1062 token: TokenId,1063 amount: u128,1064 ) {1065 if amount == 0 {1066 <Allowance<T>>::remove((collection.id, token, sender, spender));1067 } else {1068 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1069 }10701071 <PalletEvm<T>>::deposit_log(1072 ERC20Events::Approval {1073 owner: *sender.as_eth(),1074 spender: *spender.as_eth(),1075 value: amount.into(),1076 }1077 .to_log(T::EvmTokenAddressMapping::token_to_address(1078 collection.id,1079 token,1080 )),1081 );1082 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1083 collection.id,1084 token,1085 sender.clone(),1086 spender.clone(),1087 amount,1088 ))1089 }10901091 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1092 ///1093 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1094 pub fn set_allowance(1095 collection: &RefungibleHandle<T>,1096 sender: &T::CrossAccountId,1097 spender: &T::CrossAccountId,1098 token: TokenId,1099 amount: u128,1100 ) -> DispatchResult {1101 if collection.permissions.access() == AccessMode::AllowList {1102 collection.check_allowlist(sender)?;1103 collection.check_allowlist(spender)?;1104 }11051106 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11071108 if <Balance<T>>::get((collection.id, token, sender)) < amount {1109 ensure!(1110 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1111 <CommonError<T>>::CantApproveMoreThanOwned1112 );1113 }11141115 // =========11161117 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1118 Ok(())1119 }11201121 /// Returns allowance, which should be set after transaction1122 fn check_allowed(1123 collection: &RefungibleHandle<T>,1124 spender: &T::CrossAccountId,1125 from: &T::CrossAccountId,1126 token: TokenId,1127 amount: u128,1128 nesting_budget: &dyn Budget,1129 ) -> Result<Option<u128>, DispatchError> {1130 if spender.conv_eq(from) {1131 return Ok(None);1132 }1133 if collection.permissions.access() == AccessMode::AllowList {1134 // `from`, `to` checked in [`transfer`]1135 collection.check_allowlist(spender)?;1136 }1137 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1138 // TODO: should collection owner be allowed to perform this transfer?1139 ensure!(1140 <PalletStructure<T>>::check_indirectly_owned(1141 spender.clone(),1142 source.0,1143 source.1,1144 None,1145 nesting_budget1146 )?,1147 <CommonError<T>>::ApprovedValueTooLow,1148 );1149 return Ok(None);1150 }1151 let allowance =1152 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1153 if allowance.is_none() {1154 ensure!(1155 collection.ignores_allowance(spender),1156 <CommonError<T>>::ApprovedValueTooLow1157 );1158 }1159 Ok(allowance)1160 }11611162 /// Transfer RFT token pieces from one account to another.1163 ///1164 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1165 /// The owner should set allowance for the spender to transfer pieces.1166 ///1167 /// [`transfer`]: struct.Pallet.html#method.transfer1168 pub fn transfer_from(1169 collection: &RefungibleHandle<T>,1170 spender: &T::CrossAccountId,1171 from: &T::CrossAccountId,1172 to: &T::CrossAccountId,1173 token: TokenId,1174 amount: u128,1175 nesting_budget: &dyn Budget,1176 ) -> DispatchResult {1177 let allowance =1178 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11791180 // =========11811182 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1183 if let Some(allowance) = allowance {1184 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1185 }1186 Ok(())1187 }11881189 /// Burn RFT token pieces from the account.1190 ///1191 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1192 /// set allowance for the spender to burn pieces1193 ///1194 /// [`burn`]: struct.Pallet.html#method.burn1195 pub fn burn_from(1196 collection: &RefungibleHandle<T>,1197 spender: &T::CrossAccountId,1198 from: &T::CrossAccountId,1199 token: TokenId,1200 amount: u128,1201 nesting_budget: &dyn Budget,1202 ) -> DispatchResult {1203 let allowance =1204 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12051206 // =========12071208 Self::burn(collection, from, token, amount)?;1209 if let Some(allowance) = allowance {1210 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1211 }1212 Ok(())1213 }12141215 /// Create RFT token.1216 ///1217 /// The sender should be the owner/admin of the collection or collection should be configured1218 /// to allow public minting.1219 ///1220 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1221 /// of token pieces they will receive.1222 pub fn create_item(1223 collection: &RefungibleHandle<T>,1224 sender: &T::CrossAccountId,1225 data: CreateItemData<T::CrossAccountId>,1226 nesting_budget: &dyn Budget,1227 ) -> DispatchResult {1228 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1229 }12301231 /// Repartition RFT token.1232 ///1233 /// `repartition` will set token balance of the sender and total amount of token pieces.1234 /// Sender should own all of the token pieces. `repartition' could be done even if some1235 /// token pieces were burned before.1236 ///1237 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1238 pub fn repartition(1239 collection: &RefungibleHandle<T>,1240 owner: &T::CrossAccountId,1241 token: TokenId,1242 amount: u128,1243 ) -> DispatchResult {1244 ensure!(1245 amount <= MAX_REFUNGIBLE_PIECES,1246 <Error<T>>::WrongRefungiblePieces1247 );1248 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1249 // Ensure user owns all pieces1250 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1251 let balance = <Balance<T>>::get((collection.id, token, owner));1252 ensure!(1253 total_pieces == balance,1254 <Error<T>>::RepartitionWhileNotOwningAllPieces1255 );12561257 <Balance<T>>::insert((collection.id, token, owner), amount);1258 <TotalSupply<T>>::insert((collection.id, token), amount);12591260 if amount > total_pieces {1261 let mint_amount = amount - total_pieces;1262 <PalletEvm<T>>::deposit_log(1263 ERC20Events::Transfer {1264 from: H160::default(),1265 to: *owner.as_eth(),1266 value: mint_amount.into(),1267 }1268 .to_log(T::EvmTokenAddressMapping::token_to_address(1269 collection.id,1270 token,1271 )),1272 );1273 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1274 collection.id,1275 token,1276 owner.clone(),1277 mint_amount,1278 ));1279 } else if total_pieces > amount {1280 let burn_amount = total_pieces - amount;1281 <PalletEvm<T>>::deposit_log(1282 ERC20Events::Transfer {1283 from: *owner.as_eth(),1284 to: H160::default(),1285 value: burn_amount.into(),1286 }1287 .to_log(T::EvmTokenAddressMapping::token_to_address(1288 collection.id,1289 token,1290 )),1291 );1292 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1293 collection.id,1294 token,1295 owner.clone(),1296 burn_amount,1297 ));1298 }12991300 Ok(())1301 }13021303 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1304 let mut owner = None;1305 let mut count = 0;1306 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1307 count += 1;1308 if count > 1 {1309 return None;1310 }1311 owner = Some(key);1312 }1313 owner1314 }13151316 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1317 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1318 }13191320 pub fn set_collection_properties(1321 collection: &RefungibleHandle<T>,1322 sender: &T::CrossAccountId,1323 properties: Vec<Property>,1324 ) -> DispatchResult {1325 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1326 }13271328 pub fn delete_collection_properties(1329 collection: &RefungibleHandle<T>,1330 sender: &T::CrossAccountId,1331 property_keys: Vec<PropertyKey>,1332 ) -> DispatchResult {1333 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1334 }13351336 pub fn set_token_property_permissions(1337 collection: &RefungibleHandle<T>,1338 sender: &T::CrossAccountId,1339 property_permissions: Vec<PropertyKeyPermission>,1340 ) -> DispatchResult {1341 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1342 }13431344 /// Returns 10 token in no particular order.1345 ///1346 /// There is no direct way to get token holders in ascending order,1347 /// since `iter_prefix` returns values in no particular order.1348 /// Therefore, getting the 10 largest holders with a large value of holders1349 /// can lead to impact memory allocation + sorting with `n * log (n)`.1350 pub fn token_owners(1351 collection_id: CollectionId,1352 token: TokenId,1353 ) -> Option<Vec<T::CrossAccountId>> {1354 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1355 .map(|(owner, _amount)| owner)1356 .take(10)1357 .collect();13581359 if res.is_empty() {1360 None1361 } else {1362 Some(res)1363 }1364 }1365}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet18//!19//! The Refungible pallet provides functionality for handling refungible collections and tokens.20//!21//! - [`Config`]22//! - [`RefungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Refungible pallet provides functions for:29//!30//! - RFT collection creation and removal31//! - Minting and burning of RFT tokens32//! - Partition and repartition of RFT tokens33//! - Retrieving number of pieces of RFT token34//! - Retrieving account balances35//! - Transfering RFT token pieces36//! - Burning RFT token pieces37//! - Setting and checking allowance for RFT tokens38//!39//! ### Terminology40//!41//! - **RFT token:** Non fungible token that was partitioned to pieces. If an account owns all42//! of the RFT token pieces than it owns the RFT token and can repartition it.43//!44//! - **RFT Collection:** A collection of RFT tokens. All RFT tokens are part of a collection.45//! Each collection has its own settings and set of permissions.46//!47//! - **RFT token piece:** A fungible part of an RFT token.48//!49//! - **Balance:** RFT token pieces owned by an account50//!51//! - **Allowance:** Maximum number of RFT token pieces that one account is allowed to52//! transfer from the balance of another account53//!54//! - **Burning:** The process of “deleting” a token from a collection or removing token pieces from55//! an account balance.56//!57//! ### Implementations58//!59//! The Refungible pallet provides implementations for the following traits. If these traits provide60//! the functionality that you need, then you can avoid coupling with the Refungible pallet.61//!62//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight63//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing64//! with collections65//! - [`RefungibleExtensions`](pallet_common::RefungibleExtensions): Functions specific for refungible66//! collection67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create RFT collection. RFT collection can be configured to allow or deny access for73//! some accounts.74//! - `destroy_collection` - Destroy exising RFT collection. There should be no tokens in the collection.75//! - `burn` - Burn some amount of RFT token pieces owned by account. Burns the RFT token if no pieces left.76//! - `transfer` - Transfer some amount of RFT token pieces. Transfers should be enabled for RFT collection.77//! Nests the RFT token if RFT token pieces are sent to another token.78//! - `create_item` - Mint RFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account to transfer balance from sender's account.80//! - `repartition` - Repartition token to selected number of pieces. Sender should own all existing pieces.81//!82//! ## Assumptions83//!84//! * Total number of pieces for one token shouldn't exceed `up_data_structs::MAX_REFUNGIBLE_PIECES`.85//! * Total number of tokens of all types shouldn't be greater than `up_data_structs::MAX_TOKEN_PREFIX_LENGTH`.86//! * Sender should be in collection's allow list to perform operations on tokens.8788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use codec::{Encode, Decode, MaxEncodedLen};94use core::ops::Deref;95use evm_coder::ToLog;96use frame_support::{97 BoundedVec, ensure, fail, storage::with_transaction, transactional, pallet_prelude::ConstU32,98};99use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};100use pallet_evm_coder_substrate::WithRecorder;101use pallet_common::{102 CollectionHandle, CommonCollectionOperations,103 dispatch::CollectionDispatch,104 erc::static_property::{key, property_value_from_bytes},105 Error as CommonError,106 eth::collection_id_to_address,107 Event as CommonEvent, Pallet as PalletCommon,108};109use pallet_structure::Pallet as PalletStructure;110use scale_info::TypeInfo;111use sp_core::H160;112use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};113use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};114use up_data_structs::{115 AccessMode, budget::Budget, CollectionId, CollectionMode, CollectionPropertiesVec,116 CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,117 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,118 PropertyScope, PropertyValue, TokenId, TrySetProperty,119};120use frame_support::BoundedBTreeMap;121use derivative::Derivative;122123pub use pallet::*;124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod common;127pub mod erc;128pub mod erc_token;129pub mod weights;130131#[derive(Derivative, Clone)]132pub struct CreateItemData<CrossAccountId> {133 #[derivative(Debug(format_with = "bounded::map_debug"))]134 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,135 #[derivative(Debug(format_with = "bounded::vec_debug"))]136 pub properties: CollectionPropertiesVec,137}138pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;139140/// Token data, stored independently from other data used to describe it141/// for the convenience of database access. Notably contains the token metadata.142#[struct_versioning::versioned(version = 2, upper)]143#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]144#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]145pub struct ItemData {146 pub const_data: BoundedVec<u8, CustomDataLimit>,147148 #[version(..2)]149 pub variable_data: BoundedVec<u8, CustomDataLimit>,150}151152#[frame_support::pallet]153pub mod pallet {154 use super::*;155 use frame_support::{156 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,157 traits::StorageVersion,158 };159 use frame_system::pallet_prelude::*;160 use up_data_structs::{CollectionId, TokenId};161 use super::weights::WeightInfo;162163 #[pallet::error]164 pub enum Error<T> {165 /// Not Refungible item data used to mint in Refungible collection.166 NotRefungibleDataUsedToMintFungibleCollectionToken,167 /// Maximum refungibility exceeded.168 WrongRefungiblePieces,169 /// Refungible token can't be repartitioned by user who isn't owns all pieces.170 RepartitionWhileNotOwningAllPieces,171 /// Refungible token can't nest other tokens.172 RefungibleDisallowsNesting,173 /// Setting item properties is not allowed.174 SettingPropertiesNotAllowed,175 }176177 #[pallet::config]178 pub trait Config:179 frame_system::Config + pallet_common::Config + pallet_structure::Config180 {181 type WeightInfo: WeightInfo;182 }183184 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);185186 #[pallet::pallet]187 #[pallet::storage_version(STORAGE_VERSION)]188 #[pallet::generate_store(pub(super) trait Store)]189 pub struct Pallet<T>(_);190191 /// Total amount of minted tokens in a collection.192 #[pallet::storage]193 pub type TokensMinted<T: Config> =194 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;195196 /// Amount of tokens burnt in a collection.197 #[pallet::storage]198 pub type TokensBurnt<T: Config> =199 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;200201 /// Token data, used to partially describe a token.202 // TODO: remove203 #[pallet::storage]204 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]205 pub type TokenData<T: Config> = StorageNMap<206 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),207 Value = ItemData,208 QueryKind = ValueQuery,209 >;210211 /// Amount of pieces a refungible token is split into.212 #[pallet::storage]213 #[pallet::getter(fn token_properties)]214 pub type TokenProperties<T: Config> = StorageNMap<215 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),216 Value = up_data_structs::Properties,217 QueryKind = ValueQuery,218 OnEmpty = up_data_structs::TokenProperties,219 >;220221 /// Total amount of pieces for token222 #[pallet::storage]223 pub type TotalSupply<T: Config> = StorageNMap<224 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),225 Value = u128,226 QueryKind = ValueQuery,227 >;228229 /// Used to enumerate tokens owned by account.230 #[pallet::storage]231 pub type Owned<T: Config> = StorageNMap<232 Key = (233 Key<Twox64Concat, CollectionId>,234 Key<Blake2_128Concat, T::CrossAccountId>,235 Key<Twox64Concat, TokenId>,236 ),237 Value = bool,238 QueryKind = ValueQuery,239 >;240241 /// Amount of tokens (not pieces) partially owned by an account within a collection.242 #[pallet::storage]243 pub type AccountBalance<T: Config> = StorageNMap<244 Key = (245 Key<Twox64Concat, CollectionId>,246 // Owner247 Key<Blake2_128Concat, T::CrossAccountId>,248 ),249 Value = u32,250 QueryKind = ValueQuery,251 >;252253 /// Amount of token pieces owned by account.254 #[pallet::storage]255 pub type Balance<T: Config> = StorageNMap<256 Key = (257 Key<Twox64Concat, CollectionId>,258 Key<Twox64Concat, TokenId>,259 // Owner260 Key<Blake2_128Concat, T::CrossAccountId>,261 ),262 Value = u128,263 QueryKind = ValueQuery,264 >;265266 /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.267 #[pallet::storage]268 pub type Allowance<T: Config> = StorageNMap<269 Key = (270 Key<Twox64Concat, CollectionId>,271 Key<Twox64Concat, TokenId>,272 // Owner273 Key<Blake2_128, T::CrossAccountId>,274 // Spender275 Key<Blake2_128Concat, T::CrossAccountId>,276 ),277 Value = u128,278 QueryKind = ValueQuery,279 >;280281 #[pallet::hooks]282 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {283 fn on_runtime_upgrade() -> Weight {284 let storage_version = StorageVersion::get::<Pallet<T>>();285 if storage_version < StorageVersion::new(2) {286 <TokenData<T>>::remove_all(None);287 }288 StorageVersion::new(2).put::<Pallet<T>>();289290 0291 }292 }293}294295pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);296impl<T: Config> RefungibleHandle<T> {297 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {298 Self(inner)299 }300 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {301 self.0302 }303 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {304 &mut self.0305 }306}307308impl<T: Config> Deref for RefungibleHandle<T> {309 type Target = pallet_common::CollectionHandle<T>;310311 fn deref(&self) -> &Self::Target {312 &self.0313 }314}315316impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {317 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {318 self.0.recorder()319 }320 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {321 self.0.into_recorder()322 }323}324325impl<T: Config> Pallet<T> {326 /// Get number of RFT tokens in collection327 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {328 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)329 }330331 /// Check that RFT token exists332 ///333 /// - `token`: Token ID.334 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {335 <TotalSupply<T>>::contains_key((collection.id, token))336 }337338 pub fn set_scoped_token_property(339 collection_id: CollectionId,340 token_id: TokenId,341 scope: PropertyScope,342 property: Property,343 ) -> DispatchResult {344 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {345 properties.try_scoped_set(scope, property.key, property.value)346 })347 .map_err(<CommonError<T>>::from)?;348349 Ok(())350 }351352 pub fn set_scoped_token_properties(353 collection_id: CollectionId,354 token_id: TokenId,355 scope: PropertyScope,356 properties: impl Iterator<Item = Property>,357 ) -> DispatchResult {358 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {359 stored_properties.try_scoped_set_from_iter(scope, properties)360 })361 .map_err(<CommonError<T>>::from)?;362363 Ok(())364 }365}366367// unchecked calls skips any permission checks368impl<T: Config> Pallet<T> {369 /// Create RFT collection370 ///371 /// `init_collection` will take non-refundable deposit for collection creation.372 ///373 /// - `data`: Contains settings for collection limits and permissions.374 pub fn init_collection(375 owner: T::CrossAccountId,376 data: CreateCollectionData<T::AccountId>,377 ) -> Result<CollectionId, DispatchError> {378 <PalletCommon<T>>::init_collection(owner, data, false)379 }380381 /// Destroy RFT collection382 ///383 /// `destroy_collection` will throw error if collection contains any tokens.384 /// Only owner can destroy collection.385 pub fn destroy_collection(386 collection: RefungibleHandle<T>,387 sender: &T::CrossAccountId,388 ) -> DispatchResult {389 let id = collection.id;390391 if Self::collection_has_tokens(id) {392 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());393 }394395 // =========396397 PalletCommon::destroy_collection(collection.0, sender)?;398399 <TokensMinted<T>>::remove(id);400 <TokensBurnt<T>>::remove(id);401 <TotalSupply<T>>::remove_prefix((id,), None);402 <Balance<T>>::remove_prefix((id,), None);403 <Allowance<T>>::remove_prefix((id,), None);404 <Owned<T>>::remove_prefix((id,), None);405 <AccountBalance<T>>::remove_prefix((id,), None);406 Ok(())407 }408409 fn collection_has_tokens(collection_id: CollectionId) -> bool {410 <TotalSupply<T>>::iter_prefix((collection_id,))411 .next()412 .is_some()413 }414415 pub fn burn_token_unchecked(416 collection: &RefungibleHandle<T>,417 owner: &T::CrossAccountId,418 token_id: TokenId,419 ) -> DispatchResult {420 let burnt = <TokensBurnt<T>>::get(collection.id)421 .checked_add(1)422 .ok_or(ArithmeticError::Overflow)?;423424 <TokensBurnt<T>>::insert(collection.id, burnt);425 <TokenProperties<T>>::remove((collection.id, token_id));426 <TotalSupply<T>>::remove((collection.id, token_id));427 <Balance<T>>::remove_prefix((collection.id, token_id), None);428 <Allowance<T>>::remove_prefix((collection.id, token_id), None);429430 <PalletEvm<T>>::deposit_log(431 ERC721Events::Transfer {432 from: *owner.as_eth(),433 to: H160::default(),434 token_id: token_id.into(),435 }436 .to_log(collection_id_to_address(collection.id)),437 );438 Ok(())439 }440441 /// Burn RFT token pieces442 ///443 /// `burn` will decrease total amount of token pieces and amount owned by sender.444 /// `burn` can be called even if there are multiple owners of the RFT token.445 /// If sender wouldn't have any pieces left after `burn` than she will stop being446 /// one of the owners of the token. If there is no account that owns any pieces of447 /// the token than token will be burned too.448 ///449 /// - `amount`: Amount of token pieces to burn.450 /// - `token`: Token who's pieces should be burned451 /// - `collection`: Collection that contains the token452 pub fn burn(453 collection: &RefungibleHandle<T>,454 owner: &T::CrossAccountId,455 token: TokenId,456 amount: u128,457 ) -> DispatchResult {458 let total_supply = <TotalSupply<T>>::get((collection.id, token))459 .checked_sub(amount)460 .ok_or(<CommonError<T>>::TokenValueTooLow)?;461462 // This was probally last owner of this token?463 if total_supply == 0 {464 // Ensure user actually owns this amount465 ensure!(466 <Balance<T>>::get((collection.id, token, owner)) == amount,467 <CommonError<T>>::TokenValueTooLow468 );469 let account_balance = <AccountBalance<T>>::get((collection.id, owner))470 .checked_sub(1)471 // Should not occur472 .ok_or(ArithmeticError::Underflow)?;473474 // =========475476 <Owned<T>>::remove((collection.id, owner, token));477 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);478 <AccountBalance<T>>::insert((collection.id, owner), account_balance);479 Self::burn_token_unchecked(collection, owner, token)?;480 <PalletEvm<T>>::deposit_log(481 ERC20Events::Transfer {482 from: *owner.as_eth(),483 to: H160::default(),484 value: amount.into(),485 }486 .to_log(collection_id_to_address(collection.id)),487 );488 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(489 collection.id,490 token,491 owner.clone(),492 amount,493 ));494 return Ok(());495 }496497 let balance = <Balance<T>>::get((collection.id, token, owner))498 .checked_sub(amount)499 .ok_or(<CommonError<T>>::TokenValueTooLow)?;500 let account_balance = if balance == 0 {501 <AccountBalance<T>>::get((collection.id, owner))502 .checked_sub(1)503 // Should not occur504 .ok_or(ArithmeticError::Underflow)?505 } else {506 0507 };508509 // =========510511 if balance == 0 {512 <Owned<T>>::remove((collection.id, owner, token));513 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);514 <Balance<T>>::remove((collection.id, token, owner));515 <AccountBalance<T>>::insert((collection.id, owner), account_balance);516517 if let Some(user) = Self::token_owner(collection.id, token) {518 <PalletEvm<T>>::deposit_log(519 ERC721Events::Transfer {520 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,521 to: *user.as_eth(),522 token_id: token.into(),523 }524 .to_log(collection_id_to_address(collection.id)),525 );526 }527 } else {528 <Balance<T>>::insert((collection.id, token, owner), balance);529 }530 <TotalSupply<T>>::insert((collection.id, token), total_supply);531532 <PalletEvm<T>>::deposit_log(533 ERC20Events::Transfer {534 from: *owner.as_eth(),535 to: H160::default(),536 value: amount.into(),537 }538 .to_log(T::EvmTokenAddressMapping::token_to_address(539 collection.id,540 token,541 )),542 );543 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(544 collection.id,545 token,546 owner.clone(),547 amount,548 ));549 Ok(())550 }551552 #[transactional]553 fn modify_token_properties(554 collection: &RefungibleHandle<T>,555 sender: &T::CrossAccountId,556 token_id: TokenId,557 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,558 is_token_create: bool,559 nesting_budget: &dyn Budget,560 ) -> DispatchResult {561 let is_collection_admin = || collection.is_owner_or_admin(sender);562 let is_token_owner = || -> Result<bool, DispatchError> {563 let balance = collection.balance(sender.clone(), token_id);564 let total_pieces: u128 =565 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);566 if balance != total_pieces {567 return Ok(false);568 }569570 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(571 sender.clone(),572 collection.id,573 token_id,574 None,575 nesting_budget,576 )?;577578 Ok(is_bundle_owner)579 };580581 for (key, value) in properties {582 let permission = <PalletCommon<T>>::property_permissions(collection.id)583 .get(&key)584 .cloned()585 .unwrap_or_else(PropertyPermission::none);586587 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))588 .get(&key)589 .is_some();590591 match permission {592 PropertyPermission { mutable: false, .. } if is_property_exists => {593 return Err(<CommonError<T>>::NoPermission.into());594 }595596 PropertyPermission {597 collection_admin,598 token_owner,599 ..600 } => {601 //TODO: investigate threats during public minting.602 let is_token_create =603 is_token_create && (collection_admin || token_owner) && value.is_some();604 if !(is_token_create605 || (collection_admin && is_collection_admin())606 || (token_owner && is_token_owner()?))607 {608 fail!(<CommonError<T>>::NoPermission);609 }610 }611 }612613 match value {614 Some(value) => {615 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {616 properties.try_set(key.clone(), value)617 })618 .map_err(<CommonError<T>>::from)?;619620 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(621 collection.id,622 token_id,623 key,624 ));625 }626 None => {627 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {628 properties.remove(&key)629 })630 .map_err(<CommonError<T>>::from)?;631632 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(633 collection.id,634 token_id,635 key,636 ));637 }638 }639 }640641 Ok(())642 }643644 pub fn set_token_properties(645 collection: &RefungibleHandle<T>,646 sender: &T::CrossAccountId,647 token_id: TokenId,648 properties: impl Iterator<Item = Property>,649 is_token_create: bool,650 nesting_budget: &dyn Budget,651 ) -> DispatchResult {652 Self::modify_token_properties(653 collection,654 sender,655 token_id,656 properties.map(|p| (p.key, Some(p.value))),657 is_token_create,658 nesting_budget,659 )660 }661662 pub fn set_token_property(663 collection: &RefungibleHandle<T>,664 sender: &T::CrossAccountId,665 token_id: TokenId,666 property: Property,667 nesting_budget: &dyn Budget,668 ) -> DispatchResult {669 let is_token_create = false;670671 Self::set_token_properties(672 collection,673 sender,674 token_id,675 [property].into_iter(),676 is_token_create,677 nesting_budget,678 )679 }680681 pub fn delete_token_properties(682 collection: &RefungibleHandle<T>,683 sender: &T::CrossAccountId,684 token_id: TokenId,685 property_keys: impl Iterator<Item = PropertyKey>,686 nesting_budget: &dyn Budget,687 ) -> DispatchResult {688 let is_token_create = false;689690 Self::modify_token_properties(691 collection,692 sender,693 token_id,694 property_keys.into_iter().map(|key| (key, None)),695 is_token_create,696 nesting_budget,697 )698 }699700 pub fn delete_token_property(701 collection: &RefungibleHandle<T>,702 sender: &T::CrossAccountId,703 token_id: TokenId,704 property_key: PropertyKey,705 nesting_budget: &dyn Budget,706 ) -> DispatchResult {707 Self::delete_token_properties(708 collection,709 sender,710 token_id,711 [property_key].into_iter(),712 nesting_budget,713 )714 }715716 /// Transfer RFT token pieces from one account to another.717 ///718 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.719 ///720 /// - `from`: Owner of token pieces to transfer.721 /// - `to`: Recepient of transfered token pieces.722 /// - `amount`: Amount of token pieces to transfer.723 /// - `token`: Token whos pieces should be transfered724 /// - `collection`: Collection that contains the token725 pub fn transfer(726 collection: &RefungibleHandle<T>,727 from: &T::CrossAccountId,728 to: &T::CrossAccountId,729 token: TokenId,730 amount: u128,731 nesting_budget: &dyn Budget,732 ) -> DispatchResult {733 ensure!(734 collection.limits.transfers_enabled(),735 <CommonError<T>>::TransferNotAllowed736 );737738 if collection.permissions.access() == AccessMode::AllowList {739 collection.check_allowlist(from)?;740 collection.check_allowlist(to)?;741 }742 <PalletCommon<T>>::ensure_correct_receiver(to)?;743744 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));745 let updated_balance_from = initial_balance_from746 .checked_sub(amount)747 .ok_or(<CommonError<T>>::TokenValueTooLow)?;748 let mut create_target = false;749 let from_to_differ = from != to;750 let updated_balance_to = if from != to {751 let old_balance = <Balance<T>>::get((collection.id, token, to));752 if old_balance == 0 {753 create_target = true;754 }755 Some(756 old_balance757 .checked_add(amount)758 .ok_or(ArithmeticError::Overflow)?,759 )760 } else {761 None762 };763764 let account_balance_from = if updated_balance_from == 0 {765 Some(766 <AccountBalance<T>>::get((collection.id, from))767 .checked_sub(1)768 // Should not occur769 .ok_or(ArithmeticError::Underflow)?,770 )771 } else {772 None773 };774 // Account data is created in token, AccountBalance should be increased775 // But only if from != to as we shouldn't check overflow in this case776 let account_balance_to = if create_target && from_to_differ {777 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))778 .checked_add(1)779 .ok_or(ArithmeticError::Overflow)?;780 ensure!(781 account_balance_to < collection.limits.account_token_ownership_limit(),782 <CommonError<T>>::AccountTokenLimitExceeded,783 );784785 Some(account_balance_to)786 } else {787 None788 };789790 // =========791792 <PalletStructure<T>>::nest_if_sent_to_token(793 from.clone(),794 to,795 collection.id,796 token,797 nesting_budget,798 )?;799800 if let Some(updated_balance_to) = updated_balance_to {801 // from != to802 if updated_balance_from == 0 {803 <Balance<T>>::remove((collection.id, token, from));804 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);805 } else {806 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);807 }808 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);809 if let Some(account_balance_from) = account_balance_from {810 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);811 <Owned<T>>::remove((collection.id, from, token));812 }813 if let Some(account_balance_to) = account_balance_to {814 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);815 <Owned<T>>::insert((collection.id, to, token), true);816 }817 }818819 <PalletEvm<T>>::deposit_log(820 ERC20Events::Transfer {821 from: *from.as_eth(),822 to: *to.as_eth(),823 value: amount.into(),824 }825 .to_log(T::EvmTokenAddressMapping::token_to_address(826 collection.id,827 token,828 )),829 );830831 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(832 collection.id,833 token,834 from.clone(),835 to.clone(),836 amount,837 ));838839 let total_supply = <TotalSupply<T>>::get((collection.id, token));840841 if amount == total_supply {842 // if token was fully owned by `from` and will be fully owned by `to` after transfer843 <PalletEvm<T>>::deposit_log(844 ERC721Events::Transfer {845 from: *from.as_eth(),846 to: *to.as_eth(),847 token_id: token.into(),848 }849 .to_log(collection_id_to_address(collection.id)),850 );851 } else if let Some(updated_balance_to) = updated_balance_to {852 // if `from` not equals `to`. This condition is needed to avoid sending event853 // when `from` fully owns token and sends part of token pieces to itself.854 if initial_balance_from == total_supply {855 // if token was fully owned by `from` and will be only partially owned by `to`856 // and `from` after transfer857 <PalletEvm<T>>::deposit_log(858 ERC721Events::Transfer {859 from: *from.as_eth(),860 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,861 token_id: token.into(),862 }863 .to_log(collection_id_to_address(collection.id)),864 );865 } else if updated_balance_to == total_supply {866 // if token was partially owned by `from` and will be fully owned by `to` after transfer867 <PalletEvm<T>>::deposit_log(868 ERC721Events::Transfer {869 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,870 to: *to.as_eth(),871 token_id: token.into(),872 }873 .to_log(collection_id_to_address(collection.id)),874 );875 }876 }877878 Ok(())879 }880881 /// Batched operation to create multiple RFT tokens.882 ///883 /// Same as `create_item` but creates multiple tokens.884 ///885 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.886 pub fn create_multiple_items(887 collection: &RefungibleHandle<T>,888 sender: &T::CrossAccountId,889 data: Vec<CreateItemData<T::CrossAccountId>>,890 nesting_budget: &dyn Budget,891 ) -> DispatchResult {892 if !collection.is_owner_or_admin(sender) {893 ensure!(894 collection.permissions.mint_mode(),895 <CommonError<T>>::PublicMintingNotAllowed896 );897 collection.check_allowlist(sender)?;898899 for item in data.iter() {900 for user in item.users.keys() {901 collection.check_allowlist(user)?;902 }903 }904 }905906 for item in data.iter() {907 for (owner, _) in item.users.iter() {908 <PalletCommon<T>>::ensure_correct_receiver(owner)?;909 }910 }911912 // Total pieces per tokens913 let totals = data914 .iter()915 .map(|data| {916 Ok(data917 .users918 .iter()919 .map(|u| u.1)920 .try_fold(0u128, |acc, v| acc.checked_add(*v))921 .ok_or(ArithmeticError::Overflow)?)922 })923 .collect::<Result<Vec<_>, DispatchError>>()?;924 for total in &totals {925 ensure!(926 *total <= MAX_REFUNGIBLE_PIECES,927 <Error<T>>::WrongRefungiblePieces928 );929 }930931 let first_token_id = <TokensMinted<T>>::get(collection.id);932 let tokens_minted = first_token_id933 .checked_add(data.len() as u32)934 .ok_or(ArithmeticError::Overflow)?;935 ensure!(936 tokens_minted < collection.limits.token_limit(),937 <CommonError<T>>::CollectionTokenLimitExceeded938 );939940 let mut balances = BTreeMap::new();941 for data in &data {942 for owner in data.users.keys() {943 let balance = balances944 .entry(owner)945 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));946 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;947948 ensure!(949 *balance <= collection.limits.account_token_ownership_limit(),950 <CommonError<T>>::AccountTokenLimitExceeded,951 );952 }953 }954955 for (i, token) in data.iter().enumerate() {956 let token_id = TokenId(first_token_id + i as u32 + 1);957 for (to, _) in token.users.iter() {958 <PalletStructure<T>>::check_nesting(959 sender.clone(),960 to,961 collection.id,962 token_id,963 nesting_budget,964 )?;965 }966 }967968 // =========969970 with_transaction(|| {971 for (i, data) in data.iter().enumerate() {972 let token_id = first_token_id + i as u32 + 1;973 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);974975 for (user, amount) in data.users.iter() {976 if *amount == 0 {977 continue;978 }979 <Balance<T>>::insert((collection.id, token_id, &user), amount);980 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);981 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(982 user,983 collection.id,984 TokenId(token_id),985 );986 }987988 if let Err(e) = Self::set_token_properties(989 collection,990 sender,991 TokenId(token_id),992 data.properties.clone().into_iter(),993 true,994 nesting_budget,995 ) {996 return TransactionOutcome::Rollback(Err(e));997 }998 }999 TransactionOutcome::Commit(Ok(()))1000 })?;10011002 <TokensMinted<T>>::insert(collection.id, tokens_minted);10031004 for (account, balance) in balances {1005 <AccountBalance<T>>::insert((collection.id, account), balance);1006 }10071008 for (i, token) in data.into_iter().enumerate() {1009 let token_id = first_token_id + i as u32 + 1;10101011 let receivers = token1012 .users1013 .into_iter()1014 .filter(|(_, amount)| *amount > 0)1015 .collect::<Vec<_>>();10161017 if let [(user, _)] = receivers.as_slice() {1018 // if there is exactly one receiver1019 <PalletEvm<T>>::deposit_log(1020 ERC721Events::Transfer {1021 from: H160::default(),1022 to: *user.as_eth(),1023 token_id: token_id.into(),1024 }1025 .to_log(collection_id_to_address(collection.id)),1026 );1027 } else if let [_, ..] = receivers.as_slice() {1028 // if there is more than one receiver1029 <PalletEvm<T>>::deposit_log(1030 ERC721Events::Transfer {1031 from: H160::default(),1032 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1033 token_id: token_id.into(),1034 }1035 .to_log(collection_id_to_address(collection.id)),1036 );1037 }10381039 for (user, amount) in receivers.into_iter() {1040 <PalletEvm<T>>::deposit_log(1041 ERC20Events::Transfer {1042 from: H160::default(),1043 to: *user.as_eth(),1044 value: amount.into(),1045 }1046 .to_log(T::EvmTokenAddressMapping::token_to_address(1047 collection.id,1048 TokenId(token_id),1049 )),1050 );1051 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1052 collection.id,1053 TokenId(token_id),1054 user,1055 amount,1056 ));1057 }1058 }1059 Ok(())1060 }10611062 pub fn set_allowance_unchecked(1063 collection: &RefungibleHandle<T>,1064 sender: &T::CrossAccountId,1065 spender: &T::CrossAccountId,1066 token: TokenId,1067 amount: u128,1068 ) {1069 if amount == 0 {1070 <Allowance<T>>::remove((collection.id, token, sender, spender));1071 } else {1072 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1073 }10741075 <PalletEvm<T>>::deposit_log(1076 ERC20Events::Approval {1077 owner: *sender.as_eth(),1078 spender: *spender.as_eth(),1079 value: amount.into(),1080 }1081 .to_log(T::EvmTokenAddressMapping::token_to_address(1082 collection.id,1083 token,1084 )),1085 );1086 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1087 collection.id,1088 token,1089 sender.clone(),1090 spender.clone(),1091 amount,1092 ))1093 }10941095 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1096 ///1097 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1098 pub fn set_allowance(1099 collection: &RefungibleHandle<T>,1100 sender: &T::CrossAccountId,1101 spender: &T::CrossAccountId,1102 token: TokenId,1103 amount: u128,1104 ) -> DispatchResult {1105 if collection.permissions.access() == AccessMode::AllowList {1106 collection.check_allowlist(sender)?;1107 collection.check_allowlist(spender)?;1108 }11091110 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11111112 if <Balance<T>>::get((collection.id, token, sender)) < amount {1113 ensure!(1114 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1115 <CommonError<T>>::CantApproveMoreThanOwned1116 );1117 }11181119 // =========11201121 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1122 Ok(())1123 }11241125 /// Returns allowance, which should be set after transaction1126 fn check_allowed(1127 collection: &RefungibleHandle<T>,1128 spender: &T::CrossAccountId,1129 from: &T::CrossAccountId,1130 token: TokenId,1131 amount: u128,1132 nesting_budget: &dyn Budget,1133 ) -> Result<Option<u128>, DispatchError> {1134 if spender.conv_eq(from) {1135 return Ok(None);1136 }1137 if collection.permissions.access() == AccessMode::AllowList {1138 // `from`, `to` checked in [`transfer`]1139 collection.check_allowlist(spender)?;1140 }1141 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1142 // TODO: should collection owner be allowed to perform this transfer?1143 ensure!(1144 <PalletStructure<T>>::check_indirectly_owned(1145 spender.clone(),1146 source.0,1147 source.1,1148 None,1149 nesting_budget1150 )?,1151 <CommonError<T>>::ApprovedValueTooLow,1152 );1153 return Ok(None);1154 }1155 let allowance =1156 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1157 if allowance.is_none() {1158 ensure!(1159 collection.ignores_allowance(spender),1160 <CommonError<T>>::ApprovedValueTooLow1161 );1162 }1163 Ok(allowance)1164 }11651166 /// Transfer RFT token pieces from one account to another.1167 ///1168 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1169 /// The owner should set allowance for the spender to transfer pieces.1170 ///1171 /// [`transfer`]: struct.Pallet.html#method.transfer1172 pub fn transfer_from(1173 collection: &RefungibleHandle<T>,1174 spender: &T::CrossAccountId,1175 from: &T::CrossAccountId,1176 to: &T::CrossAccountId,1177 token: TokenId,1178 amount: u128,1179 nesting_budget: &dyn Budget,1180 ) -> DispatchResult {1181 let allowance =1182 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11831184 // =========11851186 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1187 if let Some(allowance) = allowance {1188 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1189 }1190 Ok(())1191 }11921193 /// Burn RFT token pieces from the account.1194 ///1195 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1196 /// set allowance for the spender to burn pieces1197 ///1198 /// [`burn`]: struct.Pallet.html#method.burn1199 pub fn burn_from(1200 collection: &RefungibleHandle<T>,1201 spender: &T::CrossAccountId,1202 from: &T::CrossAccountId,1203 token: TokenId,1204 amount: u128,1205 nesting_budget: &dyn Budget,1206 ) -> DispatchResult {1207 let allowance =1208 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12091210 // =========12111212 Self::burn(collection, from, token, amount)?;1213 if let Some(allowance) = allowance {1214 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1215 }1216 Ok(())1217 }12181219 /// Create RFT token.1220 ///1221 /// The sender should be the owner/admin of the collection or collection should be configured1222 /// to allow public minting.1223 ///1224 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1225 /// of token pieces they will receive.1226 pub fn create_item(1227 collection: &RefungibleHandle<T>,1228 sender: &T::CrossAccountId,1229 data: CreateItemData<T::CrossAccountId>,1230 nesting_budget: &dyn Budget,1231 ) -> DispatchResult {1232 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1233 }12341235 /// Repartition RFT token.1236 ///1237 /// `repartition` will set token balance of the sender and total amount of token pieces.1238 /// Sender should own all of the token pieces. `repartition' could be done even if some1239 /// token pieces were burned before.1240 ///1241 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1242 pub fn repartition(1243 collection: &RefungibleHandle<T>,1244 owner: &T::CrossAccountId,1245 token: TokenId,1246 amount: u128,1247 ) -> DispatchResult {1248 ensure!(1249 amount <= MAX_REFUNGIBLE_PIECES,1250 <Error<T>>::WrongRefungiblePieces1251 );1252 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1253 // Ensure user owns all pieces1254 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1255 let balance = <Balance<T>>::get((collection.id, token, owner));1256 ensure!(1257 total_pieces == balance,1258 <Error<T>>::RepartitionWhileNotOwningAllPieces1259 );12601261 <Balance<T>>::insert((collection.id, token, owner), amount);1262 <TotalSupply<T>>::insert((collection.id, token), amount);12631264 if amount > total_pieces {1265 let mint_amount = amount - total_pieces;1266 <PalletEvm<T>>::deposit_log(1267 ERC20Events::Transfer {1268 from: H160::default(),1269 to: *owner.as_eth(),1270 value: mint_amount.into(),1271 }1272 .to_log(T::EvmTokenAddressMapping::token_to_address(1273 collection.id,1274 token,1275 )),1276 );1277 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1278 collection.id,1279 token,1280 owner.clone(),1281 mint_amount,1282 ));1283 } else if total_pieces > amount {1284 let burn_amount = total_pieces - amount;1285 <PalletEvm<T>>::deposit_log(1286 ERC20Events::Transfer {1287 from: *owner.as_eth(),1288 to: H160::default(),1289 value: burn_amount.into(),1290 }1291 .to_log(T::EvmTokenAddressMapping::token_to_address(1292 collection.id,1293 token,1294 )),1295 );1296 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1297 collection.id,1298 token,1299 owner.clone(),1300 burn_amount,1301 ));1302 }13031304 Ok(())1305 }13061307 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1308 let mut owner = None;1309 let mut count = 0;1310 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1311 count += 1;1312 if count > 1 {1313 return None;1314 }1315 owner = Some(key);1316 }1317 owner1318 }13191320 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1321 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1322 }13231324 pub fn set_collection_properties(1325 collection: &RefungibleHandle<T>,1326 sender: &T::CrossAccountId,1327 properties: Vec<Property>,1328 ) -> DispatchResult {1329 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1330 }13311332 pub fn delete_collection_properties(1333 collection: &RefungibleHandle<T>,1334 sender: &T::CrossAccountId,1335 property_keys: Vec<PropertyKey>,1336 ) -> DispatchResult {1337 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1338 }13391340 pub fn set_token_property_permissions(1341 collection: &RefungibleHandle<T>,1342 sender: &T::CrossAccountId,1343 property_permissions: Vec<PropertyKeyPermission>,1344 ) -> DispatchResult {1345 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1346 }13471348 pub fn set_scoped_token_property_permissions(1349 collection: &RefungibleHandle<T>,1350 sender: &T::CrossAccountId,1351 scope: PropertyScope,1352 property_permissions: Vec<PropertyKeyPermission>,1353 ) -> DispatchResult {1354 <PalletCommon<T>>::set_scoped_token_property_permissions(1355 collection,1356 sender,1357 scope,1358 property_permissions,1359 )1360 }13611362 /// Returns 10 token in no particular order.1363 ///1364 /// There is no direct way to get token holders in ascending order,1365 /// since `iter_prefix` returns values in no particular order.1366 /// Therefore, getting the 10 largest holders with a large value of holders1367 /// can lead to impact memory allocation + sorting with `n * log (n)`.1368 pub fn token_owners(1369 collection_id: CollectionId,1370 token: TokenId,1371 ) -> Option<Vec<T::CrossAccountId>> {1372 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1373 .map(|(owner, _amount)| owner)1374 .take(10)1375 .collect();13761377 if res.is_empty() {1378 None1379 } else {1380 Some(res)1381 }1382 }13831384 /// Sets the NFT token as a parent for the RFT token1385 ///1386 /// Throws if `sender` is not the owner of the NFT token.1387 /// Throws if `sender` is not the owner of all of the RFT token pieces.1388 pub fn set_parent_nft(1389 collection: &RefungibleHandle<T>,1390 rft_token_id: TokenId,1391 sender: T::CrossAccountId,1392 nft_collection: CollectionId,1393 nft_token: TokenId,1394 ) -> DispatchResult {1395 let handle = <CollectionHandle<T>>::try_get(nft_collection)?;1396 if handle.mode != CollectionMode::NFT {1397 return Err("Only NFT token could be parent to RFT".into());1398 }1399 let dispatch = T::CollectionDispatch::dispatch(handle);1400 let dispatch = dispatch.as_dyn();14011402 let owner = dispatch.token_owner(nft_token).ok_or("owner not found")?;1403 if owner != sender {1404 return Err("Only owned token could be set as parent".into());1405 }14061407 let nft_token_address =1408 T::CrossTokenAddressMapping::token_to_address(nft_collection, nft_token);14091410 Self::set_parent_nft_unchecked(collection, rft_token_id, sender, nft_token_address)1411 }14121413 /// Sets the NFT token as a parent for the RFT token1414 ///1415 /// `sender` should be the owner of the NFT token.1416 /// Throws if `sender` is not the owner of all of the RFT token pieces.1417 pub fn set_parent_nft_unchecked(1418 collection: &RefungibleHandle<T>,1419 rft_token_id: TokenId,1420 sender: T::CrossAccountId,1421 nft_token_address: T::CrossAccountId,1422 ) -> DispatchResult {1423 let owner_balance = <Balance<T>>::get((collection.id, rft_token_id, &sender));1424 let total_supply = <TotalSupply<T>>::get((collection.id, rft_token_id));1425 if total_supply != owner_balance {1426 return Err("token has multiple owners".into());1427 }14281429 let parent_nft_property_key = key::parent_nft();14301431 let parent_nft_property_value =1432 property_value_from_bytes(&nft_token_address.as_eth().to_fixed_bytes())1433 .expect("address should fit in value length limit");14341435 <Pallet<T>>::set_scoped_token_property(1436 collection.id,1437 rft_token_id,1438 PropertyScope::Eth,1439 Property {1440 key: parent_nft_property_key,1441 value: parent_nft_property_value,1442 },1443 )?;14441445 Ok(())1446 }1447}pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- 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
pallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungibleToken.soldiffbeforeafterboth--- 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
+{}
pallets/refungible/src/weights.rsdiffbeforeafterboth--- a/pallets/refungible/src/weights.rs
+++ b/pallets/refungible/src/weights.rs
@@ -3,7 +3,7 @@
//! Autogenerated weights for pallet_refungible
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-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))
+ }
}
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- 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<address> {
+ let (caller, name, description, token_prefix, base_uri_value) =
+ convert_data::<T>(caller, name, description, token_prefix, base_uri)?;
+ let data = make_data::<T>(
+ 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::<T>)?;
+
+ let handle = <CollectionHandle<T>>::try_get(collection_id).map_err(dispatch_to_evm::<T>)?;
+ <PalletCommon<T>>::set_scoped_token_property_permissions(
+ &handle,
+ &caller,
+ PropertyScope::Eth,
+ vec![parent_nft_property_permissions()],
+ )
+ .map_err(dispatch_to_evm::<T>)?;
+
+ 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<T> EvmCollectionHelpers<T>
@@ -216,27 +267,20 @@
#[weight(<SelfWeightOf<T>>::create_collection())]
fn create_refungible_collection(
- &self,
+ &mut self,
caller: caller,
name: string,
description: string,
token_prefix: string,
) -> Result<address> {
- let (caller, name, description, token_prefix, _base_uri) =
- convert_data::<T>(caller, name, description, token_prefix, "".into())?;
- let data = make_data::<T>(
+ create_refungible_collection_internal::<T>(
+ 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::<T>)?;
-
- let address = pallet_common::eth::collection_id_to_address(collection_id);
- Ok(address)
+ )
}
#[weight(<SelfWeightOf<T>>::create_collection())]
@@ -249,21 +293,14 @@
token_prefix: string,
base_uri: string,
) -> Result<address> {
- let (caller, name, description, token_prefix, base_uri_value) =
- convert_data::<T>(caller, name, description, token_prefix, base_uri)?;
- let data = make_data::<T>(
+ create_refungible_collection_internal::<T>(
+ 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::<T>)?;
-
- let address = pallet_common::eth::collection_id_to_address(collection_id);
- Ok(address)
+ )
}
/// Check if a collection exists
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- 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;
}
primitives/common/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/primitives/common/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+authors = ['Unique Network <support@uniquenetwork.io>']
+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"
primitives/common/src/constants.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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;
+}
primitives/common/src/lib.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub mod constants;
+pub mod types;
primitives/common/src/types.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<BlockNumber, BlakeTwo256>;
+
+ /// Opaque block type.
+ pub type Block = generic::Block<Header, UncheckedExtrinsic>;
+
+ pub trait RuntimeInstance {
+ type CrossAccountId: pallet_evm::account::CrossAccountId<sp_runtime::AccountId32>
+ + Send
+ + Sync
+ + 'static;
+
+ type TransactionConverter: fp_rpc::ConvertTransaction<UncheckedExtrinsic>
+ + 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 = <<Signature as Verify>::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;
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- 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()]
runtime/common/Cargo.tomldiffbeforeafterboth--- a/runtime/common/Cargo.toml
+++ /dev/null
@@ -1,115 +0,0 @@
-[package]
-authors = ['Unique Network <support@uniquenetwork.io>']
-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" }
runtime/common/config/ethereum.rsdiffbeforeafterboth--- /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<Runtime>;
+
+impl pallet_evm::account::Config for Runtime {
+ type CrossAccountId = CrossAccountId;
+ type EvmAddressMapping = pallet_evm::HashedAddressMapping<Self::Hashing>;
+ 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 / <Runtime as frame_system::Config>::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<F>(core::marker::PhantomData<F>);
+impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
+ fn find_author<'a, I>(digests: I) -> Option<H160>
+ where
+ I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
+ {
+ 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<Self>;
+ type CallOrigin = EnsureAddressTruncated<Self>;
+ type WithdrawOrigin = EnsureAddressTruncated<Self>;
+ type AddressMapping = HashedAddressMapping<Self::Hashing>;
+ type PrecompilesType = ();
+ type PrecompilesValue = ();
+ type Currency = Balances;
+ type Event = Event;
+ type OnMethodCall = (
+ pallet_evm_migration::OnMethodCall<Self>,
+ pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
+ CollectionDispatchT<Self>,
+ pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
+ );
+ type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
+ type ChainId = ChainId;
+ type Runner = pallet_evm::runner::stack::Runner<Self>;
+ type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
+ type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
+ type FindAuthor = EthereumFindAuthor<Aura>;
+}
+
+impl pallet_evm_migration::Config for Runtime {
+ type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
+}
+
+impl pallet_ethereum::Config for Runtime {
+ type Event = Event;
+ type StateRoot = pallet_ethereum::IntermediateStateRoot<Self>;
+}
+
+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;
+}
runtime/common/config/mod.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+pub mod ethereum;
+pub mod orml;
+pub mod pallets;
+pub mod parachain;
+pub mod sponsoring;
+pub mod substrate;
+pub mod xcm;
runtime/common/config/orml.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<Runtime>;
+ type MinVestedTransfer = MinVestedTransfer;
+ type VestedTransferOrigin = EnsureSigned<AccountId>;
+ type WeightInfo = ();
+ type MaxVestingSchedules = MaxVestingSchedules;
+ type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
+}
runtime/common/config/pallets/mod.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<Self>;
+ type Event = Event;
+ type Currency = Balances;
+ type CollectionCreationPrice = CollectionCreationPrice;
+ type TreasuryAccountId = TreasuryAccountId;
+ type CollectionDispatch = CollectionDispatchT<Self>;
+
+ type EvmTokenAddressMapping = EvmTokenAddressMapping;
+ type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
+ type ContractAddress = EvmCollectionHelpersAddress;
+}
+
+impl pallet_structure::Config for Runtime {
+ type Event = Event;
+ type Call = Call;
+ type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
+}
+
+impl pallet_fungible::Config for Runtime {
+ type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
+}
+impl pallet_refungible::Config for Runtime {
+ type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
+}
+impl pallet_nonfungible::Config for Runtime {
+ type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
+}
+
+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<Runtime>;
+}
+
+impl pallet_unique::Config for Runtime {
+ type Event = Event;
+ type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
+ type CommonWeightInfo = CommonWeights<Self>;
+ type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
+}
runtime/common/config/pallets/rmrk.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+use crate::{Runtime, Event};
+
+impl pallet_proxy_rmrk_core::Config for Runtime {
+ type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
+ type Event = Event;
+}
+
+impl pallet_proxy_rmrk_equip::Config for Runtime {
+ type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight<Self>;
+ type Event = Event;
+}
runtime/common/config/pallets/scheduler.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<u32> = Some(10);
+ pub const Preimage: Option<u32> = Some(10);
+}
+
+/// Used the compare the privilege of an origin inside the scheduler.
+pub struct OriginPrivilegeCmp;
+
+impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
+ fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
+ 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<AccountId>;
+ type MaxScheduledPerBlock = MaxScheduledPerBlock;
+ type WeightInfo = ();
+ type CallExecutor = SchedulerPaymentExecutor;
+ type OriginPrivilegeCmp = OriginPrivilegeCmp;
+ type PreimageProvider = ();
+ type NoPreimagePostponement = NoPreimagePostponement;
+}
runtime/common/config/parachain.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<Self>;
+ type OnSystemEvent = ();
+ // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
+ // MaxDownwardMessageWeight,
+ // XcmExecutor<XcmConfig>,
+ // 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 {}
runtime/common/config/sponsoring.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<Runtime>,
+ pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
+);
+
+impl pallet_charge_transaction::Config for Runtime {
+ type SponsorshipHandler = SponsorshipHandler;
+}
runtime/common/config/substrate.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<Balance>;
+ /// 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<BlockNumber, BlakeTwo256>;
+ /// 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<AccountId, ()>;
+ /// 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<Self>;
+ /// 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<Self>;
+ /// 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<Self>;
+}
+
+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<T>(sp_std::marker::PhantomData<T>);
+
+impl<T> WeightToFeePolynomial for LinearFee<T>
+where
+ T: BaseArithmetic + From<u32> + Copy + Unsigned,
+{
+ type Balance = T;
+
+ fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
+ 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<Balances, DealWithFees>;
+ type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
+ type OperationalFeeMultiplier = OperationalFeeMultiplier;
+ type WeightToFee = LinearFee<Balance>;
+ 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<AccountId>;
+ type RejectOrigin = EnsureRoot<AccountId>;
+ 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<Self>;
+ 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;
+}
runtime/common/config/xcm.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<AccountId>,
+ // Sibling parachain origins convert to AccountId via the `ParaId::into`.
+ SiblingParachainConvertsVia<Sibling, AccountId>,
+ // Straight up local `AccountId32` origins just alias directly to `AccountId`.
+ AccountId32Aliases<RelayNetwork, AccountId>,
+);
+
+pub struct OnlySelfCurrency;
+impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
+ fn matches_fungible(a: &MultiAsset) -> Option<B> {
+ match (&a.id, &a.fun) {
+ (Concrete(_), XcmFungible(ref amount)) => CheckedConversion::checked_from(*amount),
+ _ => None,
+ }
+ }
+}
+
+/// Means for transacting assets on this chain.
+pub type LocalAssetTransactor = CurrencyAdapter<
+ // Use this currency:
+ Balances,
+ // Use this currency when it is a fungible asset matching the given location or name:
+ 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<Origin, AccountId, RelayNetwork>,);
+
+/// 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<ParachainSystem, ()>,
+ // ..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<LocationToAccountId, Origin>,
+ // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
+ // recognised.
+ RelayChainAsNative<RelayOrigin, Origin>,
+ // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
+ // recognised.
+ SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
+ // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
+ // transaction from the Root origin.
+ ParentAsSuperuser<Origin>,
+ // Native signed account converter; this just converts an `AccountId32` origin into a normal
+ // `Origin::Signed` origin of the same 32-byte value.
+ SignedAccountId32AsNative<RelayNetwork, Origin>,
+ // Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
+ XcmPassthrough<Origin>,
+);
+
+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> = {
+ MultiLocation { parents: 1, interior: Here } |
+ MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
+ };
+}
+
+pub type Barrier = (
+ TakeWeightCredit,
+ AllowTopLevelPaidExecutionFrom<Everything>,
+ // ^^^ Parent & its unit plurality gets free execution
+);
+
+pub struct UsingOnlySelfCurrencyComponents<
+ WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+ AssetId: Get<MultiLocation>,
+ AccountId,
+ Currency: CurrencyT<AccountId>,
+ OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+>(
+ Weight,
+ Currency::Balance,
+ PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
+);
+impl<
+ WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
+ AssetId: Get<MultiLocation>,
+ AccountId,
+ Currency: CurrencyT<AccountId>,
+ OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+ > WeightTrader
+ for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+{
+ fn new() -> Self {
+ Self(0, Zero::zero(), PhantomData)
+ }
+
+ fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
+ let amount = WeightToFee::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<MultiAsset> {
+ 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<Balance = Currency::Balance>,
+ AssetId: Get<MultiLocation>,
+ AccountId,
+ Currency: CurrencyT<AccountId>,
+ OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
+ > Drop
+ for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
+{
+ fn drop(&mut self) {
+ OnUnbalanced::on_unbalanced(Currency::issue(self.1));
+ }
+}
+
+pub struct XcmConfig;
+impl Config for XcmConfig {
+ type Call = Call;
+ 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<Ancestry>;
+ type Barrier = Barrier;
+ type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
+ type Trader =
+ UsingOnlySelfCurrencyComponents<LinearFee<Balance>, 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<Origin, LocalOriginToLocation>;
+ type XcmRouter = XcmRouter;
+ type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
+ type XcmExecuteFilter = Everything;
+ type XcmExecutor = XcmExecutor<XcmConfig>;
+ type XcmTeleportFilter = Everything;
+ type XcmReserveTransferFilter = Everything;
+ type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
+ type LocationInverter = LocationInverter<Ancestry>;
+ 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<XcmConfig>;
+}
+
+impl cumulus_pallet_xcmp_queue::Config for Runtime {
+ type WeightInfo = ();
+ type Event = Event;
+ type XcmExecutor = XcmExecutor<XcmConfig>;
+ type ChannelInfo = ParachainSystem;
+ type VersionWrapper = ();
+ type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+ type ControllerOrigin = EnsureRoot<AccountId>;
+ type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
+}
+
+impl cumulus_pallet_dmp_queue::Config for Runtime {
+ type Event = Event;
+ type XcmExecutor = XcmExecutor<XcmConfig>;
+ type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
+}
runtime/common/constants.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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;
+}
runtime/common/construct_runtime/mod.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<T>, ValidateUnsigned} = 20,
+ ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
+
+ Aura: pallet_aura::{Pallet, Config<T>} = 22,
+ AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
+
+ Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 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<T>} = 34,
+ Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
+ System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
+ Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
+ // Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
+ // Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
+
+ // XCM helpers.
+ XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
+ PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
+ CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
+ DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
+
+ // Unique Pallets
+ Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
+ Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
+
+ #[runtimes(opal)]
+ Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 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<T>} = 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<T>} = 70,
+
+ #[runtimes(opal)]
+ RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event<T>} = 71,
+
+ #[runtimes(opal)]
+ RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
+
+ // Frontier
+ EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 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,
+ }
+ }
+ }
+}
runtime/common/construct_runtime/util.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+#[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)*)
+ }
+ };
+}
runtime/common/dispatch.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<T>
+where
+ T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config,
+{
+ Fungible(FungibleHandle<T>),
+ Nonfungible(NonfungibleHandle<T>),
+ Refungible(RefungibleHandle<T>),
+}
+impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
+where
+ T: pallet_common::Config
+ + pallet_unique::Config
+ + pallet_fungible::Config
+ + pallet_nonfungible::Config
+ + pallet_refungible::Config,
+{
+ fn create(
+ sender: T::CrossAccountId,
+ data: CreateCollectionData<T::AccountId>,
+ ) -> Result<CollectionId, DispatchError> {
+ let id = match data.mode {
+ CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data, false)?,
+ CollectionMode::Fungible(decimal_points) => {
+ // check params
+ ensure!(
+ decimal_points <= MAX_DECIMAL_POINTS,
+ pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
+ );
+ <PalletFungible<T>>::init_collection(sender, data)?
+ }
+
+ #[cfg(feature = "refungible")]
+ CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, data)?,
+
+ #[cfg(not(feature = "refungible"))]
+ CollectionMode::ReFungible => return unsupported!(T),
+ };
+ Ok(id)
+ }
+
+ fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> 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<T>) -> 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<T> {
+ 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<T> {
+ match self {
+ Self::Fungible(h) => h,
+ Self::Nonfungible(h) => h,
+ Self::Refungible(h) => h,
+ }
+ }
+}
+
+impl<T> pallet_evm::OnMethodCall<T> for CollectionDispatchT<T>
+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(<CollectionById<T>>::contains_key)
+ .unwrap_or(false)
+ }
+ fn get_code(target: &H160) -> Option<Vec<u8>> {
+ if let Some(collection_id) = map_eth_to_id(target) {
+ let collection = <CollectionById<T>>::get(collection_id)?;
+ Some(
+ match collection.mode {
+ CollectionMode::NFT => <NonfungibleHandle<T>>::CODE,
+ CollectionMode::Fungible(_) => <FungibleHandle<T>>::CODE,
+ CollectionMode::ReFungible => <RefungibleHandle<T>>::CODE,
+ }
+ .to_owned(),
+ )
+ } else if let Some((collection_id, _token_id)) =
+ <T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(target)
+ {
+ let collection = <CollectionById<T>>::get(collection_id)?;
+ if collection.mode != CollectionMode::ReFungible {
+ return None;
+ }
+ // TODO: check token existence
+ Some(<RefungibleTokenHandle<T>>::CODE.to_owned())
+ } else {
+ None
+ }
+ }
+ fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
+ if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
+ let collection =
+ <CollectionHandle<T>>::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)) =
+ <T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
+ &handle.code_address(),
+ ) {
+ let collection =
+ <CollectionHandle<T>>::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
+ }
+ }
+}
runtime/common/ethereum/mod.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+pub mod self_contained_call;
+pub mod sponsoring;
+pub mod transaction_converter;
runtime/common/ethereum/self_contained_call.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<Result<Self::SignedInfo, TransactionValidityError>> {
+ match self {
+ Call::Ethereum(call) => call.check_self_contained(),
+ _ => None,
+ }
+ }
+
+ fn validate_self_contained(
+ &self,
+ info: &Self::SignedInfo,
+ dispatch_info: &DispatchInfoOf<Call>,
+ len: usize,
+ ) -> Option<TransactionValidity> {
+ match self {
+ Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
+ _ => None,
+ }
+ }
+
+ fn pre_dispatch_self_contained(
+ &self,
+ info: &Self::SignedInfo,
+ ) -> Option<Result<(), TransactionValidityError>> {
+ match self {
+ Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
+ _ => None,
+ }
+ }
+
+ fn apply_self_contained(
+ self,
+ info: Self::SignedInfo,
+ ) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
+ match self {
+ call @ Call::Ethereum(pallet_ethereum::Call::transact { .. }) => Some(call.dispatch(
+ Origin::from(pallet_ethereum::RawOrigin::EthereumTransaction(info)),
+ )),
+ _ => None,
+ }
+ }
+}
runtime/common/ethereum/sponsoring.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+//! 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<Runtime>,
+ pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
+);
+
+pub struct UniqueEthSponsorshipHandler<T: UniqueConfig>(PhantomData<*const T>);
+impl<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>
+ SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)> for UniqueEthSponsorshipHandler<T>
+{
+ fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
+ let collection_id = map_eth_to_id(&call.0)?;
+ let collection = <CollectionHandle<T>>::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 = <UniqueNFTCall<T>>::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::<T>(
+ &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::<T>(&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::<T>(
+ &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::<T>(&collection, &from, &token_id).map(|()| sponsor)
+ }
+ UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
+ let token_id: TokenId = token_id.try_into().ok()?;
+ withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
+ .map(|()| sponsor)
+ }
+ _ => None,
+ }
+ }
+ CollectionMode::Fungible(_) => {
+ let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
+ #[allow(clippy::single_match)]
+ match call {
+ UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
+ withdraw_transfer::<T>(&collection, who, &TokenId::default())
+ .map(|()| sponsor)
+ }
+ UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
+ let from = T::CrossAccountId::from_eth(from);
+ withdraw_transfer::<T>(&collection, &from, &TokenId::default())
+ .map(|()| sponsor)
+ }
+ UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
+ withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
+ .map(|()| sponsor)
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ }?))
+ }
+}
runtime/common/ethereum/transaction_converter.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+use codec::{Encode, Decode};
+use crate::{opaque, Runtime, UncheckedExtrinsic};
+
+pub struct TransactionConverter;
+
+impl fp_rpc::ConvertTransaction<UncheckedExtrinsic> for TransactionConverter {
+ fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
+ UncheckedExtrinsic::new_unsigned(
+ pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
+ )
+ }
+}
+
+impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
+ fn convert_transaction(
+ &self,
+ transaction: pallet_ethereum::Transaction,
+ ) -> opaque::UncheckedExtrinsic {
+ let extrinsic = UncheckedExtrinsic::new_unsigned(
+ pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
+ );
+ let encoded = extrinsic.encode();
+ opaque::UncheckedExtrinsic::decode(&mut &encoded[..])
+ .expect("Encoded extrinsic is always valid")
+ }
+}
runtime/common/instance.rsdiffbeforeafterboth--- /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
+ }
+}
runtime/common/mod.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<AccountId, ()>;
+/// Block header type as expected by this runtime.
+pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
+/// Block type as expected by this runtime.
+pub type Block = generic::Block<Header, UncheckedExtrinsic>;
+/// A Block signed with a Justification
+pub type SignedBlock = generic::SignedBlock<Block>;
+/// BlockId type as expected by this runtime.
+pub type BlockId = generic::BlockId<Block>;
+
+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<Runtime>;
+
+pub type SignedExtra = (
+ frame_system::CheckSpecVersion<Runtime>,
+ // system::CheckTxVersion<Runtime>,
+ frame_system::CheckGenesis<Runtime>,
+ frame_system::CheckEra<Runtime>,
+ frame_system::CheckNonce<Runtime>,
+ frame_system::CheckWeight<Runtime>,
+ ChargeTransactionPayment,
+ //pallet_contract_helpers::ContractHelpersExtension<Runtime>,
+ pallet_ethereum::FakeTransactionFinalizer<Runtime>,
+);
+
+/// Unchecked extrinsic type as expected by this runtime.
+pub type UncheckedExtrinsic =
+ fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
+
+/// Extrinsic type that has already been checked.
+pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
+
+/// Executive: handles dispatch to the various modules.
+pub type Executive = frame_executive::Executive<
+ Runtime,
+ Block,
+ frame_system::ChainContext<Runtime>,
+ Runtime,
+ AllPalletsReversedWithSystemFirst,
+>;
+
+type NegativeImbalance = <Balances as Currency<AccountId>>::NegativeImbalance;
+
+pub struct DealWithFees;
+impl OnUnbalanced<NegativeImbalance> for DealWithFees {
+ fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
+ 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<T>(sp_std::marker::PhantomData<T>);
+
+impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
+ for RelayChainBlockNumberProvider<T>
+{
+ type BlockNumber = BlockNumber;
+
+ fn current_block_number() -> Self::BlockNumber {
+ cumulus_pallet_parachain_system::Pallet::<T>::validation_data()
+ .map(|d| d.relay_parent_number)
+ .unwrap_or_default()
+ }
+}
+
+pub(crate) struct CheckInherents;
+
+impl cumulus_pallet_parachain_system::CheckInherents<Block> 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<XAccountId, XBalance> {
+ /// Transfer tokens to the given account from the Parachain account.
+ TransferToken(XAccountId, XBalance),
+}
runtime/common/runtime_apis.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+#[macro_export]
+macro_rules! dispatch_unique_runtime {
+ ($collection:ident.$method:ident($($name:ident),*)) => {{
+ let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::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<Block, CrossAccountId, AccountId> for Runtime {
+ fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>, DispatchError> {
+ dispatch_unique_runtime!(collection.account_tokens(account))
+ }
+ fn collection_tokens(collection: CollectionId) -> Result<Vec<TokenId>, DispatchError> {
+ dispatch_unique_runtime!(collection.collection_tokens())
+ }
+ fn token_exists(collection: CollectionId, token: TokenId) -> Result<bool, DispatchError> {
+ dispatch_unique_runtime!(collection.token_exists(token))
+ }
+
+ fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
+ dispatch_unique_runtime!(collection.token_owner(token))
+ }
+
+ fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError> {
+ dispatch_unique_runtime!(collection.token_owners(token))
+ }
+
+ fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
+ let budget = up_data_structs::budget::Value::new(10);
+
+ Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
+ }
+ fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
+ Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
+ }
+ fn collection_properties(
+ collection: CollectionId,
+ keys: Option<Vec<Vec<u8>>>
+ ) -> Result<Vec<Property>, 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<Vec<Vec<u8>>>
+ ) -> Result<Vec<Property>, 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<Vec<Vec<u8>>>
+ ) -> Result<Vec<PropertyKeyPermission>, 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<Vec<Vec<u8>>>
+ ) -> Result<TokenData<CrossAccountId>, 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<u32, DispatchError> {
+ dispatch_unique_runtime!(collection.total_supply())
+ }
+ fn account_balance(collection: CollectionId, account: CrossAccountId) -> Result<u32, DispatchError> {
+ dispatch_unique_runtime!(collection.account_balance(account))
+ }
+ fn balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<u128, DispatchError> {
+ dispatch_unique_runtime!(collection.balance(account, token))
+ }
+ fn allowance(
+ collection: CollectionId,
+ sender: CrossAccountId,
+ spender: CrossAccountId,
+ token: TokenId,
+ ) -> Result<u128, DispatchError> {
+ dispatch_unique_runtime!(collection.allowance(sender, spender, token))
+ }
+
+ fn adminlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
+ Ok(<pallet_common::Pallet<Runtime>>::adminlist(collection))
+ }
+ fn allowlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
+ Ok(<pallet_common::Pallet<Runtime>>::allowlist(collection))
+ }
+ fn allowed(collection: CollectionId, user: CrossAccountId) -> Result<bool, DispatchError> {
+ Ok(<pallet_common::Pallet<Runtime>>::allowed(collection, user))
+ }
+ fn last_token_id(collection: CollectionId) -> Result<TokenId, DispatchError> {
+ dispatch_unique_runtime!(collection.last_token_id())
+ }
+ fn collection_by_id(collection: CollectionId) -> Result<Option<RpcCollection<AccountId>>, DispatchError> {
+ Ok(<pallet_common::Pallet<Runtime>>::rpc_collection(collection))
+ }
+ fn collection_stats() -> Result<CollectionStats, DispatchError> {
+ Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
+ }
+ fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>, DispatchError> {
+ Ok(<UniqueSponsorshipPredict<Runtime> as SponsorshipPredict<Runtime>>::predict(
+ collection,
+ account,
+ token
+ ))
+ }
+
+ fn effective_collection_limits(collection: CollectionId) -> Result<Option<CollectionLimits>, DispatchError> {
+ Ok(<pallet_common::Pallet<Runtime>>::effective_collection_limits(collection))
+ }
+
+ fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result<Option<u128>, DispatchError> {
+ dispatch_unique_runtime!(collection.total_pieces(token_id))
+ }
+ }
+
+ impl rmrk_rpc::RmrkApi<
+ Block,
+ AccountId,
+ RmrkCollectionInfo<AccountId>,
+ RmrkInstanceInfo<AccountId>,
+ RmrkResourceInfo,
+ RmrkPropertyInfo,
+ RmrkBaseInfo<AccountId>,
+ RmrkPartType,
+ RmrkTheme
+ > for Runtime {
+ fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>();
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn collection_properties(
+ collection_id: RmrkCollectionId,
+ filter_keys: Option<Vec<RmrkPropertyKey>>
+ ) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(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<Vec<RmrkPropertyKey>>
+ ) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(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<Vec<RmrkResourceInfo>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(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<Option<u32>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+
+ #[allow(unused_variables)]
+ fn theme(
+ base_id: RmrkBaseId,
+ theme_name: RmrkThemeName,
+ filter_keys: Option<Vec<RmrkPropertyKey>>
+ ) -> Result<Option<RmrkTheme>, DispatchError> {
+ #[cfg(feature = "rmrk")]
+ return pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys);
+
+ #[cfg(not(feature = "rmrk"))]
+ return unsupported!();
+ }
+ }
+
+ impl sp_api::Core<Block> for Runtime {
+ fn version() -> RuntimeVersion {
+ VERSION
+ }
+
+ fn execute_block(block: Block) {
+ Executive::execute_block(block)
+ }
+
+ fn initialize_block(header: &<Block as BlockT>::Header) {
+ Executive::initialize_block(header)
+ }
+ }
+
+ impl sp_api::Metadata<Block> for Runtime {
+ fn metadata() -> OpaqueMetadata {
+ OpaqueMetadata::new(Runtime::metadata().into())
+ }
+ }
+
+ impl sp_block_builder::BlockBuilder<Block> for Runtime {
+ fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
+ Executive::apply_extrinsic(extrinsic)
+ }
+
+ fn finalize_block() -> <Block as BlockT>::Header {
+ Executive::finalize_block()
+ }
+
+ fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
+ data.create_extrinsics()
+ }
+
+ fn check_inherents(
+ block: Block,
+ data: sp_inherents::InherentData,
+ ) -> sp_inherents::CheckInherentsResult {
+ data.check_extrinsics(&block)
+ }
+
+ // fn random_seed() -> <Block as BlockT>::Hash {
+ // RandomnessCollectiveFlip::random_seed().0
+ // }
+ }
+
+ impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
+ fn validate_transaction(
+ source: TransactionSource,
+ tx: <Block as BlockT>::Extrinsic,
+ hash: <Block as BlockT>::Hash,
+ ) -> TransactionValidity {
+ Executive::validate_transaction(source, tx, hash)
+ }
+ }
+
+ impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
+ fn offchain_worker(header: &<Block as BlockT>::Header) {
+ Executive::offchain_worker(header)
+ }
+ }
+
+ impl fp_rpc::EthereumRuntimeRPCApi<Block> for Runtime {
+ fn chain_id() -> u64 {
+ <Runtime as pallet_evm::Config>::ChainId::get()
+ }
+
+ fn account_basic(address: H160) -> EVMAccount {
+ let (account, _) = EVM::account_basic(&address);
+ account
+ }
+
+ fn gas_price() -> U256 {
+ let (price, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
+ price
+ }
+
+ fn account_code_at(address: H160) -> Vec<u8> {
+ EVM::account_codes(address)
+ }
+
+ fn author() -> H160 {
+ <pallet_evm::Pallet<Runtime>>::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<u8>,
+ value: U256,
+ gas_limit: U256,
+ max_fee_per_gas: Option<U256>,
+ max_priority_fee_per_gas: Option<U256>,
+ nonce: Option<U256>,
+ estimate: bool,
+ access_list: Option<Vec<(H160, Vec<H256>)>>,
+ ) -> Result<pallet_evm::CallInfo, sp_runtime::DispatchError> {
+ let config = if estimate {
+ let mut config = <Runtime as pallet_evm::Config>::config().clone();
+ config.estimate = true;
+ Some(config)
+ } else {
+ None
+ };
+
+ let is_transactional = false;
+ <Runtime as pallet_evm::Config>::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(|| <Runtime as pallet_evm::Config>::config()),
+ ).map_err(|err| err.error.into())
+ }
+
+ #[allow(clippy::redundant_closure)]
+ fn create(
+ from: H160,
+ data: Vec<u8>,
+ value: U256,
+ gas_limit: U256,
+ max_fee_per_gas: Option<U256>,
+ max_priority_fee_per_gas: Option<U256>,
+ nonce: Option<U256>,
+ estimate: bool,
+ access_list: Option<Vec<(H160, Vec<H256>)>>,
+ ) -> Result<pallet_evm::CreateInfo, sp_runtime::DispatchError> {
+ let config = if estimate {
+ let mut config = <Runtime as pallet_evm::Config>::config().clone();
+ config.estimate = true;
+ Some(config)
+ } else {
+ None
+ };
+
+ let is_transactional = false;
+ <Runtime as pallet_evm::Config>::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(|| <Runtime as pallet_evm::Config>::config()),
+ ).map_err(|err| err.error.into())
+ }
+
+ fn current_transaction_statuses() -> Option<Vec<TransactionStatus>> {
+ Ethereum::current_transaction_statuses()
+ }
+
+ fn current_block() -> Option<pallet_ethereum::Block> {
+ Ethereum::current_block()
+ }
+
+ fn current_receipts() -> Option<Vec<pallet_ethereum::Receipt>> {
+ Ethereum::current_receipts()
+ }
+
+ fn current_all() -> (
+ Option<pallet_ethereum::Block>,
+ Option<Vec<pallet_ethereum::Receipt>>,
+ Option<Vec<TransactionStatus>>
+ ) {
+ (
+ Ethereum::current_block(),
+ Ethereum::current_receipts(),
+ Ethereum::current_transaction_statuses()
+ )
+ }
+
+ fn extrinsic_filter(xts: Vec<<Block as sp_api::BlockT>::Extrinsic>) -> Vec<pallet_ethereum::Transaction> {
+ xts.into_iter().filter_map(|xt| match xt.0.function {
+ Call::Ethereum(pallet_ethereum::Call::transact { transaction }) => Some(transaction),
+ _ => None
+ }).collect()
+ }
+
+ fn elasticity() -> Option<Permill> {
+ None
+ }
+ }
+
+ impl fp_rpc::ConvertTransactionRuntimeApi<Block> for Runtime {
+ fn convert_transaction(transaction: pallet_ethereum::Transaction) -> <Block as BlockT>::Extrinsic {
+ UncheckedExtrinsic::new_unsigned(
+ pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
+ )
+ }
+ }
+
+ impl sp_session::SessionKeys<Block> for Runtime {
+ fn decode_session_keys(
+ encoded: Vec<u8>,
+ ) -> Option<Vec<(Vec<u8>, KeyTypeId)>> {
+ SessionKeys::decode_into_raw_public_keys(&encoded)
+ }
+
+ fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
+ SessionKeys::generate(seed)
+ }
+ }
+
+ impl sp_consensus_aura::AuraApi<Block, AuraId> for Runtime {
+ fn slot_duration() -> sp_consensus_aura::SlotDuration {
+ sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration())
+ }
+
+ fn authorities() -> Vec<AuraId> {
+ Aura::authorities().to_vec()
+ }
+ }
+
+ impl cumulus_primitives_core::CollectCollationInfo<Block> for Runtime {
+ fn collect_collation_info(header: &<Block as BlockT>::Header) -> cumulus_primitives_core::CollationInfo {
+ ParachainSystem::collect_collation_info(header)
+ }
+ }
+
+ impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {
+ fn account_nonce(account: AccountId) -> Index {
+ System::account_nonce(account)
+ }
+ }
+
+ impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> for Runtime {
+ fn query_info(uxt: <Block as BlockT>::Extrinsic, len: u32) -> RuntimeDispatchInfo<Balance> {
+ TransactionPayment::query_info(uxt, len)
+ }
+ fn query_fee_details(uxt: <Block as BlockT>::Extrinsic, len: u32) -> FeeDetails<Balance> {
+ TransactionPayment::query_fee_details(uxt, len)
+ }
+ }
+
+ /*
+ impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
+ for Runtime
+ {
+ fn call(
+ origin: AccountId,
+ dest: AccountId,
+ value: Balance,
+ gas_limit: u64,
+ input_data: Vec<u8>,
+ ) -> 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<Hash>,
+ data: Vec<u8>,
+ salt: Vec<u8>,
+ ) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, BlockNumber>
+ {
+ 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<BlockNumber> {
+ Contracts::rent_projection(address)
+ }
+ }
+ */
+
+ #[cfg(feature = "runtime-benchmarks")]
+ impl frame_benchmarking::Benchmark<Block> for Runtime {
+ fn benchmark_metadata(extra: bool) -> (
+ Vec<frame_benchmarking::BenchmarkList>,
+ Vec<frame_support::traits::StorageInfo>,
+ ) {
+ use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList};
+ use frame_support::traits::StorageInfoTrait;
+
+ let mut list = Vec::<BenchmarkList>::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<Vec<frame_benchmarking::BenchmarkBatch>, sp_runtime::RuntimeString> {
+ use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey};
+
+ let allowlist: Vec<TrackedStorageKey> = 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::<BenchmarkBatch>::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<Block> 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)
+ }
+ }
+ }
+ }
+}
runtime/common/scheduler.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<Runtime>,
+ frame_system::CheckGenesis<Runtime>,
+ frame_system::CheckEra<Runtime>,
+ frame_system::CheckNonce<Runtime>,
+ frame_system::CheckWeight<Runtime>,
+);
+
+fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
+ (
+ frame_system::CheckSpecVersion::<Runtime>::new(),
+ frame_system::CheckGenesis::<Runtime>::new(),
+ frame_system::CheckEra::<Runtime>::from(Era::Immortal),
+ frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
+ from,
+ )),
+ frame_system::CheckWeight::<Runtime>::new(),
+ // sponsoring transaction logic
+ // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
+ )
+}
+
+pub struct SchedulerPaymentExecutor;
+
+impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
+ DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
+where
+ <T as frame_system::Config>::Call: Member
+ + Dispatchable<Origin = Origin, Info = DispatchInfo>
+ + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
+ + GetDispatchInfo
+ + From<frame_system::Call<Runtime>>,
+ SelfContainedSignedInfo: Send + Sync + 'static,
+ Call: From<<T as frame_system::Config>::Call>
+ + From<<T as pallet_unique_scheduler::Config>::Call>
+ + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
+ sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
+{
+ fn dispatch_call(
+ signer: <T as frame_system::Config>::AccountId,
+ call: <T as pallet_unique_scheduler::Config>::Call,
+ ) -> Result<
+ Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
+ 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::<Runtime>(&dispatch_info, 0)
+ }
+
+ fn reserve_balance(
+ id: [u8; 16],
+ sponsor: <T as frame_system::Config>::AccountId,
+ call: <T as pallet_unique_scheduler::Config>::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());
+
+ <Balances as NamedReservableCurrency<AccountId>>::reserve_named(
+ &id,
+ &(sponsor.into()),
+ weight,
+ )
+ }
+
+ fn pay_for_call(
+ id: [u8; 16],
+ sponsor: <T as frame_system::Config>::AccountId,
+ call: <T as pallet_unique_scheduler::Config>::Call,
+ ) -> Result<u128, DispatchError> {
+ let dispatch_info = call.get_dispatch_info();
+ let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
+ Ok(
+ <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+ &id,
+ &(sponsor.into()),
+ weight,
+ ),
+ )
+ }
+
+ fn cancel_reserve(
+ id: [u8; 16],
+ sponsor: <T as frame_system::Config>::AccountId,
+ ) -> Result<u128, DispatchError> {
+ Ok(
+ <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
+ &id,
+ &(sponsor.into()),
+ u128::MAX,
+ ),
+ )
+ }
+}
runtime/common/sponsoring.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+
+// TODO: permission check?
+pub fn withdraw_set_token_property<T: Config>(
+ collection: &CollectionHandle<T>,
+ who: &T::CrossAccountId,
+ item_id: &TokenId,
+ data_size: usize,
+) -> Option<()> {
+ // preliminary sponsoring correctness check
+ match collection.mode {
+ CollectionMode::NFT => {
+ let owner = pallet_nonfungible::TokenData::<T>::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 !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
+ return None;
+ }
+ }
+ }
+
+ if data_size > collection.limits.sponsored_data_size() as usize {
+ return None;
+ }
+
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection.limits.sponsored_data_rate_limit()?;
+
+ if let Some(last_tx_block) = TokenPropertyBasket::<T>::get(collection.id, item_id) {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
+ }
+ }
+
+ <TokenPropertyBasket<T>>::insert(collection.id, item_id, block_number);
+
+ Some(())
+}
+
+pub fn withdraw_transfer<T: Config>(
+ collection: &CollectionHandle<T>,
+ who: &T::CrossAccountId,
+ item_id: &TokenId,
+) -> Option<()> {
+ // preliminary sponsoring correctness check
+ match collection.mode {
+ CollectionMode::NFT => {
+ let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
+ if !owner.conv_eq(who) {
+ return None;
+ }
+ }
+ CollectionMode::Fungible(_) => {
+ if item_id != &TokenId::default() {
+ return None;
+ }
+ if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
+ return None;
+ }
+ }
+ CollectionMode::ReFungible => {
+ if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
+ return None;
+ }
+ }
+ }
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, item_id),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+ }
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::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 => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)
+ }
+ CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(
+ (collection.id, item_id, who.as_sub()),
+ block_number,
+ ),
+ };
+
+ Some(())
+}
+
+pub fn withdraw_create_item<T: Config>(
+ collection: &CollectionHandle<T>,
+ who: &T::CrossAccountId,
+ properties: &CreateItemData,
+) -> Option<()> {
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::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) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
+ }
+ }
+
+ CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);
+
+ Some(())
+}
+
+pub fn withdraw_approve<T: Config>(
+ collection: &CollectionHandle<T>,
+ who: &T::AccountId,
+ item_id: &TokenId,
+) -> Option<()> {
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection.limits.sponsor_approve_timeout();
+
+ let last_tx_block = match collection.mode {
+ CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
+ CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
+ CollectionMode::ReFungible => {
+ <RefungibleApproveBasket<T>>::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 => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
+ CollectionMode::Fungible(_) => {
+ <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
+ }
+ CollectionMode::ReFungible => {
+ <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
+ }
+ };
+
+ Some(())
+}
+
+fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
+ let collection = CollectionHandle::new(id)?;
+ let sponsor = collection.sponsorship.sponsor().cloned()?;
+ Some((sponsor, collection))
+}
+
+pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);
+impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>
+where
+ T: Config,
+ C: IsSubType<UniqueCall<T>>,
+{
+ fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
+ match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {
+ UniqueCall::set_token_properties {
+ collection_id,
+ token_id,
+ properties,
+ ..
+ } => {
+ let (sponsor, collection) = load::<T>(*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::<T>(
+ &collection,
+ &T::CrossAccountId::from_sub(who.clone()),
+ data,
+ )
+ .map(|()| sponsor)
+ }
+ UniqueCall::transfer {
+ collection_id,
+ item_id,
+ ..
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_transfer::<T>(
+ &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::<T>(&collection, from, item_id).map(|()| sponsor)
+ }
+ UniqueCall::approve {
+ collection_id,
+ item_id,
+ ..
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)
+ }
+ _ => None,
+ }
+ }
+}
+
+pub trait SponsorshipPredict<T: Config> {
+ fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>
+ where
+ u64: From<<T as frame_system::Config>::BlockNumber>;
+}
+
+pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
+
+impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {
+ fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
+ where
+ u64: From<<T as frame_system::Config>::BlockNumber>,
+ {
+ let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
+ let _ = collection.sponsorship.sponsor()?;
+
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, token),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
+ }
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::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 => {
+ <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
+ }
+ CollectionMode::Fungible(_) => token == TokenId::default(),
+ CollectionMode::ReFungible => {
+ <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
+ }
+ };
+
+ if token_exists {
+ Some(0)
+ } else {
+ None
+ }
+ }
+}
runtime/common/src/constants.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-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;
-}
runtime/common/src/dispatch.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-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<T>
-where
- T: pallet_fungible::Config + pallet_nonfungible::Config + pallet_refungible::Config,
-{
- Fungible(FungibleHandle<T>),
- Nonfungible(NonfungibleHandle<T>),
- Refungible(RefungibleHandle<T>),
-}
-impl<T> CollectionDispatch<T> for CollectionDispatchT<T>
-where
- T: pallet_common::Config
- + pallet_unique::Config
- + pallet_fungible::Config
- + pallet_nonfungible::Config
- + pallet_refungible::Config,
-{
- fn create(
- sender: T::CrossAccountId,
- data: CreateCollectionData<T::AccountId>,
- ) -> Result<CollectionId, DispatchError> {
- let id = match data.mode {
- CollectionMode::NFT => <PalletNonfungible<T>>::init_collection(sender, data, false)?,
- CollectionMode::Fungible(decimal_points) => {
- // check params
- ensure!(
- decimal_points <= MAX_DECIMAL_POINTS,
- pallet_unique::Error::<T>::CollectionDecimalPointLimitExceeded
- );
- <PalletFungible<T>>::init_collection(sender, data)?
- }
- CollectionMode::ReFungible => <PalletRefungible<T>>::init_collection(sender, data)?,
- };
- Ok(id)
- }
-
- fn destroy(sender: T::CrossAccountId, collection: CollectionHandle<T>) -> 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<T>) -> 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<T> {
- 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<T> {
- match self {
- Self::Fungible(h) => h,
- Self::Nonfungible(h) => h,
- Self::Refungible(h) => h,
- }
- }
-}
-
-impl<T> pallet_evm::OnMethodCall<T> for CollectionDispatchT<T>
-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(<CollectionById<T>>::contains_key)
- .unwrap_or(false)
- }
- fn get_code(target: &H160) -> Option<Vec<u8>> {
- if let Some(collection_id) = map_eth_to_id(target) {
- let collection = <CollectionById<T>>::get(collection_id)?;
- Some(
- match collection.mode {
- CollectionMode::NFT => <NonfungibleHandle<T>>::CODE,
- CollectionMode::Fungible(_) => <FungibleHandle<T>>::CODE,
- CollectionMode::ReFungible => <RefungibleHandle<T>>::CODE,
- }
- .to_owned(),
- )
- } else if let Some((collection_id, _token_id)) =
- <T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(target)
- {
- let collection = <CollectionById<T>>::get(collection_id)?;
- if collection.mode != CollectionMode::ReFungible {
- return None;
- }
- // TODO: check token existence
- Some(<RefungibleTokenHandle<T>>::CODE.to_owned())
- } else {
- None
- }
- }
- fn call(handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
- if let Some(collection_id) = map_eth_to_id(&handle.code_address()) {
- let collection =
- <CollectionHandle<T>>::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)) =
- <T as pallet_common::Config>::EvmTokenAddressMapping::address_to_token(
- &handle.code_address(),
- ) {
- let collection =
- <CollectionHandle<T>>::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
- }
- }
-}
runtime/common/src/eth_sponsoring.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-//! 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<T: UniqueConfig>(PhantomData<*const T>);
-impl<T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig>
- SponsorshipHandler<T::CrossAccountId, (H160, Vec<u8>)> for UniqueEthSponsorshipHandler<T>
-{
- fn get_sponsor(who: &T::CrossAccountId, call: &(H160, Vec<u8>)) -> Option<T::CrossAccountId> {
- let collection_id = map_eth_to_id(&call.0)?;
- let collection = <CollectionHandle<T>>::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 = <UniqueNFTCall<T>>::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::<T>(
- &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::<T>(&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::<T>(
- &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::<T>(&collection, &from, &token_id).map(|()| sponsor)
- }
- UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
- let token_id: TokenId = token_id.try_into().ok()?;
- withdraw_approve::<T>(&collection, who.as_sub(), &token_id)
- .map(|()| sponsor)
- }
- _ => None,
- }
- }
- CollectionMode::Fungible(_) => {
- let call = <UniqueFungibleCall<T>>::parse(method_id, &mut reader).ok()??;
- #[allow(clippy::single_match)]
- match call {
- UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
- withdraw_transfer::<T>(&collection, who, &TokenId::default())
- .map(|()| sponsor)
- }
- UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => {
- let from = T::CrossAccountId::from_eth(from);
- withdraw_transfer::<T>(&collection, &from, &TokenId::default())
- .map(|()| sponsor)
- }
- UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
- withdraw_approve::<T>(&collection, who.as_sub(), &TokenId::default())
- .map(|()| sponsor)
- }
- _ => None,
- }
- }
- _ => None,
- }?))
- }
-}
runtime/common/src/lib.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-#![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;
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-#[macro_export]
-macro_rules! impl_common_runtime_apis {
- (
- $(
- #![custom_apis]
-
- $($custom_apis:tt)+
- )?
- ) => {
- impl_runtime_apis! {
- $($($custom_apis)+)?
-
- impl up_rpc::UniqueApi<Block, CrossAccountId, AccountId> for Runtime {
- fn account_tokens(collection: CollectionId, account: CrossAccountId) -> Result<Vec<TokenId>, DispatchError> {
- dispatch_unique_runtime!(collection.account_tokens(account))
- }
- fn collection_tokens(collection: CollectionId) -> Result<Vec<TokenId>, DispatchError> {
- dispatch_unique_runtime!(collection.collection_tokens())
- }
- fn token_exists(collection: CollectionId, token: TokenId) -> Result<bool, DispatchError> {
- dispatch_unique_runtime!(collection.token_exists(token))
- }
-
- fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
- dispatch_unique_runtime!(collection.token_owner(token))
- }
-
- fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError> {
- dispatch_unique_runtime!(collection.token_owners(token))
- }
-
- fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
- let budget = up_data_structs::budget::Value::new(10);
-
- Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
- }
- fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
- Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
- }
- fn collection_properties(
- collection: CollectionId,
- keys: Option<Vec<Vec<u8>>>
- ) -> Result<Vec<Property>, 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<Vec<Vec<u8>>>
- ) -> Result<Vec<Property>, 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<Vec<Vec<u8>>>
- ) -> Result<Vec<PropertyKeyPermission>, 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<Vec<Vec<u8>>>
- ) -> Result<TokenData<CrossAccountId>, 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<u32, DispatchError> {
- dispatch_unique_runtime!(collection.total_supply())
- }
- fn account_balance(collection: CollectionId, account: CrossAccountId) -> Result<u32, DispatchError> {
- dispatch_unique_runtime!(collection.account_balance(account))
- }
- fn balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<u128, DispatchError> {
- dispatch_unique_runtime!(collection.balance(account, token))
- }
- fn allowance(
- collection: CollectionId,
- sender: CrossAccountId,
- spender: CrossAccountId,
- token: TokenId,
- ) -> Result<u128, DispatchError> {
- dispatch_unique_runtime!(collection.allowance(sender, spender, token))
- }
-
- fn adminlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
- Ok(<pallet_common::Pallet<Runtime>>::adminlist(collection))
- }
- fn allowlist(collection: CollectionId) -> Result<Vec<CrossAccountId>, DispatchError> {
- Ok(<pallet_common::Pallet<Runtime>>::allowlist(collection))
- }
- fn allowed(collection: CollectionId, user: CrossAccountId) -> Result<bool, DispatchError> {
- Ok(<pallet_common::Pallet<Runtime>>::allowed(collection, user))
- }
- fn last_token_id(collection: CollectionId) -> Result<TokenId, DispatchError> {
- dispatch_unique_runtime!(collection.last_token_id())
- }
- fn collection_by_id(collection: CollectionId) -> Result<Option<RpcCollection<AccountId>>, DispatchError> {
- Ok(<pallet_common::Pallet<Runtime>>::rpc_collection(collection))
- }
- fn collection_stats() -> Result<CollectionStats, DispatchError> {
- Ok(<pallet_common::Pallet<Runtime>>::collection_stats())
- }
- fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>, DispatchError> {
- Ok(<$crate::sponsoring::UniqueSponsorshipPredict<Runtime> as
- $crate::sponsoring::SponsorshipPredict<Runtime>>::predict(
- collection,
- account,
- token))
- }
-
- fn effective_collection_limits(collection: CollectionId) -> Result<Option<CollectionLimits>, DispatchError> {
- Ok(<pallet_common::Pallet<Runtime>>::effective_collection_limits(collection))
- }
-
- fn total_pieces(collection: CollectionId, token_id: TokenId) -> Result<Option<u128>, DispatchError> {
- dispatch_unique_runtime!(collection.total_pieces(token_id))
- }
- }
-
- impl sp_api::Core<Block> for Runtime {
- fn version() -> RuntimeVersion {
- VERSION
- }
-
- fn execute_block(block: Block) {
- Executive::execute_block(block)
- }
-
- fn initialize_block(header: &<Block as BlockT>::Header) {
- Executive::initialize_block(header)
- }
- }
-
- impl sp_api::Metadata<Block> for Runtime {
- fn metadata() -> OpaqueMetadata {
- OpaqueMetadata::new(Runtime::metadata().into())
- }
- }
-
- impl sp_block_builder::BlockBuilder<Block> for Runtime {
- fn apply_extrinsic(extrinsic: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
- Executive::apply_extrinsic(extrinsic)
- }
-
- fn finalize_block() -> <Block as BlockT>::Header {
- Executive::finalize_block()
- }
-
- fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
- data.create_extrinsics()
- }
-
- fn check_inherents(
- block: Block,
- data: sp_inherents::InherentData,
- ) -> sp_inherents::CheckInherentsResult {
- data.check_extrinsics(&block)
- }
-
- // fn random_seed() -> <Block as BlockT>::Hash {
- // RandomnessCollectiveFlip::random_seed().0
- // }
- }
-
- impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
- fn validate_transaction(
- source: TransactionSource,
- tx: <Block as BlockT>::Extrinsic,
- hash: <Block as BlockT>::Hash,
- ) -> TransactionValidity {
- Executive::validate_transaction(source, tx, hash)
- }
- }
-
- impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
- fn offchain_worker(header: &<Block as BlockT>::Header) {
- Executive::offchain_worker(header)
- }
- }
-
- impl fp_rpc::EthereumRuntimeRPCApi<Block> for Runtime {
- fn chain_id() -> u64 {
- <Runtime as pallet_evm::Config>::ChainId::get()
- }
-
- fn account_basic(address: H160) -> EVMAccount {
- let (account, _) = EVM::account_basic(&address);
- account
- }
-
- fn gas_price() -> U256 {
- let (price, _) = <Runtime as pallet_evm::Config>::FeeCalculator::min_gas_price();
- price
- }
-
- fn account_code_at(address: H160) -> Vec<u8> {
- EVM::account_codes(address)
- }
-
- fn author() -> H160 {
- <pallet_evm::Pallet<Runtime>>::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<u8>,
- value: U256,
- gas_limit: U256,
- max_fee_per_gas: Option<U256>,
- max_priority_fee_per_gas: Option<U256>,
- nonce: Option<U256>,
- estimate: bool,
- access_list: Option<Vec<(H160, Vec<H256>)>>,
- ) -> Result<pallet_evm::CallInfo, sp_runtime::DispatchError> {
- let config = if estimate {
- let mut config = <Runtime as pallet_evm::Config>::config().clone();
- config.estimate = true;
- Some(config)
- } else {
- None
- };
-
- let is_transactional = false;
- <Runtime as pallet_evm::Config>::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(|| <Runtime as pallet_evm::Config>::config()),
- ).map_err(|err| err.error.into())
- }
-
- #[allow(clippy::redundant_closure)]
- fn create(
- from: H160,
- data: Vec<u8>,
- value: U256,
- gas_limit: U256,
- max_fee_per_gas: Option<U256>,
- max_priority_fee_per_gas: Option<U256>,
- nonce: Option<U256>,
- estimate: bool,
- access_list: Option<Vec<(H160, Vec<H256>)>>,
- ) -> Result<pallet_evm::CreateInfo, sp_runtime::DispatchError> {
- let config = if estimate {
- let mut config = <Runtime as pallet_evm::Config>::config().clone();
- config.estimate = true;
- Some(config)
- } else {
- None
- };
-
- let is_transactional = false;
- <Runtime as pallet_evm::Config>::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(|| <Runtime as pallet_evm::Config>::config()),
- ).map_err(|err| err.error.into())
- }
-
- fn current_transaction_statuses() -> Option<Vec<TransactionStatus>> {
- Ethereum::current_transaction_statuses()
- }
-
- fn current_block() -> Option<pallet_ethereum::Block> {
- Ethereum::current_block()
- }
-
- fn current_receipts() -> Option<Vec<pallet_ethereum::Receipt>> {
- Ethereum::current_receipts()
- }
-
- fn current_all() -> (
- Option<pallet_ethereum::Block>,
- Option<Vec<pallet_ethereum::Receipt>>,
- Option<Vec<TransactionStatus>>
- ) {
- (
- Ethereum::current_block(),
- Ethereum::current_receipts(),
- Ethereum::current_transaction_statuses()
- )
- }
-
- fn extrinsic_filter(xts: Vec<<Block as sp_api::BlockT>::Extrinsic>) -> Vec<pallet_ethereum::Transaction> {
- xts.into_iter().filter_map(|xt| match xt.0.function {
- Call::Ethereum(pallet_ethereum::Call::transact { transaction }) => Some(transaction),
- _ => None
- }).collect()
- }
-
- fn elasticity() -> Option<Permill> {
- None
- }
- }
-
- impl fp_rpc::ConvertTransactionRuntimeApi<Block> for Runtime {
- fn convert_transaction(transaction: pallet_ethereum::Transaction) -> <Block as BlockT>::Extrinsic {
- UncheckedExtrinsic::new_unsigned(
- pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
- )
- }
- }
-
- impl sp_session::SessionKeys<Block> for Runtime {
- fn decode_session_keys(
- encoded: Vec<u8>,
- ) -> Option<Vec<(Vec<u8>, KeyTypeId)>> {
- SessionKeys::decode_into_raw_public_keys(&encoded)
- }
-
- fn generate_session_keys(seed: Option<Vec<u8>>) -> Vec<u8> {
- SessionKeys::generate(seed)
- }
- }
-
- impl sp_consensus_aura::AuraApi<Block, AuraId> for Runtime {
- fn slot_duration() -> sp_consensus_aura::SlotDuration {
- sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration())
- }
-
- fn authorities() -> Vec<AuraId> {
- Aura::authorities().to_vec()
- }
- }
-
- impl cumulus_primitives_core::CollectCollationInfo<Block> for Runtime {
- fn collect_collation_info(header: &<Block as BlockT>::Header) -> cumulus_primitives_core::CollationInfo {
- ParachainSystem::collect_collation_info(header)
- }
- }
-
- impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {
- fn account_nonce(account: AccountId) -> Index {
- System::account_nonce(account)
- }
- }
-
- impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> for Runtime {
- fn query_info(uxt: <Block as BlockT>::Extrinsic, len: u32) -> RuntimeDispatchInfo<Balance> {
- TransactionPayment::query_info(uxt, len)
- }
- fn query_fee_details(uxt: <Block as BlockT>::Extrinsic, len: u32) -> FeeDetails<Balance> {
- TransactionPayment::query_fee_details(uxt, len)
- }
- }
-
- /*
- impl pallet_contracts_rpc_runtime_api::ContractsApi<Block, AccountId, Balance, BlockNumber, Hash>
- for Runtime
- {
- fn call(
- origin: AccountId,
- dest: AccountId,
- value: Balance,
- gas_limit: u64,
- input_data: Vec<u8>,
- ) -> 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<Hash>,
- data: Vec<u8>,
- salt: Vec<u8>,
- ) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, BlockNumber>
- {
- 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<BlockNumber> {
- Contracts::rent_projection(address)
- }
- }
- */
-
- #[cfg(feature = "runtime-benchmarks")]
- impl frame_benchmarking::Benchmark<Block> for Runtime {
- fn benchmark_metadata(extra: bool) -> (
- Vec<frame_benchmarking::BenchmarkList>,
- Vec<frame_support::traits::StorageInfo>,
- ) {
- use frame_benchmarking::{list_benchmark, Benchmarking, BenchmarkList};
- use frame_support::traits::StorageInfoTrait;
-
- let mut list = Vec::<BenchmarkList>::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<Vec<frame_benchmarking::BenchmarkBatch>, sp_runtime::RuntimeString> {
- use frame_benchmarking::{Benchmarking, BenchmarkBatch, add_benchmark, TrackedStorageKey};
-
- let allowlist: Vec<TrackedStorageKey> = 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::<BenchmarkBatch>::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<Block> 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)
- }
- }
- }
- }
-}
runtime/common/src/sponsoring.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-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<T> Config for T where T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig {}
-
-// TODO: permission check?
-pub fn withdraw_set_token_property<T: Config>(
- collection: &CollectionHandle<T>,
- who: &T::CrossAccountId,
- item_id: &TokenId,
- data_size: usize,
-) -> Option<()> {
- // preliminary sponsoring correctness check
- match collection.mode {
- CollectionMode::NFT => {
- let owner = pallet_nonfungible::TokenData::<T>::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 !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
- return None;
- }
- }
- }
-
- if data_size > collection.limits.sponsored_data_size() as usize {
- return None;
- }
-
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let limit = collection.limits.sponsored_data_rate_limit()?;
-
- if let Some(last_tx_block) = TokenPropertyBasket::<T>::get(collection.id, item_id) {
- let timeout = last_tx_block + limit.into();
- if block_number < timeout {
- return None;
- }
- }
-
- <TokenPropertyBasket<T>>::insert(collection.id, item_id, block_number);
-
- Some(())
-}
-
-pub fn withdraw_transfer<T: Config>(
- collection: &CollectionHandle<T>,
- who: &T::CrossAccountId,
- item_id: &TokenId,
-) -> Option<()> {
- // preliminary sponsoring correctness check
- match collection.mode {
- CollectionMode::NFT => {
- let owner = pallet_nonfungible::TokenData::<T>::get((collection.id, item_id))?.owner;
- if !owner.conv_eq(who) {
- return None;
- }
- }
- CollectionMode::Fungible(_) => {
- if item_id != &TokenId::default() {
- return None;
- }
- if <pallet_fungible::Balance<T>>::get((collection.id, who)) == 0 {
- return None;
- }
- }
- CollectionMode::ReFungible => {
- if !<pallet_refungible::Owned<T>>::get((collection.id, who, item_id)) {
- return None;
- }
- }
- }
-
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, item_id),
- CollectionMode::Fungible(_) => {
- <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
- }
- CollectionMode::ReFungible => {
- <ReFungibleTransferBasket<T>>::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 => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
- CollectionMode::Fungible(_) => {
- <FungibleTransferBasket<T>>::insert(collection.id, who.as_sub(), block_number)
- }
- CollectionMode::ReFungible => <ReFungibleTransferBasket<T>>::insert(
- (collection.id, item_id, who.as_sub()),
- block_number,
- ),
- };
-
- Some(())
-}
-
-pub fn withdraw_create_item<T: Config>(
- collection: &CollectionHandle<T>,
- who: &T::CrossAccountId,
- properties: &CreateItemData,
-) -> Option<()> {
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::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) = <CreateItemBasket<T>>::get((collection.id, who.as_sub())) {
- let timeout = last_tx_block + limit.into();
- if block_number < timeout {
- return None;
- }
- }
-
- CreateItemBasket::<T>::insert((collection.id, who.as_sub()), block_number);
-
- Some(())
-}
-
-pub fn withdraw_approve<T: Config>(
- collection: &CollectionHandle<T>,
- who: &T::AccountId,
- item_id: &TokenId,
-) -> Option<()> {
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let limit = collection.limits.sponsor_approve_timeout();
-
- let last_tx_block = match collection.mode {
- CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
- CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
- CollectionMode::ReFungible => {
- <RefungibleApproveBasket<T>>::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 => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
- CollectionMode::Fungible(_) => {
- <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
- }
- CollectionMode::ReFungible => {
- <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
- }
- };
-
- Some(())
-}
-
-fn load<T: UniqueConfig>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
- let collection = CollectionHandle::new(id)?;
- let sponsor = collection.sponsorship.sponsor().cloned()?;
- Some((sponsor, collection))
-}
-
-pub struct UniqueSponsorshipHandler<T>(PhantomData<T>);
-impl<T, C> SponsorshipHandler<T::AccountId, C> for UniqueSponsorshipHandler<T>
-where
- T: Config,
- C: IsSubType<UniqueCall<T>>,
-{
- fn get_sponsor(who: &T::AccountId, call: &C) -> Option<T::AccountId> {
- match IsSubType::<UniqueCall<T>>::is_sub_type(call)? {
- UniqueCall::set_token_properties {
- collection_id,
- token_id,
- properties,
- ..
- } => {
- let (sponsor, collection) = load::<T>(*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::<T>(
- &collection,
- &T::CrossAccountId::from_sub(who.clone()),
- data,
- )
- .map(|()| sponsor)
- }
- UniqueCall::transfer {
- collection_id,
- item_id,
- ..
- } => {
- let (sponsor, collection) = load(*collection_id)?;
- withdraw_transfer::<T>(
- &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::<T>(&collection, from, item_id).map(|()| sponsor)
- }
- UniqueCall::approve {
- collection_id,
- item_id,
- ..
- } => {
- let (sponsor, collection) = load(*collection_id)?;
- withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)
- }
- _ => None,
- }
- }
-}
-
-pub trait SponsorshipPredict<T: Config> {
- fn predict(collection: CollectionId, account: T::CrossAccountId, token: TokenId) -> Option<u64>
- where
- u64: From<<T as frame_system::Config>::BlockNumber>;
-}
-
-pub struct UniqueSponsorshipPredict<T>(PhantomData<T>);
-
-impl<T: Config> SponsorshipPredict<T> for UniqueSponsorshipPredict<T> {
- fn predict(collection_id: CollectionId, who: T::CrossAccountId, token: TokenId) -> Option<u64>
- where
- u64: From<<T as frame_system::Config>::BlockNumber>,
- {
- let collection = <CollectionHandle<T>>::try_get(collection_id).ok()?;
- let _ = collection.sponsorship.sponsor()?;
-
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::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 => <NftTransferBasket<T>>::get(collection.id, token),
- CollectionMode::Fungible(_) => {
- <FungibleTransferBasket<T>>::get(collection.id, who.as_sub())
- }
- CollectionMode::ReFungible => {
- <ReFungibleTransferBasket<T>>::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 => {
- <pallet_nonfungible::TokenData<T>>::contains_key((collection.id, token))
- }
- CollectionMode::Fungible(_) => token == TokenId::default(),
- CollectionMode::ReFungible => {
- <pallet_refungible::TotalSupply<T>>::contains_key((collection.id, token))
- }
- };
-
- if token_exists {
- Some(0)
- } else {
- None
- }
- }
-}
runtime/common/src/types.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-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<BlockNumber, BlakeTwo256>;
-
-/// Opaque block type.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-
-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 = <<Signature as Verify>::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<sp_runtime::AccountId32>
- + Send
- + Sync
- + 'static;
-
- type TransactionConverter: fp_rpc::ConvertTransaction<UncheckedExtrinsic>
- + Send
- + Sync
- + 'static;
-
- fn get_transaction_converter() -> Self::TransactionConverter;
-}
runtime/common/src/weights.rsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-
-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)* )) => {
- <FungibleWeights<T>>::$method($($args)*)
- .max(<NonfungibleWeights<T>>::$method($($args)*))
- .max(<RefungibleWeights<T>>::$method($($args)*))
- };
-}
-
-pub struct CommonWeights<T>(PhantomData<T>)
-where
- T: FungibleConfig + NonfungibleConfig + RefungibleConfig;
-impl<T> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T>
-where
- T: FungibleConfig + NonfungibleConfig + RefungibleConfig,
-{
- fn create_item() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(create_item())
- }
-
- fn create_multiple_items(data: &[CreateItemData]) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(create_multiple_items(data))
- }
-
- fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(create_multiple_items_ex(data))
- }
-
- fn burn_item() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(burn_item())
- }
-
- fn set_collection_properties(amount: u32) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(set_collection_properties(amount))
- }
-
- fn delete_collection_properties(amount: u32) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(delete_collection_properties(amount))
- }
-
- fn set_token_properties(amount: u32) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(set_token_properties(amount))
- }
-
- fn delete_token_properties(amount: u32) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(delete_token_properties(amount))
- }
-
- fn set_token_property_permissions(amount: u32) -> Weight {
- dispatch_weight::<T>() + max_weight_of!(set_token_property_permissions(amount))
- }
-
- fn transfer() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(transfer())
- }
-
- fn approve() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(approve())
- }
-
- fn transfer_from() -> Weight {
- dispatch_weight::<T>() + max_weight_of!(transfer_from())
- }
-
- fn burn_from() -> Weight {
- dispatch_weight::<T>() + 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<T> RefungibleExtensionsWeightInfo for CommonWeights<T>
-where
- T: FungibleConfig + NonfungibleConfig + RefungibleConfig,
-{
- fn repartition() -> Weight {
- dispatch_weight::<T>() + <<T as RefungibleConfig>::WeightInfo>::repartition_item()
- }
-}
runtime/common/weights.rsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+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 = <FungibleWeights<T>>::$method($($args)*)
+ .max(<NonfungibleWeights<T>>::$method($($args)*));
+
+ #[cfg(feature = "refungible")]
+ let max_weight = max_weight.max(<RefungibleWeights<T>>::$method($($args)*));
+
+ max_weight
+ }};
+}
+
+#[cfg(not(feature = "refungible"))]
+pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig {}
+
+#[cfg(not(feature = "refungible"))]
+impl<T: FungibleConfig + NonfungibleConfig> CommonWeightConfigs for T {}
+
+#[cfg(feature = "refungible")]
+pub trait CommonWeightConfigs: FungibleConfig + NonfungibleConfig + RefungibleConfig {}
+
+#[cfg(feature = "refungible")]
+impl<T: FungibleConfig + NonfungibleConfig + RefungibleConfig> CommonWeightConfigs for T {}
+
+pub struct CommonWeights<T>(PhantomData<T>);
+
+impl<T> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T>
+where
+ T: CommonWeightConfigs,
+{
+ fn create_item() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(create_item())
+ }
+
+ fn create_multiple_items(data: &[CreateItemData]) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(create_multiple_items(data))
+ }
+
+ fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(create_multiple_items_ex(data))
+ }
+
+ fn burn_item() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(burn_item())
+ }
+
+ fn set_collection_properties(amount: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(set_collection_properties(amount))
+ }
+
+ fn delete_collection_properties(amount: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(delete_collection_properties(amount))
+ }
+
+ fn set_token_properties(amount: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(set_token_properties(amount))
+ }
+
+ fn delete_token_properties(amount: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(delete_token_properties(amount))
+ }
+
+ fn set_token_property_permissions(amount: u32) -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(set_token_property_permissions(amount))
+ }
+
+ fn transfer() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(transfer())
+ }
+
+ fn approve() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(approve())
+ }
+
+ fn transfer_from() -> Weight {
+ dispatch_weight::<T>() + max_weight_of!(transfer_from())
+ }
+
+ fn burn_from() -> Weight {
+ dispatch_weight::<T>() + 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<T> RefungibleExtensionsWeightInfo for CommonWeights<T>
+where
+ T: FungibleConfig + NonfungibleConfig + RefungibleConfig,
+{
+ fn repartition() -> Weight {
+ dispatch_weight::<T>() + <<T as RefungibleConfig>::WeightInfo>::repartition_item()
+ }
+}
+
+#[cfg(not(feature = "refungible"))]
+impl<T> RefungibleExtensionsWeightInfo for CommonWeights<T>
+where
+ T: FungibleConfig + NonfungibleConfig,
+{
+ fn repartition() -> Weight {
+ dispatch_weight::<T>()
+ }
+}
runtime/opal/Cargo.tomldiffbeforeafterboth--- 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
runtime/opal/src/lib.rsdiffbeforeafterboth--- 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<Runtime>;
-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<XAccountId, XBalance> {
- /// 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 = <Balances as Currency<AccountId>>::NegativeImbalance;
-
-pub struct DealWithFees;
-impl OnUnbalanced<NegativeImbalance> for DealWithFees {
- fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
- 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 / <Runtime as frame_system::Config>::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<Self>;
- type EvmAddressMapping = pallet_evm::HashedAddressMapping<Self::Hashing>;
- 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<Self>;
- type CallOrigin = EnsureAddressTruncated<Self>;
- type WithdrawOrigin = EnsureAddressTruncated<Self>;
- type AddressMapping = HashedAddressMapping<Self::Hashing>;
- type PrecompilesType = ();
- type PrecompilesValue = ();
- type Currency = Balances;
- type Event = Event;
- type OnMethodCall = (
- pallet_evm_migration::OnMethodCall<Self>,
- pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
- CollectionDispatchT<Self>,
- pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
- );
- type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
- type ChainId = ChainId;
- type Runner = pallet_evm::runner::stack::Runner<Self>;
- type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
- type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
- type FindAuthor = EthereumFindAuthor<Aura>;
-}
-
-impl pallet_evm_migration::Config for Runtime {
- type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
-}
-
-pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
-impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
- fn find_author<'a, I>(digests: I) -> Option<H160>
- where
- I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
- {
- 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<Self>;
-}
-
-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<Balance>;
- /// 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<BlockNumber, BlakeTwo256>;
- /// 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<AccountId, ()>;
- /// 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<Self>;
- /// 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<Self>;
- /// 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<Self>;
-}
-
-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::<pallet_contracts::Pallet<Runtime>> 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() / (
- <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
- <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
- )) / 5) as u32;
- pub Schedule: pallet_contracts::Schedule<Runtime> = 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<Self>;
- type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
- type ChainExtension = NFTExtension;
- type DeletionQueueDepth = DeletionQueueDepth;
- type DeletionWeightLimit = DeletionWeightLimit;
- type Schedule = Schedule;
- type CallStack = [pallet_contracts::Frame<Self>; 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<T>(sp_std::marker::PhantomData<T>);
-
-impl<T> WeightToFeePolynomial for LinearFee<T>
-where
- T: BaseArithmetic + From<u32> + Copy + Unsigned,
-{
- type Balance = T;
-
- fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
- 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<Balances, DealWithFees>;
- type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
- type OperationalFeeMultiplier = OperationalFeeMultiplier;
- type WeightToFee = LinearFee<Balance>;
- 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<AccountId>;
- type RejectOrigin = EnsureRoot<AccountId>;
- 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<Self>;
- type MaxApprovals = MaxApprovals;
-}
-
-impl pallet_sudo::Config for Runtime {
- type Event = Event;
- type Call = Call;
-}
-
-pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
- for RelayChainBlockNumberProvider<T>
-{
- type BlockNumber = BlockNumber;
-
- fn current_block_number() -> Self::BlockNumber {
- cumulus_pallet_parachain_system::Pallet::<T>::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<Runtime>;
- type MinVestedTransfer = MinVestedTransfer;
- type VestedTransferOrigin = EnsureSigned<AccountId>;
- type WeightInfo = ();
- type MaxVestingSchedules = MaxVestingSchedules;
- type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-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<Self>;
- type OnSystemEvent = ();
- // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
- // MaxDownwardMessageWeight,
- // XcmExecutor<XcmConfig>,
- // 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<AccountId>,
- // Sibling parachain origins convert to AccountId via the `ParaId::into`.
- SiblingParachainConvertsVia<Sibling, AccountId>,
- // Straight up local `AccountId32` origins just alias directly to `AccountId`.
- AccountId32Aliases<RelayNetwork, AccountId>,
-);
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
- fn matches_fungible(a: &MultiAsset) -> Option<B> {
- match (&a.id, &a.fun) {
- (Concrete(_), XcmFungible(ref amount)) => CheckedConversion::checked_from(*amount),
- _ => None,
- }
- }
-}
-
-/// Means for transacting assets on this chain.
-pub type LocalAssetTransactor = CurrencyAdapter<
- // Use this currency:
- Balances,
- // Use this currency when it is a fungible asset matching the given location or name:
- 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<LocationToAccountId, Origin>,
- // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
- // recognised.
- RelayChainAsNative<RelayOrigin, Origin>,
- // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
- // recognised.
- SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
- // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
- // transaction from the Root origin.
- ParentAsSuperuser<Origin>,
- // Native signed account converter; this just converts an `AccountId32` origin into a normal
- // `Origin::Signed` origin of the same 32-byte value.
- SignedAccountId32AsNative<RelayNetwork, Origin>,
- // Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
- XcmPassthrough<Origin>,
-);
-
-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> = {
- MultiLocation { parents: 1, interior: Here } |
- MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
- };
-}
-
-pub type Barrier = (
- TakeWeightCredit,
- AllowTopLevelPaidExecutionFrom<Everything>,
- // ^^^ Parent & its unit plurality gets free execution
-);
-
-pub struct UsingOnlySelfCurrencyComponents<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
- Weight,
- Currency::Balance,
- PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
-impl<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > WeightTrader
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn new() -> Self {
- Self(0, Zero::zero(), PhantomData)
- }
-
- fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
- let amount = WeightToFee::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<MultiAsset> {
- 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<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > Drop
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn drop(&mut self) {
- OnUnbalanced::on_unbalanced(Currency::issue(self.1));
- }
-}
-
-pub struct XcmConfig;
-impl Config for XcmConfig {
- type Call = Call;
- 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<Ancestry>;
- type Barrier = Barrier;
- type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
- type Trader =
- UsingOnlySelfCurrencyComponents<LinearFee<Balance>, 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<Origin, AccountId, RelayNetwork>,);
-
-/// 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<ParachainSystem, ()>,
- // ..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<Origin, LocalOriginToLocation>;
- type XcmRouter = XcmRouter;
- type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
- type XcmExecuteFilter = Everything;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type XcmTeleportFilter = Everything;
- type XcmReserveTransferFilter = Everything;
- type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
- type LocationInverter = LocationInverter<Ancestry>;
- 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<XcmConfig>;
-}
-
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
- type WeightInfo = ();
- type Event = Event;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type ChannelInfo = ParachainSystem;
- type VersionWrapper = ();
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
- type ControllerOrigin = EnsureRoot<AccountId>;
- type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
- type Event = Event;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
-
-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<Self>;
- type Event = Event;
- type Currency = Balances;
- type CollectionCreationPrice = CollectionCreationPrice;
- type TreasuryAccountId = TreasuryAccountId;
- type CollectionDispatch = CollectionDispatchT<Self>;
-
- type EvmTokenAddressMapping = EvmTokenAddressMapping;
- type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
- type ContractAddress = EvmCollectionHelpersAddress;
-}
-
-impl pallet_structure::Config for Runtime {
- type Event = Event;
- type Call = Call;
- type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_fungible::Config for Runtime {
- type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_refungible::Config for Runtime {
- type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_nonfungible::Config for Runtime {
- type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_proxy_rmrk_core::Config for Runtime {
- type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
- type Event = Event;
-}
-
-impl pallet_proxy_rmrk_equip::Config for Runtime {
- type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight<Self>;
- type Event = Event;
-}
-
-impl pallet_unique::Config for Runtime {
- type Event = Event;
- type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
- type CommonWeightInfo = CommonWeights<Self>;
- type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
-}
-
-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<Runtime>;
-}
-
-parameter_types! {
- pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
- RuntimeBlockWeights::get().max_block;
- pub const MaxScheduledPerBlock: u32 = 50;
-}
-
-type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
-use frame_support::traits::NamedReservableCurrency;
-
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
- (
- frame_system::CheckSpecVersion::<Runtime>::new(),
- frame_system::CheckGenesis::<Runtime>::new(),
- frame_system::CheckEra::<Runtime>::from(Era::Immortal),
- frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
- from,
- )),
- frame_system::CheckWeight::<Runtime>::new(),
- // sponsoring transaction logic
- // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
- )
-}
-
-pub struct SchedulerPaymentExecutor;
-impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
- DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
-where
- <T as frame_system::Config>::Call: Member
- + Dispatchable<Origin = Origin, Info = DispatchInfo>
- + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
- + GetDispatchInfo
- + From<frame_system::Call<Runtime>>,
- SelfContainedSignedInfo: Send + Sync + 'static,
- Call: From<<T as frame_system::Config>::Call>
- + From<<T as pallet_unique_scheduler::Config>::Call>
- + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
- sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
-{
- fn dispatch_call(
- signer: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::Call,
- ) -> Result<
- Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
- TransactionValidityError,
- > {
- let dispatch_info = call.get_dispatch_info();
- let extrinsic = fp_self_contained::CheckedExtrinsic::<
- AccountId,
- Call,
- SignedExtraScheduler,
- SelfContainedSignedInfo,
- > {
- signed:
- CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
- signer.clone().into(),
- get_signed_extras(signer.into()),
- ),
- function: call.into(),
- };
-
- extrinsic.apply::<Runtime>(&dispatch_info, 0)
- }
-
- fn reserve_balance(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::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());
-
- <Balances as NamedReservableCurrency<AccountId>>::reserve_named(
- &id,
- &(sponsor.into()),
- weight,
- )
- }
-
- fn pay_for_call(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::Call,
- ) -> Result<u128, DispatchError> {
- let dispatch_info = call.get_dispatch_info();
- let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
- Ok(
- <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
- &id,
- &(sponsor.into()),
- weight,
- ),
- )
- }
-
- fn cancel_reserve(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- ) -> Result<u128, DispatchError> {
- Ok(
- <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
- &id,
- &(sponsor.into()),
- u128::MAX,
- ),
- )
- }
-}
-
-parameter_types! {
- pub const NoPreimagePostponement: Option<u32> = Some(10);
- pub const Preimage: Option<u32> = Some(10);
-}
-
-/// Used the compare the privilege of an origin inside the scheduler.
-pub struct OriginPrivilegeCmp;
-
-impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
- fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
- 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<AccountId>;
- type MaxScheduledPerBlock = MaxScheduledPerBlock;
- type WeightInfo = ();
- type CallExecutor = SchedulerPaymentExecutor;
- type OriginPrivilegeCmp = OriginPrivilegeCmp;
- type PreimageProvider = ();
- type NoPreimagePostponement = NoPreimagePostponement;
-}
-
-type EvmSponsorshipHandler = (
- UniqueEthSponsorshipHandler<Runtime>,
- pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
-);
-
-type SponsorshipHandler = (
- UniqueSponsorshipHandler<Runtime>,
- //pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
- pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
-);
-
-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<T>, ValidateUnsigned} = 20,
- ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
-
- Aura: pallet_aura::{Pallet, Config<T>} = 22,
- AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
-
- Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 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<T>} = 34,
- Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
- System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
- Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
- // Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
- // Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
-
- // XCM helpers.
- XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
- PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
- CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
- DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
-
- // Unique Pallets
- Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
- Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
- Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 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<T>} = 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<T>} = 70,
- RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event<T>} = 71,
- RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
-
- // Frontier
- EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 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<UncheckedExtrinsic> for TransactionConverter {
- fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
- UncheckedExtrinsic::new_unsigned(
- pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
- )
- }
-}
-
-impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
- fn convert_transaction(
- &self,
- transaction: pallet_ethereum::Transaction,
- ) -> opaque::UncheckedExtrinsic {
- let extrinsic = UncheckedExtrinsic::new_unsigned(
- pallet_ethereum::Call::<Runtime>::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<AccountId, ()>;
-/// Block header type as expected by this runtime.
-pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
-/// Block type as expected by this runtime.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-/// A Block signed with a Justification
-pub type SignedBlock = generic::SignedBlock<Block>;
-/// BlockId type as expected by this runtime.
-pub type BlockId = generic::BlockId<Block>;
-/// The SignedExtension to the basic transaction logic.
-pub type SignedExtra = (
- frame_system::CheckSpecVersion<Runtime>,
- // system::CheckTxVersion<Runtime>,
- frame_system::CheckGenesis<Runtime>,
- frame_system::CheckEra<Runtime>,
- frame_system::CheckNonce<Runtime>,
- frame_system::CheckWeight<Runtime>,
- ChargeTransactionPayment,
- //pallet_contract_helpers::ContractHelpersExtension<Runtime>,
- pallet_ethereum::FakeTransactionFinalizer<Runtime>,
-);
-pub type SignedExtraScheduler = (
- frame_system::CheckSpecVersion<Runtime>,
- frame_system::CheckGenesis<Runtime>,
- frame_system::CheckEra<Runtime>,
- frame_system::CheckNonce<Runtime>,
- frame_system::CheckWeight<Runtime>,
-);
-/// Unchecked extrinsic type as expected by this runtime.
-pub type UncheckedExtrinsic =
- fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
-/// Extrinsic type that has already been checked.
-pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
-/// Executive: handles dispatch to the various modules.
-pub type Executive = frame_executive::Executive<
- Runtime,
- Block,
- frame_system::ChainContext<Runtime>,
- 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<Result<Self::SignedInfo, TransactionValidityError>> {
- match self {
- Call::Ethereum(call) => call.check_self_contained(),
- _ => None,
- }
- }
-
- fn validate_self_contained(
- &self,
- info: &Self::SignedInfo,
- dispatch_info: &DispatchInfoOf<Call>,
- len: usize,
- ) -> Option<TransactionValidity> {
- match self {
- Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
- _ => None,
- }
- }
-
- fn pre_dispatch_self_contained(
- &self,
- info: &Self::SignedInfo,
- ) -> Option<Result<(), TransactionValidityError>> {
- match self {
- Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
- _ => None,
- }
- }
-
- fn apply_self_contained(
- self,
- info: Self::SignedInfo,
- ) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
- 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 = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::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<AccountId>,
- RmrkInstanceInfo<AccountId>,
- RmrkResourceInfo,
- RmrkPropertyInfo,
- RmrkBaseInfo<AccountId>,
- RmrkPartType,
- RmrkTheme
- > for Runtime {
- fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
- pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>()
- }
-
- fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id)
- }
-
- fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id)
- }
-
- fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id)
- }
-
- fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id)
- }
-
- fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(collection_id, filter_keys)
- }
-
- fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(collection_id, nft_id, filter_keys)
- }
-
- fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(collection_id, nft_id)
- }
-
- fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id)
- }
-
- fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id)
- }
-
- fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id)
- }
-
- fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id)
- }
-
- fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys)
- }
- }
-}
-
-struct CheckInherents;
-
-impl cumulus_pallet_parachain_system::CheckInherents<Block> 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,
runtime/quartz/Cargo.tomldiffbeforeafterboth--- 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
runtime/quartz/src/lib.rsdiffbeforeafterboth--- 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<Runtime>;
-
-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<XAccountId, XBalance> {
- /// 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 = <Balances as Currency<AccountId>>::NegativeImbalance;
-
-pub struct DealWithFees;
-impl OnUnbalanced<NegativeImbalance> for DealWithFees {
- fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
- 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 / <Runtime as frame_system::Config>::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<Self>;
- type EvmAddressMapping = HashedAddressMapping<Self::Hashing>;
- 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<Self>;
- type CallOrigin = EnsureAddressTruncated<Self>;
- type WithdrawOrigin = EnsureAddressTruncated<Self>;
- type AddressMapping = HashedAddressMapping<Self::Hashing>;
- type PrecompilesType = ();
- type PrecompilesValue = ();
- type Currency = Balances;
- type Event = Event;
- type OnMethodCall = (
- pallet_evm_migration::OnMethodCall<Self>,
- pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
- CollectionDispatchT<Self>,
- pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
- );
- type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
- type ChainId = ChainId;
- type Runner = pallet_evm::runner::stack::Runner<Self>;
- type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
- type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
- type FindAuthor = EthereumFindAuthor<Aura>;
-}
-
-impl pallet_evm_migration::Config for Runtime {
- type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
-}
-
-pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
-impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
- fn find_author<'a, I>(digests: I) -> Option<H160>
- where
- I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
- {
- 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<Self>;
-}
-
-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<Balance>;
- /// 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<BlockNumber, BlakeTwo256>;
- /// 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<AccountId, ()>;
- /// 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<Self>;
- /// 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<Self>;
- /// 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<Self>;
-}
-
-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::<pallet_contracts::Pallet<Runtime>> 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() / (
- <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
- <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
- )) / 5) as u32;
- pub Schedule: pallet_contracts::Schedule<Runtime> = 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<Self>;
- type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
- type ChainExtension = NFTExtension;
- type DeletionQueueDepth = DeletionQueueDepth;
- type DeletionWeightLimit = DeletionWeightLimit;
- type Schedule = Schedule;
- type CallStack = [pallet_contracts::Frame<Self>; 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<T>(sp_std::marker::PhantomData<T>);
-
-impl<T> WeightToFeePolynomial for LinearFee<T>
-where
- T: BaseArithmetic + From<u32> + Copy + Unsigned,
-{
- type Balance = T;
-
- fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
- 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<Balances, DealWithFees>;
- type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
- type OperationalFeeMultiplier = OperationalFeeMultiplier;
- type WeightToFee = LinearFee<Balance>;
- 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<AccountId>;
- type RejectOrigin = EnsureRoot<AccountId>;
- 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<Self>;
- type MaxApprovals = MaxApprovals;
-}
-
-impl pallet_sudo::Config for Runtime {
- type Event = Event;
- type Call = Call;
-}
-
-pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
- for RelayChainBlockNumberProvider<T>
-{
- type BlockNumber = BlockNumber;
-
- fn current_block_number() -> Self::BlockNumber {
- cumulus_pallet_parachain_system::Pallet::<T>::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<Runtime>;
- type MinVestedTransfer = MinVestedTransfer;
- type VestedTransferOrigin = EnsureSigned<AccountId>;
- type WeightInfo = ();
- type MaxVestingSchedules = MaxVestingSchedules;
- type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-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<Self>;
- type OnSystemEvent = ();
- // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
- // MaxDownwardMessageWeight,
- // XcmExecutor<XcmConfig>,
- // 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<AccountId>,
- // Sibling parachain origins convert to AccountId via the `ParaId::into`.
- SiblingParachainConvertsVia<Sibling, AccountId>,
- // Straight up local `AccountId32` origins just alias directly to `AccountId`.
- AccountId32Aliases<RelayNetwork, AccountId>,
-);
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
- fn matches_fungible(a: &MultiAsset) -> Option<B> {
- match (&a.id, &a.fun) {
- (Concrete(_), XcmFungible(ref amount)) => CheckedConversion::checked_from(*amount),
- _ => None,
- }
- }
-}
-
-/// Means for transacting assets on this chain.
-pub type LocalAssetTransactor = CurrencyAdapter<
- // Use this currency:
- Balances,
- // Use this currency when it is a fungible asset matching the given location or name:
- 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<LocationToAccountId, Origin>,
- // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
- // recognised.
- RelayChainAsNative<RelayOrigin, Origin>,
- // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
- // recognised.
- SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
- // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
- // transaction from the Root origin.
- ParentAsSuperuser<Origin>,
- // Native signed account converter; this just converts an `AccountId32` origin into a normal
- // `Origin::Signed` origin of the same 32-byte value.
- SignedAccountId32AsNative<RelayNetwork, Origin>,
- // Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
- XcmPassthrough<Origin>,
-);
-
-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> = {
- MultiLocation { parents: 1, interior: Here } |
- MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
- };
-}
-
-pub type Barrier = (
- TakeWeightCredit,
- AllowTopLevelPaidExecutionFrom<Everything>,
- // ^^^ Parent & its unit plurality gets free execution
-);
-
-pub struct UsingOnlySelfCurrencyComponents<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
- Weight,
- Currency::Balance,
- PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
-impl<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > WeightTrader
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn new() -> Self {
- Self(0, Zero::zero(), PhantomData)
- }
-
- fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
- let amount = WeightToFee::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<MultiAsset> {
- 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<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > Drop
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn drop(&mut self) {
- OnUnbalanced::on_unbalanced(Currency::issue(self.1));
- }
-}
-
-pub struct XcmConfig;
-impl Config for XcmConfig {
- type Call = Call;
- 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<Ancestry>;
- type Barrier = Barrier;
- type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
- type Trader =
- UsingOnlySelfCurrencyComponents<LinearFee<Balance>, 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<Origin, AccountId, RelayNetwork>,);
-
-/// 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<ParachainSystem, ()>,
- // ..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<Origin, LocalOriginToLocation>;
- type XcmRouter = XcmRouter;
- type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
- type XcmExecuteFilter = Everything;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type XcmTeleportFilter = Everything;
- type XcmReserveTransferFilter = Everything;
- type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
- type LocationInverter = LocationInverter<Ancestry>;
- 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<XcmConfig>;
-}
-
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
- type WeightInfo = ();
- type Event = Event;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type ChannelInfo = ParachainSystem;
- type VersionWrapper = ();
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
- type ControllerOrigin = EnsureRoot<AccountId>;
- type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
- type Event = Event;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
-
-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<Self>;
- type Event = Event;
- type Currency = Balances;
- type CollectionCreationPrice = CollectionCreationPrice;
- type TreasuryAccountId = TreasuryAccountId;
- type CollectionDispatch = CollectionDispatchT<Self>;
-
- type EvmTokenAddressMapping = EvmTokenAddressMapping;
- type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
- type ContractAddress = EvmCollectionHelpersAddress;
-}
-
-impl pallet_structure::Config for Runtime {
- type Event = Event;
- type Call = Call;
- type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_fungible::Config for Runtime {
- type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_refungible::Config for Runtime {
- type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_nonfungible::Config for Runtime {
- type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_proxy_rmrk_core::Config for Runtime {
- type WeightInfo = pallet_proxy_rmrk_core::weights::SubstrateWeight<Self>;
- type Event = Event;
-}
-
-impl pallet_proxy_rmrk_equip::Config for Runtime {
- type WeightInfo = pallet_proxy_rmrk_equip::weights::SubstrateWeight<Self>;
- type Event = Event;
-}
-
-impl pallet_unique::Config for Runtime {
- type Event = Event;
- type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
- type CommonWeightInfo = CommonWeights<Self>;
- type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
-}
-
-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<Runtime>;
-}
-
-parameter_types! {
- pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
- RuntimeBlockWeights::get().max_block;
- pub const MaxScheduledPerBlock: u32 = 50;
-}
-
-type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
-use frame_support::traits::NamedReservableCurrency;
-
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
- (
- frame_system::CheckSpecVersion::<Runtime>::new(),
- frame_system::CheckGenesis::<Runtime>::new(),
- frame_system::CheckEra::<Runtime>::from(Era::Immortal),
- frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
- from,
- )),
- frame_system::CheckWeight::<Runtime>::new(),
- // sponsoring transaction logic
- // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
- )
-}
-
-pub struct SchedulerPaymentExecutor;
-impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
- DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
-where
- <T as frame_system::Config>::Call: Member
- + Dispatchable<Origin = Origin, Info = DispatchInfo>
- + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
- + GetDispatchInfo
- + From<frame_system::Call<Runtime>>,
- SelfContainedSignedInfo: Send + Sync + 'static,
- Call: From<<T as frame_system::Config>::Call>
- + From<<T as pallet_unique_scheduler::Config>::Call>
- + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
- sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
-{
- fn dispatch_call(
- signer: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::Call,
- ) -> Result<
- Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
- TransactionValidityError,
- > {
- let dispatch_info = call.get_dispatch_info();
- let extrinsic = fp_self_contained::CheckedExtrinsic::<
- AccountId,
- Call,
- SignedExtraScheduler,
- SelfContainedSignedInfo,
- > {
- signed:
- CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
- signer.clone().into(),
- get_signed_extras(signer.into()),
- ),
- function: call.into(),
- };
-
- extrinsic.apply::<Runtime>(&dispatch_info, 0)
- }
-
- fn reserve_balance(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::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());
-
- <Balances as NamedReservableCurrency<AccountId>>::reserve_named(
- &id,
- &(sponsor.into()),
- weight.into(),
- )
- }
-
- fn pay_for_call(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::Call,
- ) -> Result<u128, DispatchError> {
- let dispatch_info = call.get_dispatch_info();
- let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
- Ok(
- <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
- &id,
- &(sponsor.into()),
- weight.into(),
- ),
- )
- }
-
- fn cancel_reserve(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- ) -> Result<u128, DispatchError> {
- Ok(
- <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
- &id,
- &(sponsor.into()),
- u128::MAX,
- ),
- )
- }
-}
-
-parameter_types! {
- pub const NoPreimagePostponement: Option<u32> = Some(10);
- pub const Preimage: Option<u32> = Some(10);
-}
-
-/// Used the compare the privilege of an origin inside the scheduler.
-pub struct OriginPrivilegeCmp;
-
-impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
- fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
- 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<AccountId>;
- type MaxScheduledPerBlock = MaxScheduledPerBlock;
- type WeightInfo = ();
- type CallExecutor = SchedulerPaymentExecutor;
- type OriginPrivilegeCmp = OriginPrivilegeCmp;
- type PreimageProvider = ();
- type NoPreimagePostponement = NoPreimagePostponement;
-}
-
-type EvmSponsorshipHandler = (
- UniqueEthSponsorshipHandler<Runtime>,
- pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
-);
-type SponsorshipHandler = (
- UniqueSponsorshipHandler<Runtime>,
- //pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
- pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
-);
-
-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<T>, ValidateUnsigned} = 20,
- ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
-
- Aura: pallet_aura::{Pallet, Config<T>} = 22,
- AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
-
- Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 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<T>} = 34,
- Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
- System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
- Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
- // Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
- // Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
-
- // XCM helpers.
- XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
- PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
- CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
- DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
-
- // Unique Pallets
- Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
- Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
- Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 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<T>} = 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<T>} = 70,
- RmrkCore: pallet_proxy_rmrk_core::{Pallet, Call, Storage, Event<T>} = 71,
- RmrkEquip: pallet_proxy_rmrk_equip::{Pallet, Call, Storage, Event<T>} = 72,
-
- // Frontier
- EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 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<UncheckedExtrinsic> for TransactionConverter {
- fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
- UncheckedExtrinsic::new_unsigned(
- pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
- )
- }
-}
-
-impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
- fn convert_transaction(
- &self,
- transaction: pallet_ethereum::Transaction,
- ) -> opaque::UncheckedExtrinsic {
- let extrinsic = UncheckedExtrinsic::new_unsigned(
- pallet_ethereum::Call::<Runtime>::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<AccountId, ()>;
-/// Block header type as expected by this runtime.
-pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
-/// Block type as expected by this runtime.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-/// A Block signed with a Justification
-pub type SignedBlock = generic::SignedBlock<Block>;
-/// BlockId type as expected by this runtime.
-pub type BlockId = generic::BlockId<Block>;
-/// The SignedExtension to the basic transaction logic.
-pub type SignedExtra = (
- frame_system::CheckSpecVersion<Runtime>,
- // system::CheckTxVersion<Runtime>,
- frame_system::CheckGenesis<Runtime>,
- frame_system::CheckEra<Runtime>,
- frame_system::CheckNonce<Runtime>,
- frame_system::CheckWeight<Runtime>,
- ChargeTransactionPayment,
- //pallet_contract_helpers::ContractHelpersExtension<Runtime>,
- pallet_ethereum::FakeTransactionFinalizer<Runtime>,
-);
-
-pub type SignedExtraScheduler = (
- frame_system::CheckSpecVersion<Runtime>,
- frame_system::CheckGenesis<Runtime>,
- frame_system::CheckEra<Runtime>,
- frame_system::CheckNonce<Runtime>,
- frame_system::CheckWeight<Runtime>,
- // pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
-);
-/// Unchecked extrinsic type as expected by this runtime.
-pub type UncheckedExtrinsic =
- fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
-/// Extrinsic type that has already been checked.
-pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
-/// Executive: handles dispatch to the various modules.
-pub type Executive = frame_executive::Executive<
- Runtime,
- Block,
- frame_system::ChainContext<Runtime>,
- 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<Result<Self::SignedInfo, TransactionValidityError>> {
- match self {
- Call::Ethereum(call) => call.check_self_contained(),
- _ => None,
- }
- }
-
- fn validate_self_contained(
- &self,
- info: &Self::SignedInfo,
- dispatch_info: &DispatchInfoOf<Call>,
- len: usize,
- ) -> Option<TransactionValidity> {
- match self {
- Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
- _ => None,
- }
- }
-
- fn pre_dispatch_self_contained(
- &self,
- info: &Self::SignedInfo,
- ) -> Option<Result<(), TransactionValidityError>> {
- match self {
- Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
- _ => None,
- }
- }
-
- fn apply_self_contained(
- self,
- info: Self::SignedInfo,
- ) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
- 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 = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::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<AccountId>,
- RmrkInstanceInfo<AccountId>,
- RmrkResourceInfo,
- RmrkPropertyInfo,
- RmrkBaseInfo<AccountId>,
- RmrkPartType,
- RmrkTheme
- > for Runtime {
- fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
- pallet_proxy_rmrk_core::rpc::last_collection_idx::<Runtime>()
- }
-
- fn collection_by_id(collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::collection_by_id::<Runtime>(collection_id)
- }
-
- fn nft_by_id(collection_id: RmrkCollectionId, nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_by_id::<Runtime>(collection_id, nft_by_id)
- }
-
- fn account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::account_tokens::<Runtime>(account_id, collection_id)
- }
-
- fn nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_children::<Runtime>(collection_id, nft_id)
- }
-
- fn collection_properties(collection_id: RmrkCollectionId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::collection_properties::<Runtime>(collection_id, filter_keys)
- }
-
- fn nft_properties(collection_id: RmrkCollectionId, nft_id: RmrkNftId, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_properties::<Runtime>(collection_id, nft_id, filter_keys)
- }
-
- fn nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_resources::<Runtime>(collection_id, nft_id)
- }
-
- fn nft_resource_priority(collection_id: RmrkCollectionId, nft_id: RmrkNftId, resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
- pallet_proxy_rmrk_core::rpc::nft_resource_priority::<Runtime>(collection_id, nft_id, resource_id)
- }
-
- fn base(base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::base::<Runtime>(base_id)
- }
-
- fn base_parts(base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::base_parts::<Runtime>(base_id)
- }
-
- fn theme_names(base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::theme_names::<Runtime>(base_id)
- }
-
- fn theme(base_id: RmrkBaseId, theme_name: RmrkThemeName, filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
- pallet_proxy_rmrk_equip::rpc::theme::<Runtime>(base_id, theme_name, filter_keys)
- }
- }
-}
-
-struct CheckInherents;
-
-impl cumulus_pallet_parachain_system::CheckInherents<Block> 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,
runtime/tests/Cargo.tomldiffbeforeafterboth--- 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' }
runtime/tests/src/lib.rsdiffbeforeafterboth--- 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<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
runtime/unique/Cargo.tomldiffbeforeafterboth--- 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
runtime/unique/src/lib.rsdiffbeforeafterboth--- 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<Runtime>;
-
-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<XAccountId, XBalance> {
- /// 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 = <Balances as Currency<AccountId>>::NegativeImbalance;
-
-pub struct DealWithFees;
-impl OnUnbalanced<NegativeImbalance> for DealWithFees {
- fn on_unbalanceds<B>(mut fees_then_tips: impl Iterator<Item = NegativeImbalance>) {
- 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 / <Runtime as frame_system::Config>::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<Self>;
- type EvmAddressMapping = pallet_evm::HashedAddressMapping<Self::Hashing>;
- 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<Self>;
- type CallOrigin = EnsureAddressTruncated<Self>;
- type WithdrawOrigin = EnsureAddressTruncated<Self>;
- type AddressMapping = HashedAddressMapping<Self::Hashing>;
- type PrecompilesType = ();
- type PrecompilesValue = ();
- type Currency = Balances;
- type Event = Event;
- type OnMethodCall = (
- pallet_evm_migration::OnMethodCall<Self>,
- pallet_evm_contract_helpers::HelpersOnMethodCall<Self>,
- CollectionDispatchT<Self>,
- pallet_unique::eth::CollectionHelpersOnMethodCall<Self>,
- );
- type OnCreate = pallet_evm_contract_helpers::HelpersOnCreate<Self>;
- type ChainId = ChainId;
- type Runner = pallet_evm::runner::stack::Runner<Self>;
- type OnChargeTransaction = pallet_evm::EVMCurrencyAdapter<Balances, DealWithFees>;
- type TransactionValidityHack = pallet_evm_transaction_payment::TransactionValidityHack<Self>;
- type FindAuthor = EthereumFindAuthor<Aura>;
-}
-
-impl pallet_evm_migration::Config for Runtime {
- type WeightInfo = pallet_evm_migration::weights::SubstrateWeight<Self>;
-}
-
-pub struct EthereumFindAuthor<F>(core::marker::PhantomData<F>);
-impl<F: FindAuthor<u32>> FindAuthor<H160> for EthereumFindAuthor<F> {
- fn find_author<'a, I>(digests: I) -> Option<H160>
- where
- I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
- {
- 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<Self>;
-}
-
-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<Balance>;
- /// 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<BlockNumber, BlakeTwo256>;
- /// 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<AccountId, ()>;
- /// 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<Self>;
- /// 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<Self>;
- /// 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<Self>;
-}
-
-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::<pallet_contracts::Pallet<Runtime>> 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() / (
- <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
- <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
- )) / 5) as u32;
- pub Schedule: pallet_contracts::Schedule<Runtime> = 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<Self>;
- type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
- type ChainExtension = NFTExtension;
- type DeletionQueueDepth = DeletionQueueDepth;
- type DeletionWeightLimit = DeletionWeightLimit;
- type Schedule = Schedule;
- type CallStack = [pallet_contracts::Frame<Self>; 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<T>(sp_std::marker::PhantomData<T>);
-
-impl<T> WeightToFeePolynomial for LinearFee<T>
-where
- T: BaseArithmetic + From<u32> + Copy + Unsigned,
-{
- type Balance = T;
-
- fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
- 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<Balances, DealWithFees>;
- type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
- type OperationalFeeMultiplier = OperationalFeeMultiplier;
- type WeightToFee = LinearFee<Balance>;
- 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<AccountId>;
- type RejectOrigin = EnsureRoot<AccountId>;
- 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<Self>;
- type MaxApprovals = MaxApprovals;
-}
-
-impl pallet_sudo::Config for Runtime {
- type Event = Event;
- type Call = Call;
-}
-
-pub struct RelayChainBlockNumberProvider<T>(sp_std::marker::PhantomData<T>);
-
-impl<T: cumulus_pallet_parachain_system::Config> BlockNumberProvider
- for RelayChainBlockNumberProvider<T>
-{
- type BlockNumber = BlockNumber;
-
- fn current_block_number() -> Self::BlockNumber {
- cumulus_pallet_parachain_system::Pallet::<T>::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<Runtime>;
- type MinVestedTransfer = MinVestedTransfer;
- type VestedTransferOrigin = EnsureSigned<AccountId>;
- type WeightInfo = ();
- type MaxVestingSchedules = MaxVestingSchedules;
- type BlockNumberProvider = RelayChainBlockNumberProvider<Runtime>;
-}
-
-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<Self>;
- type OnSystemEvent = ();
- // type DownwardMessageHandlers = cumulus_primitives_utility::UnqueuedDmpAsParent<
- // MaxDownwardMessageWeight,
- // XcmExecutor<XcmConfig>,
- // 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<AccountId>,
- // Sibling parachain origins convert to AccountId via the `ParaId::into`.
- SiblingParachainConvertsVia<Sibling, AccountId>,
- // Straight up local `AccountId32` origins just alias directly to `AccountId`.
- AccountId32Aliases<RelayNetwork, AccountId>,
-);
-
-pub struct OnlySelfCurrency;
-impl<B: TryFrom<u128>> MatchesFungible<B> for OnlySelfCurrency {
- fn matches_fungible(a: &MultiAsset) -> Option<B> {
- match (&a.id, &a.fun) {
- (Concrete(_), XcmFungible(ref amount)) => CheckedConversion::checked_from(*amount),
- _ => None,
- }
- }
-}
-
-/// Means for transacting assets on this chain.
-pub type LocalAssetTransactor = CurrencyAdapter<
- // Use this currency:
- Balances,
- // Use this currency when it is a fungible asset matching the given location or name:
- 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<LocationToAccountId, Origin>,
- // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when
- // recognised.
- RelayChainAsNative<RelayOrigin, Origin>,
- // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when
- // recognised.
- SiblingParachainAsNative<cumulus_pallet_xcm::Origin, Origin>,
- // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a
- // transaction from the Root origin.
- ParentAsSuperuser<Origin>,
- // Native signed account converter; this just converts an `AccountId32` origin into a normal
- // `Origin::Signed` origin of the same 32-byte value.
- SignedAccountId32AsNative<RelayNetwork, Origin>,
- // Xcm origins can be represented natively under the Xcm pallet's Xcm origin.
- XcmPassthrough<Origin>,
-);
-
-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> = {
- MultiLocation { parents: 1, interior: Here } |
- MultiLocation { parents: 1, interior: X1(Plurality { id: BodyId::Unit, .. }) }
- };
-}
-
-pub type Barrier = (
- TakeWeightCredit,
- AllowTopLevelPaidExecutionFrom<Everything>,
- // ^^^ Parent & its unit plurality gets free execution
-);
-
-pub struct UsingOnlySelfCurrencyComponents<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
->(
- Weight,
- Currency::Balance,
- PhantomData<(WeightToFee, AssetId, AccountId, Currency, OnUnbalanced)>,
-);
-impl<
- WeightToFee: WeightToFeePolynomial<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > WeightTrader
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn new() -> Self {
- Self(0, Zero::zero(), PhantomData)
- }
-
- fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, XcmError> {
- let amount = WeightToFee::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<MultiAsset> {
- 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<Balance = Currency::Balance>,
- AssetId: Get<MultiLocation>,
- AccountId,
- Currency: CurrencyT<AccountId>,
- OnUnbalanced: OnUnbalancedT<Currency::NegativeImbalance>,
- > Drop
- for UsingOnlySelfCurrencyComponents<WeightToFee, AssetId, AccountId, Currency, OnUnbalanced>
-{
- fn drop(&mut self) {
- OnUnbalanced::on_unbalanced(Currency::issue(self.1));
- }
-}
-
-pub struct XcmConfig;
-impl Config for XcmConfig {
- type Call = Call;
- 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<Ancestry>;
- type Barrier = Barrier;
- type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
- type Trader =
- UsingOnlySelfCurrencyComponents<LinearFee<Balance>, 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<Origin, AccountId, RelayNetwork>,);
-
-/// 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<ParachainSystem, ()>,
- // ..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<Origin, LocalOriginToLocation>;
- type XcmRouter = XcmRouter;
- type ExecuteXcmOrigin = EnsureXcmOrigin<Origin, LocalOriginToLocation>;
- type XcmExecuteFilter = Everything;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type XcmTeleportFilter = Everything;
- type XcmReserveTransferFilter = Everything;
- type Weigher = FixedWeightBounds<UnitWeightCost, Call, MaxInstructions>;
- type LocationInverter = LocationInverter<Ancestry>;
- 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<XcmConfig>;
-}
-
-impl cumulus_pallet_xcmp_queue::Config for Runtime {
- type WeightInfo = ();
- type Event = Event;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type ChannelInfo = ParachainSystem;
- type VersionWrapper = ();
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
- type ControllerOrigin = EnsureRoot<AccountId>;
- type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin;
-}
-
-impl cumulus_pallet_dmp_queue::Config for Runtime {
- type Event = Event;
- type XcmExecutor = XcmExecutor<XcmConfig>;
- type ExecuteOverweightOrigin = frame_system::EnsureRoot<AccountId>;
-}
-
-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<Self>;
- type Event = Event;
- type Currency = Balances;
- type CollectionCreationPrice = CollectionCreationPrice;
- type TreasuryAccountId = TreasuryAccountId;
- type CollectionDispatch = CollectionDispatchT<Self>;
-
- type EvmTokenAddressMapping = EvmTokenAddressMapping;
- type CrossTokenAddressMapping = CrossTokenAddressMapping<Self::AccountId>;
- type ContractAddress = EvmCollectionHelpersAddress;
-}
-
-impl pallet_structure::Config for Runtime {
- type Event = Event;
- type Call = Call;
- type WeightInfo = pallet_structure::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_fungible::Config for Runtime {
- type WeightInfo = pallet_fungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_refungible::Config for Runtime {
- type WeightInfo = pallet_refungible::weights::SubstrateWeight<Self>;
-}
-impl pallet_nonfungible::Config for Runtime {
- type WeightInfo = pallet_nonfungible::weights::SubstrateWeight<Self>;
-}
-
-impl pallet_unique::Config for Runtime {
- type Event = Event;
- type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
- type CommonWeightInfo = CommonWeights<Self>;
- type RefungibleExtensionsWeightInfo = CommonWeights<Self>;
-}
-
-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<Runtime>;
-}
-
-parameter_types! {
- pub MaximumSchedulerWeight: Weight = Perbill::from_percent(50) *
- RuntimeBlockWeights::get().max_block;
- pub const MaxScheduledPerBlock: u32 = 50;
-}
-
-type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
-use frame_support::traits::NamedReservableCurrency;
-
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
- (
- frame_system::CheckSpecVersion::<Runtime>::new(),
- frame_system::CheckGenesis::<Runtime>::new(),
- frame_system::CheckEra::<Runtime>::from(Era::Immortal),
- frame_system::CheckNonce::<Runtime>::from(frame_system::Pallet::<Runtime>::account_nonce(
- from,
- )),
- frame_system::CheckWeight::<Runtime>::new(),
- // sponsoring transaction logic
- // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
- )
-}
-
-pub struct SchedulerPaymentExecutor;
-impl<T: frame_system::Config + pallet_unique_scheduler::Config, SelfContainedSignedInfo>
- DispatchCall<T, SelfContainedSignedInfo> for SchedulerPaymentExecutor
-where
- <T as frame_system::Config>::Call: Member
- + Dispatchable<Origin = Origin, Info = DispatchInfo>
- + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>
- + GetDispatchInfo
- + From<frame_system::Call<Runtime>>,
- SelfContainedSignedInfo: Send + Sync + 'static,
- Call: From<<T as frame_system::Config>::Call>
- + From<<T as pallet_unique_scheduler::Config>::Call>
- + SelfContainedCall<SignedInfo = SelfContainedSignedInfo>,
- sp_runtime::AccountId32: From<<T as frame_system::Config>::AccountId>,
-{
- fn dispatch_call(
- signer: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::Call,
- ) -> Result<
- Result<PostDispatchInfo, DispatchErrorWithPostInfo<PostDispatchInfo>>,
- TransactionValidityError,
- > {
- let dispatch_info = call.get_dispatch_info();
- let extrinsic = fp_self_contained::CheckedExtrinsic::<
- AccountId,
- Call,
- SignedExtraScheduler,
- SelfContainedSignedInfo,
- > {
- signed:
- CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
- signer.clone().into(),
- get_signed_extras(signer.into()),
- ),
- function: call.into(),
- };
-
- extrinsic.apply::<Runtime>(&dispatch_info, 0)
- }
-
- fn reserve_balance(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::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());
-
- <Balances as NamedReservableCurrency<AccountId>>::reserve_named(
- &id,
- &(sponsor.into()),
- weight,
- )
- }
-
- fn pay_for_call(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- call: <T as pallet_unique_scheduler::Config>::Call,
- ) -> Result<u128, DispatchError> {
- let dispatch_info = call.get_dispatch_info();
- let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
- Ok(
- <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
- &id,
- &(sponsor.into()),
- weight,
- ),
- )
- }
-
- fn cancel_reserve(
- id: [u8; 16],
- sponsor: <T as frame_system::Config>::AccountId,
- ) -> Result<u128, DispatchError> {
- Ok(
- <Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
- &id,
- &(sponsor.into()),
- u128::MAX,
- ),
- )
- }
-}
-
-parameter_types! {
- pub const NoPreimagePostponement: Option<u32> = Some(10);
- pub const Preimage: Option<u32> = Some(10);
-}
-
-/// Used the compare the privilege of an origin inside the scheduler.
-pub struct OriginPrivilegeCmp;
-
-impl PrivilegeCmp<OriginCaller> for OriginPrivilegeCmp {
- fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option<Ordering> {
- 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<AccountId>;
- type MaxScheduledPerBlock = MaxScheduledPerBlock;
- type WeightInfo = ();
- type CallExecutor = SchedulerPaymentExecutor;
- type OriginPrivilegeCmp = OriginPrivilegeCmp;
- type PreimageProvider = ();
- type NoPreimagePostponement = NoPreimagePostponement;
-}
-
-type EvmSponsorshipHandler = (
- UniqueEthSponsorshipHandler<Runtime>,
- pallet_evm_contract_helpers::HelpersContractSponsoring<Runtime>,
-);
-type SponsorshipHandler = (
- UniqueSponsorshipHandler<Runtime>,
- //pallet_contract_helpers::ContractSponsorshipHandler<Runtime>,
- pallet_evm_transaction_payment::BridgeSponsorshipHandler<Runtime>,
-);
-
-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<T>, ValidateUnsigned} = 20,
- ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21,
-
- Aura: pallet_aura::{Pallet, Config<T>} = 22,
- AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23,
-
- Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>} = 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<T>} = 34,
- Sudo: pallet_sudo::{Pallet, Call, Storage, Config<T>, Event<T>} = 35,
- System: frame_system::{Pallet, Call, Storage, Config, Event<T>} = 36,
- Vesting: orml_vesting::{Pallet, Storage, Call, Event<T>, Config<T>} = 37,
- // Vesting: pallet_vesting::{Pallet, Call, Config<T>, Storage, Event<T>} = 37,
- // Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>} = 38,
-
- // XCM helpers.
- XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event<T>} = 50,
- PolkadotXcm: pallet_xcm::{Pallet, Call, Event<T>, Origin} = 51,
- CumulusXcm: cumulus_pallet_xcm::{Pallet, Call, Event<T>, Origin} = 52,
- DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
-
- // Unique Pallets
- Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
- Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
- Scheduler: pallet_unique_scheduler::{Pallet, Call, Storage, Event<T>} = 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<T>} = 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<T>} = 70,
-
- // Frontier
- EVM: pallet_evm::{Pallet, Config, Call, Storage, Event<T>} = 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<UncheckedExtrinsic> for TransactionConverter {
- fn convert_transaction(&self, transaction: pallet_ethereum::Transaction) -> UncheckedExtrinsic {
- UncheckedExtrinsic::new_unsigned(
- pallet_ethereum::Call::<Runtime>::transact { transaction }.into(),
- )
- }
-}
-
-impl fp_rpc::ConvertTransaction<opaque::UncheckedExtrinsic> for TransactionConverter {
- fn convert_transaction(
- &self,
- transaction: pallet_ethereum::Transaction,
- ) -> opaque::UncheckedExtrinsic {
- let extrinsic = UncheckedExtrinsic::new_unsigned(
- pallet_ethereum::Call::<Runtime>::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<AccountId, ()>;
-/// Block header type as expected by this runtime.
-pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
-/// Block type as expected by this runtime.
-pub type Block = generic::Block<Header, UncheckedExtrinsic>;
-/// A Block signed with a Justification
-pub type SignedBlock = generic::SignedBlock<Block>;
-/// BlockId type as expected by this runtime.
-pub type BlockId = generic::BlockId<Block>;
-/// The SignedExtension to the basic transaction logic.
-pub type SignedExtra = (
- frame_system::CheckSpecVersion<Runtime>,
- // system::CheckTxVersion<Runtime>,
- frame_system::CheckGenesis<Runtime>,
- frame_system::CheckEra<Runtime>,
- frame_system::CheckNonce<Runtime>,
- frame_system::CheckWeight<Runtime>,
- pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
- //pallet_contract_helpers::ContractHelpersExtension<Runtime>,
- pallet_ethereum::FakeTransactionFinalizer<Runtime>,
-);
-pub type SignedExtraScheduler = (
- frame_system::CheckSpecVersion<Runtime>,
- frame_system::CheckGenesis<Runtime>,
- frame_system::CheckEra<Runtime>,
- frame_system::CheckNonce<Runtime>,
- frame_system::CheckWeight<Runtime>,
- // pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
-);
-/// Unchecked extrinsic type as expected by this runtime.
-pub type UncheckedExtrinsic =
- fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
-/// Extrinsic type that has already been checked.
-pub type CheckedExtrinsic = fp_self_contained::CheckedExtrinsic<AccountId, Call, SignedExtra, H160>;
-/// Executive: handles dispatch to the various modules.
-pub type Executive = frame_executive::Executive<
- Runtime,
- Block,
- frame_system::ChainContext<Runtime>,
- 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<Result<Self::SignedInfo, TransactionValidityError>> {
- match self {
- Call::Ethereum(call) => call.check_self_contained(),
- _ => None,
- }
- }
-
- fn validate_self_contained(
- &self,
- info: &Self::SignedInfo,
- dispatch_info: &DispatchInfoOf<Call>,
- len: usize,
- ) -> Option<TransactionValidity> {
- match self {
- Call::Ethereum(call) => call.validate_self_contained(info, dispatch_info, len),
- _ => None,
- }
- }
-
- fn pre_dispatch_self_contained(
- &self,
- info: &Self::SignedInfo,
- ) -> Option<Result<(), TransactionValidityError>> {
- match self {
- Call::Ethereum(call) => call.pre_dispatch_self_contained(info),
- _ => None,
- }
- }
-
- fn apply_self_contained(
- self,
- info: Self::SignedInfo,
- ) -> Option<sp_runtime::DispatchResultWithInfo<PostDispatchInfoOf<Self>>> {
- 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 = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::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<AccountId>,
- RmrkInstanceInfo<AccountId>,
- RmrkResourceInfo,
- RmrkPropertyInfo,
- RmrkBaseInfo<AccountId>,
- RmrkPartType,
- RmrkTheme
- > for Runtime {
- fn last_collection_idx() -> Result<RmrkCollectionId, DispatchError> {
- Ok(Default::default())
- }
-
- fn collection_by_id(_collection_id: RmrkCollectionId) -> Result<Option<RmrkCollectionInfo<AccountId>>, DispatchError> {
- Ok(Default::default())
- }
-
- fn nft_by_id(_collection_id: RmrkCollectionId, _nft_by_id: RmrkNftId) -> Result<Option<RmrkInstanceInfo<AccountId>>, DispatchError> {
- Ok(Default::default())
- }
-
- fn account_tokens(_account_id: AccountId, _collection_id: RmrkCollectionId) -> Result<Vec<RmrkNftId>, DispatchError> {
- Ok(Default::default())
- }
-
- fn nft_children(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result<Vec<RmrkNftChild>, DispatchError> {
- Ok(Default::default())
- }
-
- fn collection_properties(_collection_id: RmrkCollectionId, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- Ok(Default::default())
- }
-
- fn nft_properties(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Vec<RmrkPropertyInfo>, DispatchError> {
- Ok(Default::default())
- }
-
- fn nft_resources(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId) -> Result<Vec<RmrkResourceInfo>, DispatchError> {
- Ok(Default::default())
- }
-
- fn nft_resource_priority(_collection_id: RmrkCollectionId, _nft_id: RmrkNftId, _resource_id: RmrkResourceId) -> Result<Option<u32>, DispatchError> {
- Ok(Default::default())
- }
-
- fn base(_base_id: RmrkBaseId) -> Result<Option<RmrkBaseInfo<AccountId>>, DispatchError> {
- Ok(Default::default())
- }
-
- fn base_parts(_base_id: RmrkBaseId) -> Result<Vec<RmrkPartType>, DispatchError> {
- Ok(Default::default())
- }
-
- fn theme_names(_base_id: RmrkBaseId) -> Result<Vec<RmrkThemeName>, DispatchError> {
- Ok(Default::default())
- }
-
- fn theme(_base_id: RmrkBaseId, _theme_name: RmrkThemeName, _filter_keys: Option<Vec<RmrkPropertyKey>>) -> Result<Option<RmrkTheme>, DispatchError> {
- Ok(Default::default())
- }
- }
-}
-
-struct CheckInherents;
-
-impl cumulus_pallet_parachain_system::CheckInherents<Block> 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,
tests/package.jsondiffbeforeafterboth--- 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"
tests/src/approve.test.tsdiffbeforeafterboth--- 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');
tests/src/burnItem.test.tsdiffbeforeafterboth--- 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});
tests/src/confirmSponsorship.test.tsdiffbeforeafterboth--- 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');
tests/src/createCollection.test.tsdiffbeforeafterboth--- 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'}});
});
tests/src/createItem.test.tsdiffbeforeafterboth--- 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;
tests/src/createMultipleItems.test.tsdiffbeforeafterboth--- 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);
tests/src/createMultipleItemsEx.test.tsdiffbeforeafterboth--- 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$/);
});
});
});
tests/src/destroyCollection.test.tsdiffbeforeafterboth--- 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);
});
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- 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(
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- 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
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- 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
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- 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
tests/src/eth/api/UniqueRefungibleToken.soldiffbeforeafterboth--- 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
{}
tests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth--- 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);
tests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth--- 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"
},
{
tests/src/eth/createRFTCollection.test.tsdiffbeforeafterboth--- 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
+});
tests/src/eth/fractionalizer/Fractionalizer.bindiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/fractionalizer/Fractionalizer.bin
@@ -0,0 +1 @@
+60c0604052600a60805269526546756e6769626c6560b01b60a0527fcdb72fd4e1d0d6d4eebd7ab142113ec2b4b06ddb24324db5c287ef01ab484d6b60055534801561004a57600080fd5b50600480546001600160a01b0319163317905561137a8061006c6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063115091401461005c5780631b191ea214610071578063d470e60f14610084578063dbc38ad214610097578063eb292412146100aa575b600080fd5b61006f61006a366004610f85565b6100bd565b005b61006f61007f366004610ff2565b61035b565b61006f61009236600461108c565b6104c0565b61006f6100a53660046110b8565b61089d565b61006f6100b8366004611114565b610ee0565b6004546001600160a01b031633146100f05760405162461bcd60e51b81526004016100e79061114d565b60405180910390fd5b6000546001600160a01b0316156101495760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b60008190506000816001600160a01b031663d34b55b86040518163ffffffff1660e01b81526004016000604051808303816000875af1158015610190573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526101b8919081019061118b565b905060055481805190602001201461022f5760405162461bcd60e51b815260206004820152603460248201527f57726f6e6720636f6c6c656374696f6e20747970652e20436f6c6c656374696f604482015273371034b9903737ba103932b33ab733b4b136329760611b60648201526084016100e7565b816001600160a01b03166304a460536040518163ffffffff1660e01b81526004016020604051808303816000875af115801561026f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610293919061125b565b6103055760405162461bcd60e51b815260206004820152603c60248201527f4672616374696f6e616c697a657220636f6e74726163742073686f756c64206260448201527f6520616e2061646d696e206f662074686520636f6c6c656374696f6e0000000060648201526084016100e7565b600080546001600160a01b0319166001600160a01b0385169081179091556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef9060200160405180910390a1505050565b6004546001600160a01b031633146103855760405162461bcd60e51b81526004016100e79061114d565b6000546001600160a01b0316156103de5760405162461bcd60e51b815260206004820152601d60248201527f52465420636f6c6c656374696f6e20697320616c72656164792073657400000060448201526064016100e7565b6040516344a68ad560e01b8152736c4e9fe1ae37a41e93cee429e8e1881abdcbb54f9081906344a68ad590610421908a908a908a908a908a908a906004016112a1565b6020604051808303816000875af1158015610440573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061046491906112ea565b600080546001600160a01b0319166001600160a01b039290921691821790556040519081527f7186a599bf2297b1f4c8957d30b0965291eee0021a5f9a0aeb54dcbd1ffdceef906020015b60405180910390a150505050505050565b6000546001600160a01b03166105145760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b6000546001600160a01b038381169116146105685760405162461bcd60e51b81526020600482015260146024820152732bb937b7339029232a1031b7b63632b1ba34b7b760611b60448201526064016100e7565b600080546040516355bb7d6360e11b8152600481018490526001600160a01b039091169190829063ab76fac690602401602060405180830381865afa1580156105b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d991906112ea565b6001600160a01b03808216600090815260036020908152604091829020825180840190935280549093168083526001909301549082015291925061065f5760405162461bcd60e51b815260206004820181905260248201527f4e6f20636f72726573706f6e64696e67204e465420746f6b656e20666f756e6460448201526064016100e7565b6000829050806001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106c69190611307565b6040516370a0823160e01b81523360048201526001600160a01b038316906370a0823190602401602060405180830381865afa15801561070a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061072e9190611307565b1461078a5760405162461bcd60e51b815260206004820152602660248201527f4e6f7420616c6c2070696563657320617265206f776e6564206279207468652060448201526531b0b63632b960d11b60648201526084016100e7565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd906107ba90339030908a90600401611320565b600060405180830381600087803b1580156107d457600080fd5b505af11580156107e8573d6000803e3d6000fd5b5050835160208501516040516323b872dd60e01b81526001600160a01b0390921693506323b872dd92506108229130913391600401611320565b600060405180830381600087803b15801561083c57600080fd5b505af1158015610850573d6000803e3d6000fd5b5050835160208501516040517fe9e9808d24ff79ccc3b1ecf48be7b2d11591adccc452150d0d7947cb48eb0d53945061088d935087929190611320565b60405180910390a1505050505050565b6000546001600160a01b03166108f15760405162461bcd60e51b81526020600482015260196024820152781491950818dbdb1b1958dd1a5bdb881a5cc81b9bdd081cd95d603a1b60448201526064016100e7565b600080546001600160a01b0385811683526001602081905260409093205491169160ff90911615151461098c5760405162461bcd60e51b815260206004820152603c60248201527f4672616374696f6e616c697a6174696f6e206f66207468697320636f6c6c656360448201527f74696f6e206973206e6f7420616c6c6f7765642062792061646d696e0000000060648201526084016100e7565b6040516331a9108f60e11b81526004810184905233906001600160a01b03861690636352211e90602401602060405180830381865afa1580156109d3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109f791906112ea565b6001600160a01b031614610a5d5760405162461bcd60e51b815260206004820152602760248201527f4f6e6c7920746f6b656e206f776e657220636f756c64206672616374696f6e616044820152661b1a5e99481a5d60ca1b60648201526084016100e7565b6040516323b872dd60e01b81526001600160a01b038516906323b872dd90610a8d90339030908890600401611320565b600060405180830381600087803b158015610aa757600080fd5b505af1158015610abb573d6000803e3d6000fd5b505050506001600160a01b0384166000908152600260209081526040808320868452909152812054819081908103610d0757836001600160a01b03166375794a3c6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b4f9190611307565b6040516340c10f1960e01b8152306004820152602481018290529093506001600160a01b038516906340c10f19906044016020604051808303816000875af1158015610b9f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bc3919061125b565b506040516355bb7d6360e11b8152600481018490526001600160a01b0385169063ab76fac690602401602060405180830381865afa158015610c09573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c2d91906112ea565b6001600160a01b0388811660008181526002602090815260408083208c84528252808320899055805180820182528481528083018d8152878716808652600390945293829020905181546001600160a01b0319169616959095178555915160019094019390935551630217888360e11b81526004810191909152602481018990529193508392509063042f1106906044016020604051808303816000875af1158015610cdd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d01919061125b565b50610d98565b6001600160a01b0387811660009081526002602090815260408083208a8452909152908190205490516355bb7d6360e11b8152600481018290529094509085169063ab76fac690602401602060405180830381865afa158015610d6e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9291906112ea565b91508190505b60405163d2418ca760e01b81526001600160801b03861660048201526001600160a01b0382169063d2418ca7906024016020604051808303816000875af1158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b919061125b565b5060405163a9059cbb60e01b81523360048201526001600160801b03861660248201526001600160a01b0382169063a9059cbb906044016020604051808303816000875af1158015610e61573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e85919061125b565b50604080516001600160a01b03808a168252602082018990528416918101919091526001600160801b03861660608201527f29f372538523984f33874da1b50e596ce0f180a6eb04d7ff22bb2ce80e1576b6906080016104af565b6004546001600160a01b03163314610f0a5760405162461bcd60e51b81526004016100e79061114d565b6001600160a01b038216600081815260016020908152604091829020805460ff19168515159081179091558251938452908301527f6dad0aed33f4b7f07095619b668698e17943fd9f4c83e7cfcc7f6dd880a11588910160405180910390a15050565b6001600160a01b0381168114610f8257600080fd5b50565b600060208284031215610f9757600080fd5b8135610fa281610f6d565b9392505050565b60008083601f840112610fbb57600080fd5b50813567ffffffffffffffff811115610fd357600080fd5b602083019150836020828501011115610feb57600080fd5b9250929050565b6000806000806000806060878903121561100b57600080fd5b863567ffffffffffffffff8082111561102357600080fd5b61102f8a838b01610fa9565b9098509650602089013591508082111561104857600080fd5b6110548a838b01610fa9565b9096509450604089013591508082111561106d57600080fd5b5061107a89828a01610fa9565b979a9699509497509295939492505050565b6000806040838503121561109f57600080fd5b82356110aa81610f6d565b946020939093013593505050565b6000806000606084860312156110cd57600080fd5b83356110d881610f6d565b92506020840135915060408401356001600160801b03811681146110fb57600080fd5b809150509250925092565b8015158114610f8257600080fd5b6000806040838503121561112757600080fd5b823561113281610f6d565b9150602083013561114281611106565b809150509250929050565b6020808252600e908201526d27b7363c9037bbb732b91031b0b760911b604082015260600190565b634e487b7160e01b600052604160045260246000fd5b6000602080838503121561119e57600080fd5b825167ffffffffffffffff808211156111b657600080fd5b818501915085601f8301126111ca57600080fd5b8151818111156111dc576111dc611175565b604051601f8201601f19908116603f0116810190838211818310171561120457611204611175565b81604052828152888684870101111561121c57600080fd5b600093505b8284101561123e5784840186015181850187015292850192611221565b8284111561124f5760008684830101525b98975050505050505050565b60006020828403121561126d57600080fd5b8151610fa281611106565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6060815260006112b560608301888a611278565b82810360208401526112c8818789611278565b905082810360408401526112dd818587611278565b9998505050505050505050565b6000602082840312156112fc57600080fd5b8151610fa281610f6d565b60006020828403121561131957600080fd5b5051919050565b6001600160a01b03938416815291909216602082015260408101919091526060019056fea2646970667358221220a118fe8c3b83b3933baa7bfe084ed165a014ec509367252e5c99e1a3332d000364736f6c634300080f0033
\ No newline at end of file
tests/src/eth/fractionalizer/Fractionalizer.soldiffbeforeafterboth--- /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
tests/src/eth/fractionalizer/FractionalizerAbi.jsondiffbeforeafterboth--- /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"
+ }
+]
tests/src/eth/fractionalizer/fractionalizer.test.tsdiffbeforeafterboth--- /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 <http://www.gnu.org/licenses/>.
+
+
+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
tests/src/eth/fungibleAbi.jsondiffbeforeafterboth--- 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"
}
]
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- 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"
}
]
tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-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
+});
tests/src/eth/reFungibleAbi.jsondiffbeforeafterboth--- 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"
}
]
tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-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);
+ });
+});
tests/src/eth/reFungibleTokenAbi.jsondiffbeforeafterboth--- 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",
tests/src/eth/scheduling.test.tsdiffbeforeafterboth--- 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
+});
tests/src/eth/util/helpers.tsdiffbeforeafterboth--- 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: {
tests/src/fungible.test.tsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-import {default as usingApi} from './substrate/substrate-api';
import {IKeyringPair} from '@polkadot/types/types';
-import {
- getBalance,
- createMultipleItemsExpectSuccess,
- isTokenExists,
- getLastTokenId,
- getAllowance,
- approve,
- transferFrom,
- createCollection,
- transfer,
- burnItem,
- normalizeAccountId,
- CrossAccountId,
- createFungibleItemExpectSuccess,
- U128_MAX,
- burnFromExpectSuccess,
-} from './util/helpers';
+import {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);
});
});
});
tests/src/interfaces/augment-api-query.tsdiffbeforeafterboth--- 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<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
+ tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | 'Eth' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
/**
* Used to enumerate token's children.
**/
tests/src/interfaces/default/types.tsdiffbeforeafterboth--- 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 */
tests/src/interfaces/lookup.tsdiffbeforeafterboth--- 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<T>
tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- 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) */
tests/src/limits.test.tsdiffbeforeafterboth--- 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');
tests/src/nesting/nest.test.tsdiffbeforeafterboth--- 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:[]}});
tests/src/nesting/properties.test.tsdiffbeforeafterboth--- 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');
tests/src/nesting/rules-smoke.test.tsdiffbeforeafterboth--- 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: {}})))
tests/src/nesting/unnest.test.tsdiffbeforeafterboth--- 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}});
tests/src/nextSponsoring.test.tsdiffbeforeafterboth--- 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';
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- 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
}
});
});
tests/src/refungible.test.tsdiffbeforeafterboth--- 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 <http://www.gnu.org/licenses/>.
-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);
});
});
});
+
tests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth--- 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)});
});
});
});
tests/src/rmrk/acceptNft.test.tsdiffbeforeafterboth--- 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,
tests/src/rmrk/addResource.test.tsdiffbeforeafterboth--- 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 () => {
tests/src/rmrk/addTheme.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/burnNft.test.tsdiffbeforeafterboth--- 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,
tests/src/rmrk/changeCollectionIssuer.test.tsdiffbeforeafterboth--- 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,
tests/src/rmrk/createBase.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/createCollection.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/deleteCollection.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/equipNft.test.tsdiffbeforeafterboth--- 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 () => {
tests/src/rmrk/getOwnedNfts.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/lockCollection.test.tsdiffbeforeafterboth--- 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 () => {
tests/src/rmrk/mintNft.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/rejectNft.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/removeResource.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/rmrkIsolation.test.tsdiffbeforeafterboth--- 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]);
});
});
tests/src/rmrk/sendNft.test.tsdiffbeforeafterboth--- 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;
tests/src/rmrk/setCollectionProperty.test.tsdiffbeforeafterboth--- 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 () => {
tests/src/rmrk/setEquippableList.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/setNftProperty.test.tsdiffbeforeafterboth--- 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';
tests/src/rmrk/setResourcePriorities.test.tsdiffbeforeafterboth--- 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';
tests/src/setCollectionSponsor.test.tsdiffbeforeafterboth--- 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);
});
tests/src/transfer.test.tsdiffbeforeafterboth--- 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');
tests/src/transferFrom.test.tsdiffbeforeafterboth--- 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');
tests/src/util/helpers.tsdiffbeforeafterboth--- 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<boolean> {
+ return usingApi(async api => {
+ const chain = await api.rpc.system.chain();
+
+ return chain.eq('UNIQUE');
+ });
+}
+
+export async function isQuartz(): Promise<boolean> {
+ 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<string[]> {
+ return await usingApi(async api => {
+ const pallets = getModuleNames(api);
+
+ return requiredPallets.filter(p => !pallets.includes(p));
+ });
+}
+
+export async function checkPalletsPresence(requiredPallets: string[]): Promise<boolean> {
+ 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) {
tests/src/util/playgrounds/index.tsdiffbeforeafterboth--- /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<void> {
+ 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<void>) => {
+ // 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
tests/src/util/playgrounds/unique.tsdiffbeforeafterboth--- /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<TUniqueNetworks> {
+ 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<TUniqueNetworks> {
+ 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<number | null> {
+ 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<number> {
+ 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<ICrossAccountId[]> {
+ 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<ICollectionLimits> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<bigint> {
+ 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<number> {
+ 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<boolean> {
+ 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<number[]> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<UniqueCollectionBase> {
+ 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<ICrossAccountId> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<ICrossAccountId | null> {
+ 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<IToken[]> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<UniqueNFTCollection> {
+ 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<UniqueNFTToken> {
+ 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<UniqueNFTToken[]> {
+ 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<UniqueNFTToken[]> {
+ 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<ICrossAccountId[]> {
+ 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<bigint> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<UniqueRFTCollection> {
+ 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<UniqueRFTToken> {
+ 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<UniqueRFTToken[]> {
+ 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<UniqueRFTToken[]> {
+ 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<bigint> {
+ 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<boolean> {
+ 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<UniqueFTCollection> {
+ 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<boolean> {
+ 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<boolean> {
+ 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<ICrossAccountId[]> {
+ 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<bigint> {
+ 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<boolean> {
+ 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<boolean> {
+ return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, label, amount);
+ }
+
+ /**
+ * Get total collection supply
+ * @param collectionId
+ * @returns
+ */
+ async getTotalPieces(collectionId: number): Promise<bigint> {
+ 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<number> {
+ 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<string | null> {
+ 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<number> {
+ 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<bigint> {
+ 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<bigint> {
+ 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<boolean> {
+ 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<TSubstrateAccount> {
+ 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<TSubstrateAccount> {
+ 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
tests/yarn.lockdiffbeforeafterboth--- 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"