--- a/.docker/Dockerfile-try-runtime +++ b/.docker/Dockerfile-try-runtime @@ -46,4 +46,4 @@ echo "Fork from: $REPLICA_FROM\n" && \ cargo build --features=try-runtime,${NETWORK}-runtime --release -CMD cargo run --features=try-runtime,${NETWORK}-runtime --release -- try-runtime -ltry-runtime::cli=debug --no-spec-check-panic on-runtime-upgrade live --uri $REPLICA_FROM +CMD cargo run --release --features ${NETWORK}-runtime,try-runtime -- try-runtime --runtime target/release/wbuild/${NETWORK}-runtime/${NETWORK}_runtime.compact.compressed.wasm -lruntime=debug -ltry-runtime::cli=debug on-runtime-upgrade --checks live --uri $REPLICA_FROM --- a/.env +++ b/.env @@ -5,19 +5,19 @@ STATEMINT_BUILD_BRANCH=release-parachains-v9320 ACALA_BUILD_BRANCH=2.11.0 MOONBEAM_BUILD_BRANCH=runtime-1901 -UNIQUE_MAINNET_BRANCH=release-v930033 +UNIQUE_MAINNET_BRANCH=release-v930033-fix-gas-price UNIQUE_REPLICA_FROM=wss://eu-ws.unique.network:443 KUSAMA_MAINNET_BRANCH=release-v0.9.35 STATEMINE_BUILD_BRANCH=release-parachains-v9330 KARURA_BUILD_BRANCH=release-karura-2.11.0 MOONRIVER_BUILD_BRANCH=runtime-2000 -QUARTZ_MAINNET_BRANCH=release-v930034 +QUARTZ_MAINNET_BRANCH=release-v930034-fix-gas-price QUARTZ_REPLICA_FROM=wss://eu-ws-quartz.unique.network:443 UNQWND_MAINNET_BRANCH=release-v0.9.30 WESTMINT_BUILD_BRANCH=parachains-v9330 -OPAL_MAINNET_BRANCH=release-v930034 +OPAL_MAINNET_BRANCH=release-v930034-fix-gas-price OPAL_REPLICA_FROM=wss://eu-ws-opal.unique.network:443 POLKADOT_LAUNCH_BRANCH=unique-network --- a/.github/workflows/ci-master.yml +++ b/.github/workflows/ci-master.yml @@ -42,3 +42,6 @@ codestyle: uses: ./.github/workflows/codestyle.yml + + polkadot-types: + uses: ./.github/workflows/polkadot-types.yml \ No newline at end of file --- /dev/null +++ b/.github/workflows/polkadot-types.yml @@ -0,0 +1,96 @@ +# Integration test in --dev mode +# https://cryptousetech.atlassian.net/wiki/spaces/CI/pages/2586411104/Integration+tests +name: Polkadot types + +# Triger: only call from main workflow(re-usable workflows) +on: + workflow_call: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# on: +# pull_request: +# branches: [ 'develop' ] +# types: [ opened, reopened, synchronize, ready_for_review, converted_to_draft ] + + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + polkadot_generate_types: + # The type of runner that the job will run on + runs-on: [self-hosted-ci,medium] + timeout-minutes: 1380 + + name: ${{ matrix.network }} + + continue-on-error: true #Do not stop testing of matrix runs failed. As it decided during PR review - it required 50/50& Let's check it with false. + + strategy: + matrix: + include: + - network: sapphire + usage: "--sapphire" + - network: "opal" + usage: "" + - network: "quartz" + usage: "" + - network: "unique" + usage: "" + + steps: + + - 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 }} #Checking out head commit + + - name: Read .env file + uses: xom9ikk/dotenv@v2 + + - name: Generate ENV related extend file for docker-compose + uses: cuchi/jinja2-action@v1.2.0 + with: + template: .docker/docker-compose.tmp-dev.j2 + output_file: .docker/docker-compose.${{ matrix.network }}.yml + variables: | + RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} + NETWORK=${{ matrix.network }} + + - name: Show build configuration + run: cat .docker/docker-compose.${{ matrix.network }}.yml + + - name: Build the stack + run: docker-compose -f ".docker/docker-compose.${{ matrix.network }}.yml" up -d --build --remove-orphans + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install jq + run: sudo apt install jq -y + + - name: Run generate_types_package script + working-directory: tests + run: | + yarn install + /bin/bash ./scripts/wait_for_first_block.sh + git config --global user.name "Unique" + git config --global user.email github-actions@usetech.com + /bin/bash ./scripts/generate_types_package.sh --release ${{ matrix.usage }} --push + env: + RPC_URL: http://127.0.0.1:9933/ + + - name: Stop running containers + if: always() # run this step always + run: docker-compose -f ".docker/docker-compose.${{ matrix.network }}.yml" down + + - name: Remove builder cache + if: always() # run this step always + run: | + docker builder prune -f -a + docker system prune -f + docker image prune -f -a --- a/.github/workflows/schedule-trigger-for-develop-build.yml +++ b/.github/workflows/schedule-trigger-for-develop-build.yml @@ -1,8 +1,8 @@ name: schedule-trigger-for-develop-build on: # update the branch ci/develop-scheduler every night - schedule: - - cron: '0 1 * * *' + # schedule: + # - cron: '0 1 * * *' # or update the branch manually workflow_dispatch: # pull_request: --- a/Cargo.lock +++ b/Cargo.lock @@ -2467,7 +2467,7 @@ [[package]] name = "fc-consensus" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "async-trait", "fc-db", @@ -2486,7 +2486,7 @@ [[package]] name = "fc-db" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "fp-storage", "kvdb-rocksdb", @@ -2505,7 +2505,7 @@ [[package]] name = "fc-mapping-sync" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "fc-db", "fp-consensus", @@ -2522,7 +2522,7 @@ [[package]] name = "fc-rpc" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "ethereum 0.14.0", "ethereum-types 0.14.1", @@ -2565,7 +2565,7 @@ [[package]] name = "fc-rpc-core" version = "1.1.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "ethereum 0.14.0", "ethereum-types 0.14.1", @@ -2730,7 +2730,7 @@ [[package]] name = "fp-consensus" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "ethereum 0.14.0", "parity-scale-codec 3.2.1", @@ -2742,7 +2742,7 @@ [[package]] name = "fp-ethereum" version = "1.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "ethereum 0.14.0", "ethereum-types 0.14.1", @@ -2757,7 +2757,7 @@ [[package]] name = "fp-evm" version = "3.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "evm", "frame-support", @@ -2771,7 +2771,7 @@ [[package]] name = "fp-evm-mapping" version = "0.1.0" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "frame-support", "sp-core", @@ -2780,7 +2780,7 @@ [[package]] name = "fp-rpc" version = "3.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "ethereum 0.14.0", "ethereum-types 0.14.1", @@ -2797,7 +2797,7 @@ [[package]] name = "fp-self-contained" version = "1.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "ethereum 0.14.0", "frame-support", @@ -2810,7 +2810,7 @@ [[package]] name = "fp-storage" version = "2.0.0" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "parity-scale-codec 3.2.1", "serde", @@ -5682,7 +5682,7 @@ [[package]] name = "pallet-base-fee" version = "1.0.0" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "fp-evm", "frame-support", @@ -5918,7 +5918,7 @@ [[package]] name = "pallet-ethereum" version = "4.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "ethereum 0.14.0", "ethereum-types 0.14.1", @@ -5946,7 +5946,7 @@ [[package]] name = "pallet-evm" version = "6.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "environmental", "evm", @@ -6032,7 +6032,7 @@ [[package]] name = "pallet-evm-precompile-simple" version = "2.0.0-dev" -source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#db60b911813df1d82e60e0de7f3dfa290e60d2f0" +source = "git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.36#cf1894629c7df1c4dafe58aa773627a3d940da14" dependencies = [ "fp-evm", "ripemd", @@ -12965,6 +12965,7 @@ "sp-core", "sp-finality-grandpa", "sp-inherents", + "sp-io", "sp-keystore", "sp-offchain", "sp-runtime", --- a/crates/evm-coder/procedural/src/abi_derive/derive_enum.rs +++ b/crates/evm-coder/procedural/src/abi_derive/derive_enum.rs @@ -143,8 +143,7 @@ ) } -pub fn check_and_count_options(de: &syn::DataEnum) -> syn::Result { - let mut count = 0; +pub fn check_enum_fields(de: &syn::DataEnum) -> syn::Result<()> { for v in de.variants.iter() { if !v.fields.is_empty() { return Err(syn::Error::new( @@ -156,12 +155,10 @@ v.ident.span(), "Enumeration options should not have an explicit specified value", )); - } else { - count += 1; } } - Ok(count) + Ok(()) } pub fn check_repr_u8(name: &syn::Ident, attrs: &[syn::Attribute]) -> syn::Result<()> { --- a/crates/evm-coder/procedural/src/abi_derive/mod.rs +++ b/crates/evm-coder/procedural/src/abi_derive/mod.rs @@ -70,6 +70,7 @@ ) -> syn::Result { let name = &ast.ident; check_repr_u8(name, &ast.attrs)?; + check_enum_fields(de)?; let docs = extract_docs(&ast.attrs)?; let enum_options = de.variants.iter(); --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -122,6 +122,10 @@ git = "https://github.com/paritytech/substrate" branch = "polkadot-v0.9.36" +[dependencies.sp-io] +git = "https://github.com/paritytech/substrate" +branch = "polkadot-v0.9.36" + [dependencies.sp-finality-grandpa] git = "https://github.com/paritytech/substrate" branch = "polkadot-v0.9.36" --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -412,6 +412,7 @@ #[cfg(feature = "try-runtime")] Some(Subcommand::TryRuntime(cmd)) => { use std::{future::Future, pin::Pin}; + use sc_executor::{sp_wasm_interface::ExtendedHostFunctions, NativeExecutionDispatch}; let runner = cli.create_runner(cmd)?; @@ -429,12 +430,21 @@ Ok(( match config.chain_spec.runtime_id() { #[cfg(feature = "unique-runtime")] - RuntimeId::Unique => Box::pin(cmd.run::(config)), + RuntimeId::Unique => Box::pin(cmd.run::::ExtendHostFunctions, + >>()), #[cfg(feature = "quartz-runtime")] - RuntimeId::Quartz => Box::pin(cmd.run::(config)), + RuntimeId::Quartz => Box::pin(cmd.run::::ExtendHostFunctions, + >>()), - RuntimeId::Opal => Box::pin(cmd.run::(config)), + RuntimeId::Opal => Box::pin(cmd.run::::ExtendHostFunctions, + >>()), RuntimeId::Unknown(chain) => return Err(no_runtime_err!(chain).into()), }, task_manager, --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -106,7 +106,12 @@ #[cfg(feature = "unique-runtime")] impl NativeExecutionDispatch for UniqueRuntimeExecutor { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); fn dispatch(method: &str, data: &[u8]) -> Option> { unique_runtime::api::dispatch(method, data) @@ -119,7 +124,12 @@ #[cfg(feature = "quartz-runtime")] impl NativeExecutionDispatch for QuartzRuntimeExecutor { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); fn dispatch(method: &str, data: &[u8]) -> Option> { quartz_runtime::api::dispatch(method, data) @@ -131,7 +141,12 @@ } impl NativeExecutionDispatch for OpalRuntimeExecutor { + /// Only enable the benchmarking host functions when we actually want to benchmark. + #[cfg(feature = "runtime-benchmarks")] type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + /// Otherwise we only use the default Substrate host functions. + #[cfg(not(feature = "runtime-benchmarks"))] + type ExtendHostFunctions = (); fn dispatch(method: &str, data: &[u8]) -> Option> { opal_runtime::api::dispatch(method, data) --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -127,7 +127,7 @@ pub struct OptionCrossAddress { /// Whether or not this CrossAdress is valid and has meaning. pub status: bool, - /// The underlying CrossAddress. If the status is false, can be set to whatever. + /// The underlying CrossAddress value. If the status is false, can be set to whatever. pub value: CrossAddress, } --- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol +++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol @@ -283,6 +283,8 @@ /// Ethereum representation of Optional value with CrossAddress. struct OptionCrossAddress { + /// TODO: field description bool status; + /// TODO: field description CrossAddress value; } --- a/pallets/nonfungible/src/benchmarking.rs +++ b/pallets/nonfungible/src/benchmarking.rs @@ -228,7 +228,7 @@ owner: sub; collection: collection(owner); owner: cross_from_sub; operator: cross_sub; }; - }: {>::set_allowance_for_all(&collection, &owner, &operator, true)} + }: {>::set_allowance_for_all(&collection, &owner, &operator, true)?} allowance_for_all { bench_init!{ --- a/pallets/refungible/src/benchmarking.rs +++ b/pallets/refungible/src/benchmarking.rs @@ -22,21 +22,17 @@ use frame_benchmarking::{benchmarks, account}; use pallet_common::{ bench_init, - benchmarking::{create_collection_raw, property_key, property_value, create_data}, + benchmarking::{create_collection_raw, property_key, property_value}, }; -use sp_core::H160; use sp_std::prelude::*; -use up_data_structs::{ - CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, CUSTOM_DATA_LIMIT, - budget::Unlimited, -}; +use up_data_structs::{CollectionMode, MAX_ITEMS_PER_BATCH, MAX_PROPERTIES_PER_ITEM, budget::Unlimited}; const SEED: u32 = 1; -fn create_max_item_data( - users: impl IntoIterator, -) -> CreateItemData { - CreateItemData { +fn create_max_item_data( + users: impl IntoIterator, +) -> CreateItemData { + CreateItemData:: { users: users .into_iter() .collect::>() @@ -51,7 +47,7 @@ sender: &T::CrossAccountId, users: impl IntoIterator, ) -> Result { - let data: CreateItemData = create_max_item_data(users); + let data: CreateItemData = create_max_item_data::(users); >::create_item(&collection, sender, data, &Unlimited)?; Ok(TokenId(>::get(&collection.id))) } @@ -83,7 +79,7 @@ owner: sub; collection: collection(owner); sender: cross_from_sub(owner); to: cross_sub; }; - let data = (0..b).map(|_| create_max_item_data([(to.clone(), 200)])).collect(); + let data = (0..b).map(|_| create_max_item_data::([(to.clone(), 200)])).collect(); }: {>::create_multiple_items(&collection, &sender, data, &Unlimited)?} create_multiple_items_ex_multiple_items { @@ -94,7 +90,7 @@ }; let data = (0..b).map(|t| { bench_init!(to: cross_sub(t);); - create_max_item_data([(to, 200)]) + create_max_item_data::([(to, 200)]) }).collect(); }: {>::create_multiple_items(&collection, &sender, data, &Unlimited)?} @@ -104,7 +100,7 @@ owner: sub; collection: collection(owner); sender: cross_from_sub(owner); }; - let data = vec![create_max_item_data((0..b).map(|u| { + let data = vec![create_max_item_data::((0..b).map(|u| { bench_init!(to: cross_sub(u);); (to, 200) }))].try_into().unwrap(); @@ -296,7 +292,7 @@ owner: sub; collection: collection(owner); owner: cross_from_sub; operator: cross_sub; }; - }: {>::set_allowance_for_all(&collection, &owner, &operator, true)} + }: {>::set_allowance_for_all(&collection, &owner, &operator, true)?} allowance_for_all { bench_init!{ --- a/pallets/refungible/src/erc_token.rs +++ b/pallets/refungible/src/erc_token.rs @@ -43,6 +43,9 @@ TotalSupply, weights::WeightInfo, }; +/// Refungible token handle contains information about token's collection and id +/// +/// RefungibleTokenHandle doesn't check token's existance upon creation pub struct RefungibleTokenHandle(pub RefungibleHandle, pub TokenId); #[solidity_interface(name = ERC1633)] --- a/runtime/common/ethereum/sponsoring.rs +++ b/runtime/common/ethereum/sponsoring.rs @@ -32,14 +32,22 @@ Config as FungibleConfig, erc::{UniqueFungibleCall, ERC20Call}, }; -use pallet_refungible::Config as RefungibleConfig; +use pallet_refungible::{ + Config as RefungibleConfig, + erc::UniqueRefungibleCall, + erc_token::{RefungibleTokenHandle, UniqueRefungibleTokenCall}, + RefungibleHandle, +}; use pallet_unique::Config as UniqueConfig; use sp_std::prelude::*; -use up_data_structs::{CollectionMode, CreateItemData, CreateNftData, TokenId}; +use up_data_structs::{ + CollectionMode, CreateItemData, CreateNftData, mapping::TokenAddressMapping, TokenId, +}; use up_sponsorship::SponsorshipHandler; - use crate::{Runtime, runtime_common::sponsoring::*}; +mod refungible; + pub type EvmSponsorshipHandler = ( UniqueEthSponsorshipHandler, pallet_evm_contract_helpers::HelpersContractSponsoring, @@ -53,80 +61,163 @@ who: &T::CrossAccountId, call_context: &CallContext, ) -> Option { - let collection_id = map_eth_to_id(&call_context.contract_address)?; - let collection = >::new(collection_id)?; - let sponsor = collection.sponsorship.sponsor()?.clone(); - let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?; - Some(T::CrossAccountId::from_sub(match &collection.mode { - CollectionMode::NFT => { - let call = >::parse(method_id, &mut reader).ok()??; - match call { - UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty { - token_id, - key, - value, - .. - }) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_set_token_property::( + if let Some(collection_id) = map_eth_to_id(&call_context.contract_address) { + let collection = >::new(collection_id)?; + let sponsor = collection.sponsorship.sponsor()?.clone(); + let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?; + Some(T::CrossAccountId::from_sub(match &collection.mode { + CollectionMode::NFT => { + let call = >::parse(method_id, &mut reader).ok()??; + match call { + UniqueNFTCall::TokenProperties(TokenPropertiesCall::SetProperty { + token_id, + key, + value, + .. + }) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_set_token_property::( + &collection, + &who, + &token_id, + key.len() + value.len(), + ) + .map(|()| sponsor) + } + UniqueNFTCall::ERC721UniqueExtensions( + ERC721UniqueExtensionsCall::Transfer { token_id, .. }, + ) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_transfer::(&collection, &who, &token_id).map(|()| sponsor) + } + UniqueNFTCall::ERC721UniqueMintable( + ERC721UniqueMintableCall::Mint { .. } + | ERC721UniqueMintableCall::MintCheckId { .. } + | ERC721UniqueMintableCall::MintWithTokenUri { .. } + | ERC721UniqueMintableCall::MintWithTokenUriCheckId { .. }, + ) => withdraw_create_item::( &collection, &who, - &token_id, - key.len() + value.len(), + &CreateItemData::NFT(CreateNftData::default()), ) - .map(|()| sponsor) - } - UniqueNFTCall::ERC721UniqueExtensions( - ERC721UniqueExtensionsCall::Transfer { token_id, .. }, - ) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_transfer::(&collection, &who, &token_id).map(|()| sponsor) + .map(|()| sponsor), + UniqueNFTCall::ERC721(ERC721Call::TransferFrom { + token_id, from, .. + }) => { + let token_id: TokenId = token_id.try_into().ok()?; + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&collection, &from, &token_id).map(|()| sponsor) + } + UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => { + let token_id: TokenId = token_id.try_into().ok()?; + withdraw_approve::(&collection, who.as_sub(), &token_id) + .map(|()| sponsor) + } + _ => None, } - UniqueNFTCall::ERC721UniqueMintable( - ERC721UniqueMintableCall::Mint { .. } - | ERC721UniqueMintableCall::MintCheckId { .. } - | ERC721UniqueMintableCall::MintWithTokenUri { .. } - | ERC721UniqueMintableCall::MintWithTokenUriCheckId { .. }, - ) => withdraw_create_item::( - &collection, - &who, - &CreateItemData::NFT(CreateNftData::default()), - ) - .map(|()| sponsor), - UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, from, .. }) => { - let token_id: TokenId = token_id.try_into().ok()?; - let from = T::CrossAccountId::from_eth(from); - withdraw_transfer::(&collection, &from, &token_id).map(|()| sponsor) - } - UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => { - let token_id: TokenId = token_id.try_into().ok()?; - withdraw_approve::(&collection, who.as_sub(), &token_id) - .map(|()| sponsor) - } - _ => None, } - } - CollectionMode::Fungible(_) => { - let call = >::parse(method_id, &mut reader).ok()??; - #[allow(clippy::single_match)] - match call { - UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => { - withdraw_transfer::(&collection, who, &TokenId::default()) - .map(|()| sponsor) - } - UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => { - let from = T::CrossAccountId::from_eth(from); - withdraw_transfer::(&collection, &from, &TokenId::default()) - .map(|()| sponsor) + CollectionMode::ReFungible => { + let call = >::parse(method_id, &mut reader).ok()??; + refungible::call_sponsor(call, collection, who).map(|()| sponsor) + } + CollectionMode::Fungible(_) => { + let call = >::parse(method_id, &mut reader).ok()??; + match call { + UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => { + withdraw_transfer::(&collection, who, &TokenId::default()) + .map(|()| sponsor) + } + UniqueFungibleCall::ERC20(ERC20Call::TransferFrom { from, .. }) => { + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&collection, &from, &TokenId::default()) + .map(|()| sponsor) + } + UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => { + withdraw_approve::(&collection, who.as_sub(), &TokenId::default()) + .map(|()| sponsor) + } + _ => None, } - UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => { - withdraw_approve::(&collection, who.as_sub(), &TokenId::default()) - .map(|()| sponsor) - } - _ => None, } + }?)) + } else { + let (collection_id, token_id) = + T::EvmTokenAddressMapping::address_to_token(&call_context.contract_address)?; + let collection = >::new(collection_id)?; + if collection.mode != CollectionMode::ReFungible { + return None; } - _ => None, - }?)) + let sponsor = collection.sponsorship.sponsor()?.clone(); + let rft_collection = RefungibleHandle::cast(collection); + // Token existance isn't checked at this point and should be checked in `withdraw` method. + let token = RefungibleTokenHandle(rft_collection, token_id); + + let (method_id, mut reader) = AbiReader::new_call(&call_context.input).ok()?; + let call = >::parse(method_id, &mut reader).ok()??; + Some(T::CrossAccountId::from_sub( + refungible::token_call_sponsor(call, token, who).map(|()| sponsor)?, + )) + } + } +} + +mod common { + use super::*; + + use pallet_common::erc::{CollectionCall}; + + pub fn collection_call_sponsor( + call: CollectionCall, + _collection: CollectionHandle, + _who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use CollectionCall::*; + + match call { + // Readonly + ERC165Call(_, _) + | CollectionProperty { .. } + | CollectionProperties { .. } + | HasCollectionPendingSponsor + | CollectionSponsor + | ContractAddress + | AllowlistedCross { .. } + | IsOwnerOrAdminEth { .. } + | IsOwnerOrAdminCross { .. } + | CollectionOwner + | CollectionAdmins + | CollectionLimits + | CollectionNestingRestrictedIds + | CollectionNestingPermissions + | UniqueCollectionType => None, + + // Not sponsored + AddToCollectionAllowList { .. } + | AddToCollectionAllowListCross { .. } + | RemoveFromCollectionAllowList { .. } + | RemoveFromCollectionAllowListCross { .. } + | AddCollectionAdminCross { .. } + | RemoveCollectionAdminCross { .. } + | AddCollectionAdmin { .. } + | RemoveCollectionAdmin { .. } + | SetNestingBool { .. } + | SetNesting { .. } + | SetCollectionAccess { .. } + | SetCollectionMintMode { .. } + | SetOwner { .. } + | ChangeCollectionOwnerCross { .. } + | SetCollectionProperty { .. } + | SetCollectionProperties { .. } + | DeleteCollectionProperty { .. } + | DeleteCollectionProperties { .. } + | SetCollectionSponsor { .. } + | SetCollectionSponsorCross { .. } + | SetCollectionLimit { .. } + | ConfirmCollectionSponsorship + | RemoveCollectionSponsor => None, + } } } --- /dev/null +++ b/runtime/common/ethereum/sponsoring/refungible.rs @@ -0,0 +1,381 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +//! Implements EVM sponsoring logic via TransactionValidityHack + +use core::convert::TryInto; +use pallet_common::CollectionHandle; +use pallet_evm::account::CrossAccountId; +use pallet_fungible::Config as FungibleConfig; +use pallet_refungible::Config as RefungibleConfig; +use pallet_nonfungible::Config as NonfungibleConfig; +use pallet_unique::Config as UniqueConfig; +use up_data_structs::{CreateItemData, CreateNftData, TokenId}; + +use super::common; +use crate::runtime_common::sponsoring::*; + +use pallet_refungible::{ + erc::{ + ERC721BurnableCall, ERC721Call, ERC721EnumerableCall, ERC721MetadataCall, + ERC721UniqueExtensionsCall, ERC721UniqueMintableCall, TokenPropertiesCall, + UniqueRefungibleCall, + }, + erc_token::{ + ERC1633Call, ERC20Call, ERC20UniqueExtensionsCall, RefungibleTokenHandle, + UniqueRefungibleTokenCall, + }, +}; + +pub fn call_sponsor( + call: UniqueRefungibleCall, + collection: CollectionHandle, + who: &T::CrossAccountId, +) -> Option<()> +where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, +{ + use UniqueRefungibleCall::*; + + match call { + // Readonly + ERC165Call(_, _) => None, + + ERC721Enumerable(call) => erc721::enumerable_call_sponsor(call, collection, who), + ERC721Burnable(call) => erc721::burnable_call_sponsor(call, collection, who), + ERC721Metadata(call) => erc721::metadata_call_sponsor(call, collection, who), + Collection(call) => common::collection_call_sponsor(call, collection, who), + ERC721(call) => erc721::call_sponsor(call, collection, who), + ERC721UniqueExtensions(call) => { + erc721::unique_extensions_call_sponsor(call, collection, who) + } + ERC721UniqueMintable(call) => erc721::unique_mintable_call_sponsor(call, collection, who), + TokenProperties(call) => token_properties_call_sponsor(call, collection, who), + } +} + +pub fn token_properties_call_sponsor( + call: TokenPropertiesCall, + collection: CollectionHandle, + who: &T::CrossAccountId, +) -> Option<()> +where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, +{ + use TokenPropertiesCall::*; + + match call { + // Readonly + ERC165Call(_, _) | Property { .. } | TokenPropertyPermissions => None, + + // Not sponsored + SetTokenPropertyPermission { .. } + | SetTokenPropertyPermissions { .. } + | SetProperties { .. } + | DeleteProperty { .. } + | DeleteProperties { .. } => None, + + SetProperty { + token_id, + key, + value, + .. + } => { + let token_id = TokenId::try_from(token_id).ok()?; + withdraw_set_token_property::(&collection, &who, &token_id, key.len() + value.len()) + } + } +} + +pub fn token_call_sponsor( + call: UniqueRefungibleTokenCall, + token: RefungibleTokenHandle, + who: &T::CrossAccountId, +) -> Option<()> +where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, +{ + use UniqueRefungibleTokenCall::*; + + match call { + // Readonly + ERC165Call(_, _) => None, + + ERC20(call) => erc20::call_sponsor(call, token, who), + ERC20UniqueExtensions(call) => erc20::unique_extensions_call_sponsor(call, token, who), + ERC1633(call) => erc1633::call_sponsor(call, token, who), + } +} + +mod erc721 { + use super::*; + + pub fn call_sponsor( + call: ERC721Call, + collection: CollectionHandle, + _who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC721Call::*; + + match call { + // Readonly + ERC165Call(_, _) + | BalanceOf { .. } + | OwnerOf { .. } + | GetApproved { .. } + | IsApprovedForAll { .. } + | CollectionHelperAddress => None, + + // Not sponsored + SafeTransferFromWithData { .. } + | SafeTransferFrom { .. } + | SetApprovalForAll { .. } => None, + + TransferFrom { token_id, from, .. } => { + let token_id = TokenId::try_from(token_id).ok()?; + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&collection, &from, &token_id) + } + + // Not supported + Approve { .. } => None, + } + } + + pub fn enumerable_call_sponsor( + call: ERC721EnumerableCall, + _collection: CollectionHandle, + _who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC721EnumerableCall::*; + + match call { + // Readonly + ERC165Call(_, _) | TokenByIndex { .. } | TokenOfOwnerByIndex { .. } | TotalSupply => { + None + } + } + } + + pub fn burnable_call_sponsor( + call: ERC721BurnableCall, + _collection: CollectionHandle, + _who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC721BurnableCall::*; + + match call { + // Readonly + ERC165Call(_, _) => None, + + // Not sponsored + Burn { .. } => None, + } + } + + pub fn metadata_call_sponsor( + call: ERC721MetadataCall, + _collection: CollectionHandle, + _who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC721MetadataCall::*; + + match call { + // Readonly + ERC165Call(_, _) | NameProxy | SymbolProxy | TokenUri { .. } => None, + } + } + + pub fn unique_extensions_call_sponsor( + call: ERC721UniqueExtensionsCall, + collection: CollectionHandle, + who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC721UniqueExtensionsCall::*; + + match call { + // Readonly + ERC165Call(_, _) + | Name + | Symbol + | Description + | CrossOwnerOf { .. } + | Properties { .. } + | NextTokenId + | TokenContractAddress { .. } => None, + + // Not sponsored + BurnFrom { .. } + | BurnFromCross { .. } + | MintBulk { .. } + | MintBulkWithTokenUri { .. } => None, + + MintCross { .. } => withdraw_create_item::( + &collection, + &who, + &CreateItemData::NFT(CreateNftData::default()), + ), + + TransferCross { token_id, .. } + | TransferFromCross { token_id, .. } + | Transfer { token_id, .. } => { + let token_id = TokenId::try_from(token_id).ok()?; + withdraw_transfer::(&collection, &who, &token_id) + } + } + } + + pub fn unique_mintable_call_sponsor( + call: ERC721UniqueMintableCall, + collection: CollectionHandle, + who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC721UniqueMintableCall::*; + + match call { + // Readonly + ERC165Call(_, _) | MintingFinished => None, + + // Not sponsored + FinishMinting => None, + + Mint { .. } + | MintCheckId { .. } + | MintWithTokenUri { .. } + | MintWithTokenUriCheckId { .. } => withdraw_create_item::( + &collection, + &who, + &CreateItemData::NFT(CreateNftData::default()), + ), + } + } +} + +/// Module for methods of refungible token +/// +/// Existance of token should be checked before searching for sponsor +/// because RefungibleTokenHandle doesn't check token's existence upon creation +mod erc20 { + use super::*; + + pub fn call_sponsor( + call: ERC20Call, + token: RefungibleTokenHandle, + who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC20Call::*; + + match call { + // Readonly + ERC165Call(_, _) + | Name + | Symbol + | TotalSupply + | Decimals + | BalanceOf { .. } + | Allowance { .. } => None, + + Transfer { .. } => { + let RefungibleTokenHandle(handle, token_id) = token; + let token_id = token_id.try_into().ok()?; + withdraw_transfer::(&handle, &who, &token_id) + } + TransferFrom { from, .. } => { + let RefungibleTokenHandle(handle, token_id) = token; + let token_id = token_id.try_into().ok()?; + let from = T::CrossAccountId::from_eth(from); + withdraw_transfer::(&handle, &from, &token_id) + } + Approve { .. } => { + let RefungibleTokenHandle(handle, token_id) = token; + let token_id = token_id.try_into().ok()?; + withdraw_approve::(&handle, who.as_sub(), &token_id) + } + } + } + + pub fn unique_extensions_call_sponsor( + call: ERC20UniqueExtensionsCall, + token: RefungibleTokenHandle, + who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC20UniqueExtensionsCall::*; + + match call { + // Readonly + ERC165Call(_, _) => None, + + // Not sponsored + BurnFrom { .. } | BurnFromCross { .. } | Repartition { .. } => None, + + TransferCross { .. } | TransferFromCross { .. } => { + let RefungibleTokenHandle(handle, token_id) = token; + let token_id = token_id.try_into().ok()?; + withdraw_transfer::(&handle, &who, &token_id) + } + + ApproveCross { .. } => { + let RefungibleTokenHandle(handle, token_id) = token; + let token_id = token_id.try_into().ok()?; + withdraw_approve::(&handle, who.as_sub(), &token_id) + } + } + } +} + +mod erc1633 { + use super::*; + + pub fn call_sponsor( + call: ERC1633Call, + _token: RefungibleTokenHandle, + _who: &T::CrossAccountId, + ) -> Option<()> + where + T: UniqueConfig + FungibleConfig + NonfungibleConfig + RefungibleConfig, + { + use ERC1633Call::*; + + match call { + // Readonly + ERC165Call(_, _) | ParentToken | ParentTokenId => None, + } + } +} --- a/runtime/common/runtime_apis.rs +++ b/runtime/common/runtime_apis.rs @@ -780,15 +780,16 @@ #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade() -> (frame_support::pallet_prelude::Weight, frame_support::pallet_prelude::Weight) { + fn on_runtime_upgrade(checks: bool) -> (frame_support::pallet_prelude::Weight, frame_support::pallet_prelude::Weight) { log::info!("try-runtime::on_runtime_upgrade unique-chain."); - let weight = Executive::try_runtime_upgrade().unwrap(); + let weight = Executive::try_runtime_upgrade(checks).unwrap(); (weight, crate::config::substrate::RuntimeBlockWeights::get().max_block) } fn execute_block( block: Block, state_root_check: bool, + signature_check: bool, select: frame_try_runtime::TryStateSelect ) -> frame_support::pallet_prelude::Weight { log::info!( @@ -799,7 +800,7 @@ select, ); - Executive::try_execute_block(block, state_root_check, select).unwrap() + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() } } } --- a/runtime/opal/Cargo.toml +++ b/runtime/opal/Cargo.toml @@ -91,6 +91,7 @@ 'pallet-unique-scheduler-v2/try-runtime', 'pallet-maintenance/try-runtime', 'pallet-test-utils?/try-runtime', + 'fp-self-contained/try-runtime', ] std = [ 'codec/std', --- a/runtime/quartz/Cargo.toml +++ b/runtime/quartz/Cargo.toml @@ -86,6 +86,7 @@ 'pallet-evm-transaction-payment/try-runtime', 'pallet-evm-migration/try-runtime', 'pallet-maintenance/try-runtime', + 'fp-self-contained/try-runtime', ] std = [ 'codec/std', --- a/runtime/unique/Cargo.toml +++ b/runtime/unique/Cargo.toml @@ -87,6 +87,7 @@ 'pallet-evm-transaction-payment/try-runtime', 'pallet-evm-migration/try-runtime', 'pallet-maintenance/try-runtime', + 'fp-self-contained/try-runtime', ] std = [ 'codec/std', --- a/tests/.eslintrc.json +++ b/tests/.eslintrc.json @@ -18,6 +18,9 @@ "mocha" ], "rules": { + "@typescript-eslint/no-floating-promises": [ + "error" + ], "indent": [ "error", 2, --- a/tests/package.json +++ b/tests/package.json @@ -11,8 +11,8 @@ "@types/chai-subset": "^1.3.3", "@types/mocha": "^10.0.0", "@types/node": "^18.11.2", - "@typescript-eslint/eslint-plugin": "^5.40.1", - "@typescript-eslint/parser": "^5.40.1", + "@typescript-eslint/eslint-plugin": "^5.47.0", + "@typescript-eslint/parser": "^5.47.0", "chai": "^4.3.6", "chai-subset": "^1.6.0", "eslint": "^8.25.0", --- a/tests/src/apiConsts.test.ts +++ b/tests/src/apiConsts.test.ts @@ -46,33 +46,33 @@ describe('integration test: API UNIQUE consts', () => { let api: ApiPromise; - + before(async () => { await usingPlaygrounds(async (helper) => { api = await helper.getApi(); }); }); - + itSub('DEFAULT_NFT_COLLECTION_LIMITS', () => { expect(api.consts.unique.nftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT); }); - + itSub('DEFAULT_RFT_COLLECTION_LIMITS', () => { expect(api.consts.unique.rftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT); }); - + itSub('DEFAULT_FT_COLLECTION_LIMITS', () => { expect(api.consts.unique.ftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT); }); - + itSub('MAX_COLLECTION_NAME_LENGTH', () => { checkConst(api.consts.unique.maxCollectionNameLength, MAX_COLLECTION_NAME_LENGTH); }); - + itSub('MAX_COLLECTION_DESCRIPTION_LENGTH', () => { checkConst(api.consts.unique.maxCollectionDescriptionLength, MAX_COLLECTION_DESCRIPTION_LENGTH); }); - + itSub('MAX_COLLECTION_PROPERTIES_SIZE', () => { checkConst(api.consts.unique.maxCollectionPropertiesSize, MAX_COLLECTION_PROPERTIES_SIZE); }); @@ -84,31 +84,31 @@ itSub('MAX_PROPERTY_KEY_LENGTH', () => { checkConst(api.consts.unique.maxPropertyKeyLength, MAX_PROPERTY_KEY_LENGTH); }); - + itSub('MAX_PROPERTY_VALUE_LENGTH', () => { checkConst(api.consts.unique.maxPropertyValueLength, MAX_PROPERTY_VALUE_LENGTH); }); - + itSub('MAX_PROPERTIES_PER_ITEM', () => { checkConst(api.consts.unique.maxPropertiesPerItem, MAX_PROPERTIES_PER_ITEM); }); - + itSub('NESTING_BUDGET', () => { checkConst(api.consts.unique.nestingBudget, NESTING_BUDGET); }); - + itSub('MAX_TOKEN_PROPERTIES_SIZE', () => { checkConst(api.consts.unique.maxTokenPropertiesSize, MAX_TOKEN_PROPERTIES_SIZE); }); - + itSub('COLLECTION_ADMINS_LIMIT', () => { checkConst(api.consts.unique.collectionAdminsLimit, COLLECTION_ADMINS_LIMIT); }); - + itSub('HELPERS_CONTRACT_ADDRESS', () => { expect(api.consts.evmContractHelpers.contractAddress.toString().toLowerCase()).to.be.equal(HELPERS_CONTRACT_ADDRESS.toLowerCase()); }); - + itSub('EVM_COLLECTION_HELPERS_ADDRESS', () => { expect(api.consts.common.contractAddress.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS.toLowerCase()); }); --- a/tests/src/app-promotion.seqtest.ts +++ b/tests/src/app-promotion.seqtest.ts @@ -50,31 +50,31 @@ await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected; await expect(helper.signTransaction(nonAdmin, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected; }); - + itSub('can be any valid CrossAccountId', async ({helper}) => { // We are not going to set an eth address as a sponsor, // but we do want to check, it doesn't break anything; const api = helper.getApi(); const [account] = await helper.arrange.createAccounts([10n], donor); - const ethAccount = helper.address.substrateToEth(account.address); + const ethAccount = helper.address.substrateToEth(account.address); // Alice sets Ethereum address as a sudo. Then Substrate address back... await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled; await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; - + // ...It doesn't break anything; const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); await expect(helper.signTransaction(account, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; }); - + itSub('can be reassigned', async ({helper}) => { const api = helper.getApi(); const [oldAdmin, newAdmin, collectionOwner] = await helper.arrange.createAccounts([10n, 10n, 10n], donor); const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); - + await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: oldAdmin.address})))).to.be.fulfilled; await expect(helper.signTransaction(superuser, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: newAdmin.address})))).to.be.fulfilled; await expect(helper.signTransaction(oldAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; - + await expect(helper.signTransaction(newAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled; }); }); --- a/tests/src/approve.test.ts +++ b/tests/src/approve.test.ts @@ -538,7 +538,7 @@ const approveTx = () => helper.rft.approveToken(alice, collectionId, 1, {Substrate: bob.address}); await expect(approveTx()).to.be.rejected; }); - + itSub('[nft] Approve transfer of a token that does not exist', async ({helper}) => { const {collectionId} = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); const approveTx = () => helper.nft.approveToken(alice, collectionId, 2, {Substrate: bob.address}); @@ -639,7 +639,7 @@ await helper.rft.setAllowanceForAll(alice, collectionId, {Substrate: bob.address}, true); const checkAfterApproval = await helper.rft.allowanceForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address}); expect(checkAfterApproval).to.be.true; - + await helper.rft.setAllowanceForAll(alice, collectionId, {Substrate: bob.address}, false); const checkAfterDisapproval = await helper.rft.allowanceForAll(collectionId, {Substrate: alice.address}, {Substrate: bob.address}); expect(checkAfterDisapproval).to.be.false; --- a/tests/src/burnItem.test.ts +++ b/tests/src/burnItem.test.ts @@ -185,7 +185,7 @@ const collection = await helper.nft.mintCollection(alice, {name: 'Coll', description: 'Desc', tokenPrefix: 'T'}); const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address}); const tokenBob = await collection.mintToken(alice, {Substrate: bob.address}); - + // 1. Zero burn of own tokens allowed: await helper.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, tokenAlice.tokenId, 0]); // 2. Zero burn of non-owned tokens not allowed: --- a/tests/src/calibrate.ts +++ b/tests/src/calibrate.ts @@ -295,6 +295,7 @@ } } +// eslint-disable-next-line @typescript-eslint/no-floating-promises (async () => { await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => { // Subsequent runs reduce error, as price line is not actually straight, this is a curve --- a/tests/src/createCollection.test.ts +++ b/tests/src/createCollection.test.ts @@ -127,7 +127,7 @@ const mintCollectionTx = () => helper.nft.mintCollection(alice, {name: 'name', description: 'descr', tokenPrefix: 'A'.repeat(17)}); await expect(mintCollectionTx()).to.be.rejectedWith('Verification Error'); }); - + itSub('(!negative test!) fails when bad limits are set', async ({helper}) => { const mintCollectionTx = () => helper.nft.mintCollection(alice, {name: 'name', description: 'descr', tokenPrefix: 'COL', limits: {tokenLimit: 0}}); await expect(mintCollectionTx()).to.be.rejectedWith(/common\.CollectionTokenLimitExceeded/); --- a/tests/src/createMultipleItems.test.ts +++ b/tests/src/createMultipleItems.test.ts @@ -272,8 +272,8 @@ const types = ['NFT', 'Fungible', 'ReFungible']; await expect(helper.executeExtrinsic( - alice, - 'api.tx.unique.createMultipleItems', + alice, + 'api.tx.unique.createMultipleItems', [collectionId, {Substrate: alice.address}, types], )).to.be.rejectedWith(/nonfungible\.NotNonfungibleDataUsedToMintFungibleCollectionToken/); }); --- a/tests/src/eth/abi/reFungibleDeprecated.json +++ b/tests/src/eth/abi/reFungibleDeprecated.json @@ -98,5 +98,33 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256[]", "name": "tokenIds", "type": "uint256[]" } + ], + "name": "mintBulk", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { + "components": [ + { "internalType": "uint256", "name": "field_0", "type": "uint256" }, + { "internalType": "string", "name": "field_1", "type": "string" } + ], + "internalType": "struct Tuple0[]", + "name": "tokens", + "type": "tuple[]" + } + ], + "name": "mintBulkWithTokenURI", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" } ] --- a/tests/src/eth/api/ContractHelpers.sol +++ b/tests/src/eth/api/ContractHelpers.sol @@ -183,7 +183,9 @@ /// Ethereum representation of Optional value with CrossAddress. struct OptionCrossAddress { + /// TODO: field description bool status; + /// TODO: field description CrossAddress value; } --- a/tests/src/eth/collectionSponsoring.test.ts +++ b/tests/src/eth/collectionSponsoring.test.ts @@ -15,10 +15,10 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import {usingPlaygrounds} from '../util/index'; +import {Pallets, requirePalletsOrSkip, usingPlaygrounds} from '../util/index'; import {itEth, expect} from './util'; -describe('evm collection sponsoring', () => { +describe('evm nft collection sponsoring', () => { let donor: IKeyringPair; let alice: IKeyringPair; let nominal: bigint; @@ -101,12 +101,14 @@ expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true; await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + let sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner}); + expect(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; await collectionEvm.methods.removeCollectionSponsor().send({from: owner}); - const sponsorTuple = await collectionEvm.methods.collectionSponsor().call({from: owner}); - expect(sponsorTuple.eth).to.be.eq('0x0000000000000000000000000000000000000000'); + sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner}); + expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000'); })); [ @@ -142,7 +144,7 @@ expect(nextTokenId).to.be.equal('1'); // Set collection permissions: - const oldPermissions = (await collectionSub.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman(); + const oldPermissions = (await collectionSub.getData())!.raw.permissions; expect(oldPermissions.mintMode).to.be.false; expect(oldPermissions.access).to.be.equal('Normal'); @@ -150,12 +152,13 @@ await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); - const newPermissions = (await collectionSub.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman(); + const newPermissions = (await collectionSub.getData())!.raw.permissions; expect(newPermissions.mintMode).to.be.true; expect(newPermissions.access).to.be.equal('AllowList'); const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth)); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); // User can mint token without balance: { @@ -179,7 +182,7 @@ expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); - expect(userBalanceAfter).to.be.eq(0n); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true; } })); @@ -315,3 +318,531 @@ expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address}); }); }); + +describe('evm RFT collection sponsoring', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; + let nominal: bigint; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + donor = await privateKey({filename: __filename}); + [alice] = await helper.arrange.createAccounts([100n], donor); + nominal = helper.balance.getOneTokenNominal(); + }); + }); + + [ + 'mintCross', + 'mintWithTokenURI', + ].map(testCase => + itEth(`[${testCase}] sponsors mint transactions`, async ({helper}) => { + const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'spnr', permissions: {mintMode: true}, tokenPropertyPermissions: [ + {key: 'URI', permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}, + ]}); + + const owner = await helper.eth.createAccountWithBalance(donor); + await collection.setSponsor(alice, alice.address); + await collection.confirmSponsorship(alice); + + const minter = helper.eth.createAccount(); + const minterCross = helper.ethCrossAccount.fromAddress(minter); + expect(await helper.balance.getEthereum(minter)).to.equal(0n); + + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', minter, true); + + await collection.addToAllowList(alice, {Ethereum: minter}); + await collection.addAdmin(alice, {Ethereum: owner}); + const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner); + await collectionHelpers.methods.makeCollectionERC721MetadataCompatible(collectionAddress, 'base/') + .send(); + + let mintingResult; + let tokenId; + switch (testCase) { + case 'mintCross': + mintingResult = await contract.methods.mintCross(minterCross, []).send(); + break; + case 'mintWithTokenURI': + mintingResult = await contract.methods.mintWithTokenURI(minter, 'Test URI').send(); + tokenId = mintingResult.events.Transfer.returnValues.tokenId; + expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI'); + break; + } + + const events = helper.eth.normalizeEvents(mintingResult.events); + expect(events).to.deep.include({ + address: collectionAddress, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: minter, + tokenId: '1', + }, + }); + })); + + [ + 'setCollectionSponsorCross', + 'setCollectionSponsor', // Soft-deprecated + ].map(testCase => + itEth(`[${testCase}] can remove collection sponsor`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner); + + let result = await collectionHelpers.methods.createRFTCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)}); + const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); + const collectionEvm = await helper.ethNativeContract.collection(collectionIdAddress, 'rft', owner, testCase === 'setCollectionSponsor'); + + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + result = await collectionEvm.methods[testCase](testCase === 'setCollectionSponsor' ? sponsor : sponsorCross).send({from: owner}); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true; + + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + + await collectionEvm.methods.removeCollectionSponsor().send({from: owner}); + + const sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner}); + expect(sponsorStruct.eth).to.be.eq('0x0000000000000000000000000000000000000000'); + })); + + [ + 'setCollectionSponsorCross', + 'setCollectionSponsor', // Soft-deprecated + ].map(testCase => + itEth(`[${testCase}] Can sponsor from evm address via access list`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsorEth = await helper.eth.createAccountWithBalance(donor); + const sponsorCrossEth = helper.ethCrossAccount.fromAddress(sponsorEth); + + const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Sponsor collection', '1', '1', ''); + + const collectionSub = helper.rft.getCollectionObject(collectionId); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner, testCase === 'setCollectionSponsor'); + + // Set collection sponsor: + await collectionEvm.methods[testCase](testCase === 'setCollectionSponsor' ? sponsorEth : sponsorCrossEth).send({from: owner}); + let sponsorship = (await collectionSub.getData())!.raw.sponsorship; + expect(sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true)); + // Account cannot confirm sponsorship if it is not set as a sponsor + await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail'); + + // Sponsor can confirm sponsorship: + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth}); + sponsorship = (await collectionSub.getData())!.raw.sponsorship; + expect(sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsorEth, true)); + + // Create user with no balance: + const user = helper.eth.createAccount(); + const userCross = helper.ethCrossAccount.fromAddress(user); + const nextTokenId = await collectionEvm.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + + // Set collection permissions: + const oldPermissions = (await collectionSub.getData())!.raw.permissions; + expect(oldPermissions.mintMode).to.be.false; + expect(oldPermissions.access).to.be.equal('Normal'); + + await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner}); + await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); + await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); + + const newPermissions = (await collectionSub.getData())!.raw.permissions; + expect(newPermissions.mintMode).to.be.true; + expect(newPermissions.access).to.be.equal('AllowList'); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth)); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + // User can mint token without balance: + { + const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const events = helper.eth.normalizeEvents(result.events); + + expect(events).to.deep.include({ + address: collectionAddress, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: user, + tokenId: '1', + }, + }); + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsorEth)); + const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); + expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); + expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true; + } + })); + + [ + 'setCollectionSponsorCross', + 'setCollectionSponsor', // Soft-deprecated + ].map(testCase => + itEth(`[${testCase}] Check that collection admin EVM transaction spend money from sponsor eth address`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', ''); + + const collectionSub = helper.rft.getCollectionObject(collectionId); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner, testCase === 'setCollectionSponsor'); + // Set collection sponsor: + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call()).to.be.false; + await collectionEvm.methods[testCase](testCase === 'setCollectionSponsor' ? sponsor : sponsorCross).send(); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call()).to.be.true; + let collectionData = (await collectionSub.getData())!; + expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); + await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail'); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call()).to.be.true; + + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + collectionData = (await collectionSub.getData())!; + expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call()).to.be.false; + const sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner}); + const sponsorSubAddress = helper.address.normalizeSubstrateToChainFormat(helper.address.ethToSubstrate(sponsor)); + const actualSubAddress = helper.address.normalizeSubstrateToChainFormat(helper.address.restoreCrossAccountFromBigInt(BigInt(sponsorStruct.sub))); + expect(actualSubAddress).to.be.equal(sponsorSubAddress); + + const user = helper.eth.createAccount(); + const userCross = helper.ethCrossAccount.fromAddress(user); + await collectionEvm.methods.addCollectionAdminCross(userCross).send(); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + + const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const tokenId = mintingResult.events.Transfer.returnValues.tokenId; + + const events = helper.eth.normalizeEvents(mintingResult.events); + const address = helper.ethAddress.fromCollectionId(collectionId); + + expect(events).to.deep.include({ + address, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: user, + tokenId: '1', + }, + }); + expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI'); + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + })); + + itEth('Check that collection admin EVM transaction spend money from sponsor sub address', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = alice; + const sponsorCross = helper.ethCrossAccount.fromKeyringPair(sponsor); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', ''); + + const collectionSub = helper.rft.getCollectionObject(collectionId); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner, false); + // Set collection sponsor: + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call()).to.be.false; + await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send(); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call()).to.be.true; + let collectionData = (await collectionSub.getData())!; + expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(sponsor.address); + await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmSponsorshipFail'); + + await collectionSub.confirmSponsorship(sponsor); + collectionData = (await collectionSub.getData())!; + expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(sponsor.address); + expect(await collectionEvm.methods.hasCollectionPendingSponsor().call()).to.be.false; + const sponsorStruct = await collectionEvm.methods.collectionSponsor().call({from: owner}); + expect(BigInt(sponsorStruct.sub)).to.be.equal(BigInt('0x' + Buffer.from(sponsor.addressRaw).toString('hex'))); + + const user = helper.eth.createAccount(); + const userCross = helper.ethCrossAccount.fromAddress(user); + await collectionEvm.methods.addCollectionAdminCross(userCross).send(); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(sponsor.address); + + const mintingResult = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const tokenId = mintingResult.events.Transfer.returnValues.tokenId; + + const events = helper.eth.normalizeEvents(mintingResult.events); + + expect(events).to.deep.include({ + address: collectionAddress, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: user, + tokenId: '1', + }, + }); + expect(await collectionEvm.methods.tokenURI(tokenId).call({from: user})).to.be.equal('Test URI'); + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); + const sponsorBalanceAfter = await helper.balance.getSubstrate(sponsor.address); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + }); + + itEth('Sponsoring collection from substrate address via access list', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const user = helper.eth.createAccount(); + const userCross = helper.ethCrossAccount.fromAddress(user); + const sponsor = alice; + const sponsorCross = helper.ethCrossAccount.fromKeyringPair(sponsor); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', ''); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner, false); + + await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send({from: owner}); + + const collectionSub = helper.rft.getCollectionObject(collectionId); + await collectionSub.confirmSponsorship(sponsor); + + const nextTokenId = await collectionEvm.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + + await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner}); + + await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); + await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(sponsor.address); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + { + const nextTokenId = await collectionEvm.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + const mintingResult = await collectionEvm.methods.mintWithTokenURI( + user, + 'Test URI', + ).send({from: user}); + + const events = helper.eth.normalizeEvents(mintingResult.events); + + + expect(events).to.deep.include({ + address: collectionAddress, + event: 'Transfer', + args: { + from: '0x0000000000000000000000000000000000000000', + to: user, + tokenId: nextTokenId, + }, + }); + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceAfter = await helper.balance.getSubstrate(sponsor.address); + const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); + expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); + expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true; + } + }); + + itEth('Can reassign collection sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsorEth = await helper.eth.createAccountWithBalance(donor); + const sponsorCrossEth = helper.ethCrossAccount.fromAddress(sponsorEth); + const [sponsorSub] = await helper.arrange.createAccounts([100n], donor); + const sponsorCrossSub = helper.ethCrossAccount.fromKeyringPair(sponsorSub); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', ''); + const collectionSub = helper.rft.getCollectionObject(collectionId); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + + // Set and confirm sponsor: + await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossEth).send({from: owner}); + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsorEth}); + + // Can reassign sponsor: + await collectionEvm.methods.setCollectionSponsorCross(sponsorCrossSub).send({from: owner}); + const collectionSponsor = (await collectionSub.getData())?.raw.sponsorship; + expect(collectionSponsor).to.deep.eq({Unconfirmed: sponsorSub.address}); + }); + + [ + 'transfer', + 'transferCross', + 'transferFrom', + 'transferFromCross', + ].map(testCase => + itEth(`[${testCase}] Check that transfer via EVM spend money from sponsor address`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', ''); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); + const receiver = await helper.eth.createAccountWithBalance(donor); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + + await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send(); + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + + const user = await helper.eth.createAccountWithBalance(donor); + const userCross = helper.ethCrossAccount.fromAddress(user); + await collectionEvm.methods.addCollectionAdminCross(userCross).send(); + + const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const tokenId = result.events.Transfer.returnValues.tokenId; + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + switch (testCase) { + case 'transfer': + await collectionEvm.methods.transfer(receiver, tokenId).send({from: user}); + break; + case 'transferCross': + await collectionEvm.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user}); + break; + case 'transferFrom': + await collectionEvm.methods.transferFrom(user, receiver, tokenId).send({from: user}); + break; + case 'transferFromCross': + await collectionEvm.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), tokenId).send({from: user}); + break; + } + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); + })); +}); + +describe('evm RFT token sponsoring', () => { + let donor: IKeyringPair; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + donor = await privateKey({filename: __filename}); + }); + }); + + [ + 'transfer', + 'transferCross', + 'transferFrom', + 'transferFromCross', + ].map(testCase => + itEth(`[${testCase}] Check that token piece transfer via EVM spend money from sponsor address`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', ''); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); + const receiver = await helper.eth.createAccountWithBalance(donor); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + + await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send(); + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + + const user = await helper.eth.createAccountWithBalance(donor); + const userCross = helper.ethCrossAccount.fromAddress(user); + await collectionEvm.methods.addCollectionAdminCross(userCross).send(); + + const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const tokenId = result.events.Transfer.returnValues.tokenId; + + const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, user); + await tokenContract.methods.repartition(2).send(); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + switch (testCase) { + case 'transfer': + await tokenContract.methods.transfer(receiver, 1).send(); + break; + case 'transferCross': + await tokenContract.methods.transferCross(helper.ethCrossAccount.fromAddress(receiver), 1).send(); + break; + case 'transferFrom': + await tokenContract.methods.transferFrom(user, receiver, 1).send(); + break; + case 'transferFromCross': + await tokenContract.methods.transferFromCross(helper.ethCrossAccount.fromAddress(user), helper.ethCrossAccount.fromAddress(receiver), 1).send(); + break; + } + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); + })); + + [ + 'approve', + 'approveCross', + ].map(testCase => + itEth(`[${testCase}] Check that approve via EVM spend money from sponsor address`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner,'Sponsor collection', '1', '1', ''); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); + const receiver = await helper.eth.createAccountWithBalance(donor); + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + + await collectionEvm.methods.setCollectionSponsorCross(sponsorCross).send(); + await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); + + const user = await helper.eth.createAccountWithBalance(donor); + const userCross = helper.ethCrossAccount.fromAddress(user); + await collectionEvm.methods.addCollectionAdminCross(userCross).send(); + + const result = await collectionEvm.methods.mintWithTokenURI(user, 'Test URI').send({from: user}); + const tokenId = result.events.Transfer.returnValues.tokenId; + + const tokenContract = await helper.ethNativeContract.rftTokenById(collectionId, tokenId, user); + await tokenContract.methods.repartition(2).send(); + + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + const userBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + + switch (testCase) { + case 'approve': + await tokenContract.methods.approve(receiver, 1).send(); + break; + case 'approveCross': + await tokenContract.methods.approveCross(helper.ethCrossAccount.fromAddress(receiver), 1).send(); + break; + } + + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; + const userBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(user)); + expect(userBalanceAfter).to.be.eq(userBalanceBefore); + })); +}); + --- a/tests/src/eth/createFTCollection.test.ts +++ b/tests/src/eth/createFTCollection.test.ts @@ -32,6 +32,7 @@ }); }); + // TODO move sponsorship tests to another file: // Soft-deprecated itEth('[eth] Set sponsorship', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); @@ -93,6 +94,11 @@ expect(await collectionHelpers .methods.isCollectionExist(collectionAddress).call()) .to.be.true; + + // check collectionOwner: + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true); + const collectionOwner = await collectionEvm.methods.collectionOwner().call(); + expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true)); }); itEth('destroyCollection', async ({helper}) => { --- a/tests/src/eth/createNFTCollection.test.ts +++ b/tests/src/eth/createNFTCollection.test.ts @@ -134,6 +134,11 @@ expect(await collectionHelpers .methods.isCollectionExist(collectionAddress).call()) .to.be.true; + + // check collectionOwner: + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true); + const collectionOwner = await collectionEvm.methods.collectionOwner().call(); + expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true)); }); }); --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -88,27 +88,6 @@ ]); }); - // this test will occasionally fail when in async environment. - itEth.skip('Check collection address exist', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - - const expectedCollectionId = +(await helper.callRpc('api.rpc.unique.collectionStats')).created + 1; - const expectedCollectionAddress = helper.ethAddress.fromCollectionId(expectedCollectionId); - const collectionHelpers = await helper.ethNativeContract.collectionHelpers(owner); - - expect(await collectionHelpers.methods - .isCollectionExist(expectedCollectionAddress) - .call()).to.be.false; - - await collectionHelpers.methods - .createRFTCollection('A', 'A', 'A') - .send({value: Number(2n * helper.balance.getOneTokenNominal())}); - - expect(await collectionHelpers.methods - .isCollectionExist(expectedCollectionAddress) - .call()).to.be.true; - }); - // Soft-deprecated itEth('[eth] Set sponsorship', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); @@ -166,6 +145,11 @@ expect(await collectionHelpers .methods.isCollectionExist(collectionAddress).call()) .to.be.true; + + // check collectionOwner: + const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true); + const collectionOwner = await collectionEvm.methods.collectionOwner().call(); + expect(helper.address.restoreCrossAccountFromBigInt(BigInt(collectionOwner.sub))).to.eq(helper.address.ethToSubstrate(owner, true)); }); }); --- a/tests/src/eth/evmCoder.test.ts +++ b/tests/src/eth/evmCoder.test.ts @@ -62,7 +62,7 @@ donor = await privateKey({filename: __filename}); }); }); - + itEth('Call non-existing function', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const collection = await helper.eth.createNFTCollection(owner, 'EVMCODER', '', 'TEST'); --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -167,7 +167,6 @@ expect(event.returnValues.to).to.be.equal(receiver); expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI'); - console.log(await contract.methods.crossOwnerOf(tokenId).call()); expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']); // TODO: this wont work right now, need release 919000 first // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send(); @@ -200,7 +199,6 @@ }, }; }); - const collection = await helper.nft.mintCollection(minter, { tokenPrefix: 'ethp', --- a/tests/src/eth/proxyContract.test.ts +++ b/tests/src/eth/proxyContract.test.ts @@ -31,11 +31,11 @@ const deployer = await helper.eth.createAccountWithBalance(donor); const caller = await helper.eth.createAccountWithBalance(donor); const proxyContract = await deployProxyContract(helper, deployer); - + const realContractV1 = await deployRealContractV1(helper, deployer); const realContractV1proxy = new helper.web3!.eth.Contract(realContractV1.options.jsonInterface, proxyContract.options.address, {from: caller, gas: helper.eth.DEFAULT_GAS}); await proxyContract.methods.updateVersion(realContractV1.options.address).send(); - + await realContractV1proxy.methods.flip().send(); await realContractV1proxy.methods.flip().send(); await realContractV1proxy.methods.flip().send(); --- a/tests/src/eth/scheduling.test.ts +++ b/tests/src/eth/scheduling.test.ts @@ -53,7 +53,7 @@ ); expect(await flipper.methods.getValue().call()).to.be.equal(initialValue); - + await helper.wait.newBlocks(waitForBlocks + 1); expect(await flipper.methods.getValue().call()).to.be.not.equal(initialValue); --- a/tests/src/eth/util/index.ts +++ b/tests/src/eth/util/index.ts @@ -58,9 +58,9 @@ silentConsole.disable(); } }; - + export function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) { - (opts.only ? it.only : + (opts.only ? it.only : opts.skip ? it.skip : it)(name, async function() { await usingEthPlaygrounds(async (helper, privateKey) => { if (opts.requiredPallets) { --- a/tests/src/fungible.test.ts +++ b/tests/src/fungible.test.ts @@ -43,7 +43,7 @@ expect(itemCountAfter).to.be.equal(defaultTokenId); expect(aliceBalance).to.be.equal(U128_MAX); }); - + itSub('RPC method tokenOnewrs for fungible collection and token', async ({helper}) => { const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; const facelessCrowd = (await helper.arrange.createAccounts(Array(7).fill(0n), donor)).map(keyring => {return {Substrate: keyring.address};}); @@ -54,22 +54,22 @@ await collection.transfer(alice, {Substrate: bob.address}, 1000n); await collection.transfer(alice, ethAcc, 900n); - + for (let i = 0; i < 7; i++) { await collection.transfer(alice, facelessCrowd[i], 1n); - } + } const owners = await collection.getTop10Owners(); // What to expect expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]); expect(owners.length).to.be.equal(10); - + const [eleven] = await helper.arrange.createAccounts([0n], donor); expect(await collection.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true; expect((await collection.getTop10Owners()).length).to.be.equal(10); }); - + itSub('Transfer token', async ({helper}) => { const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); @@ -108,7 +108,7 @@ expect(await collection.doesTokenExist(0)).to.be.true; expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n); }); - + itSub('Burn all tokens ', async ({helper}) => { const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); await collection.mint(alice, 500n); @@ -127,7 +127,7 @@ await collection.mint(alice, 100n); expect(await collection.getBalance({Substrate: alice.address})).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); @@ -169,11 +169,11 @@ // 1. Alice cannot transfer more than 0 tokens if balance low: await expect(collection.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow'); await expect(collection.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow'); - + // 2. Alice cannot transfer non-existing token: await expect(nonExistingCollection.transfer(alice, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.CollectionNotFound'); await expect(nonExistingCollection.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.CollectionNotFound'); - + // 3. Zero transfer allowed (EIP-20): await collection.transfer(bob, {Substrate: charlie.address}, 0n); // 3.1 even if the balance = 0 --- a/tests/src/inflation.seqtest.ts +++ b/tests/src/inflation.seqtest.ts @@ -26,7 +26,7 @@ superuser = await privateKey('//Alice'); }); }); - + itSub('First year inflation is 10%', async ({helper}) => { // Make sure non-sudo can't start inflation const [bob] = await helper.arrange.createAccounts([10n], superuser); --- a/tests/src/interfaces/unique/definitions.ts +++ b/tests/src/interfaces/unique/definitions.ts @@ -38,39 +38,39 @@ types: {}, rpc: { accountTokens: fun( - 'Get tokens owned by an account in a collection', - [collectionParam, crossAccountParam()], + 'Get tokens owned by an account in a collection', + [collectionParam, crossAccountParam()], 'Vec', ), collectionTokens: fun( - 'Get tokens contained within a collection', - [collectionParam], + 'Get tokens contained within a collection', + [collectionParam], 'Vec', ), tokenExists: fun( - 'Check if the token exists', - [collectionParam, tokenParam], + 'Check if the token exists', + [collectionParam, tokenParam], 'bool', ), tokenOwner: fun( - 'Get the token owner', - [collectionParam, tokenParam], + 'Get the token owner', + [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`, ), topmostTokenOwner: fun( - 'Get the topmost token owner in the hierarchy of a possibly nested token', - [collectionParam, tokenParam], + 'Get the topmost token owner in the hierarchy of a possibly nested token', + [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`, ), tokenOwners: fun( - 'Returns 10 tokens owners in no particular order', - [collectionParam, tokenParam], + 'Returns 10 tokens owners in no particular order', + [collectionParam, tokenParam], `Vec<${CROSS_ACCOUNT_ID_TYPE}>`, ), tokenChildren: fun( - 'Get tokens nested directly into the token', - [collectionParam, tokenParam], + 'Get tokens nested directly into the token', + [collectionParam, tokenParam], 'Vec', ), @@ -91,13 +91,13 @@ ), constMetadata: fun( - 'Get token constant metadata', - [collectionParam, tokenParam], + 'Get token constant metadata', + [collectionParam, tokenParam], 'Vec', ), variableMetadata: fun( - 'Get token variable metadata', - [collectionParam, tokenParam], + 'Get token variable metadata', + [collectionParam, tokenParam], 'Vec', ), @@ -107,77 +107,77 @@ 'UpDataStructsTokenData', ), totalSupply: fun( - 'Get the amount of distinctive tokens present in a collection', - [collectionParam], + 'Get the amount of distinctive tokens present in a collection', + [collectionParam], 'u32', ), accountBalance: fun( - 'Get the amount of any user tokens owned by an account', - [collectionParam, crossAccountParam()], + 'Get the amount of any user tokens owned by an account', + [collectionParam, crossAccountParam()], 'u32', ), balance: fun( - 'Get the amount of a specific token owned by an account', - [collectionParam, crossAccountParam(), tokenParam], + 'Get the amount of a specific token owned by an account', + [collectionParam, crossAccountParam(), tokenParam], 'u128', ), allowance: fun( - 'Get the amount of currently possible sponsored transactions on a token for the fee to be taken off a sponsor', - [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], + 'Get the amount of currently possible sponsored transactions on a token for the fee to be taken off a sponsor', + [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128', ), adminlist: fun( - 'Get the list of admin accounts of a collection', - [collectionParam], + 'Get the list of admin accounts of a collection', + [collectionParam], 'Vec', ), allowlist: fun( - 'Get the list of accounts allowed to operate within a collection', - [collectionParam], + 'Get the list of accounts allowed to operate within a collection', + [collectionParam], 'Vec', ), allowed: fun( - 'Check if a user is allowed to operate within a collection', - [collectionParam, crossAccountParam()], + 'Check if a user is allowed to operate within a collection', + [collectionParam, crossAccountParam()], 'bool', ), lastTokenId: fun( - 'Get the last token ID created in a collection', - [collectionParam], + 'Get the last token ID created in a collection', + [collectionParam], 'u32', ), collectionById: fun( - 'Get a collection by the specified ID', - [collectionParam], + 'Get a collection by the specified ID', + [collectionParam], 'Option', ), collectionStats: fun( - 'Get chain stats about collections', - [], + 'Get chain stats about collections', + [], 'UpDataStructsCollectionStats', ), nextSponsored: fun( - 'Get the number of blocks until sponsoring a transaction is available', - [collectionParam, crossAccountParam(), tokenParam], + 'Get the number of blocks until sponsoring a transaction is available', + [collectionParam, crossAccountParam(), tokenParam], 'Option', ), effectiveCollectionLimits: fun( - 'Get effective collection limits', - [collectionParam], + 'Get effective collection limits', + [collectionParam], 'Option', ), totalPieces: fun( - 'Get the total amount of pieces of an RFT', - [collectionParam, tokenParam], + 'Get the total amount of pieces of an RFT', + [collectionParam, tokenParam], 'Option', ), allowanceForAll: fun( - 'Tells whether the given `owner` approves the `operator`.', - [collectionParam, crossAccountParam('owner'), crossAccountParam('operator')], + 'Tells whether the given `owner` approves the `operator`.', + [collectionParam, crossAccountParam('owner'), crossAccountParam('operator')], 'Option', ), }, --- a/tests/src/limits.test.ts +++ b/tests/src/limits.test.ts @@ -30,7 +30,7 @@ itSub.skip('Collection limits allow greater number than chain limits, chain limits are enforced', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {}); await collection.setLimits(alice, {accountTokenOwnershipLimit: 20}); - + for(let i = 0; i < 10; i++){ await expect(collection.mintToken(alice)).to.be.not.rejected; } @@ -40,14 +40,14 @@ } await collection.burn(alice); }); - + itSub('Collection limits allow lower number than chain limits, collection limits are enforced', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {}); await collection.setLimits(alice, {accountTokenOwnershipLimit: 1}); await collection.mintToken(alice); await expect(collection.mintToken(alice)).to.be.rejectedWith(/common\.AccountTokenLimitExceeded/); - + await collection.burnToken(alice, 1); await expect(collection.burn(alice)).to.be.not.rejected; }); @@ -68,7 +68,7 @@ itSub.skip('Collection limits allow greater number than chain limits, chain limits are enforced', async ({helper}) => { const collection = await helper.rft.mintCollection(alice, {}); await collection.setLimits(alice, {accountTokenOwnershipLimit: 20}); - + for(let i = 0; i < 10; i++){ await expect(collection.mintToken(alice, 10n)).to.be.not.rejected; } @@ -85,7 +85,7 @@ await collection.mintToken(alice); await expect(collection.mintToken(alice)).to.be.rejectedWith(/common\.AccountTokenLimitExceeded/); - + await collection.burnToken(alice, 1); await expect(collection.burn(alice)).to.be.not.rejected; }); @@ -314,7 +314,7 @@ await collection.setSponsor(alice, alice.address); await collection.confirmSponsorship(alice); - + await token.transfer(alice, {Substrate: bob.address}); const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); @@ -345,7 +345,7 @@ await collection.setSponsor(alice, alice.address); await collection.confirmSponsorship(alice); - + await collection.transfer(alice, {Substrate: bob.address}, 2n); const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); @@ -387,7 +387,7 @@ await collection.setSponsor(alice, alice.address); await collection.confirmSponsorship(alice); - + await token.transfer(alice, {Substrate: bob.address}, 2n); const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); @@ -408,17 +408,17 @@ [alice] = await helper.arrange.createAccounts([10n], donor); }); }); - + itSub('Effective collection limits', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {}); - await collection.setLimits(alice, {ownerCanTransfer: true}); - - { + await collection.setLimits(alice, {ownerCanTransfer: true}); + + { // Check that limits are undefined const collectionInfo = await collection.getData(); const limits = collectionInfo?.raw.limits; expect(limits).to.be.any; - + expect(limits.accountTokenOwnershipLimit).to.be.null; expect(limits.sponsoredDataSize).to.be.null; expect(limits.sponsoredDataRateLimit).to.be.null; @@ -449,7 +449,7 @@ expect(limits.transfersEnabled).to.be.true; } - { + { // Check the values for collection limits await collection.setLimits(alice, { accountTokenOwnershipLimit: 99_999, --- a/tests/src/nesting/collectionProperties.seqtest.ts +++ b/tests/src/nesting/collectionProperties.seqtest.ts @@ -20,7 +20,7 @@ describe('Integration Test: Collection Properties with sudo', () => { let superuser: IKeyringPair; let alice: IKeyringPair; - + before(async () => { await usingPlaygrounds(async (helper, privateKey) => { superuser = await privateKey('//Alice'); @@ -32,7 +32,7 @@ [ {mode: 'nft' as const, requiredPallets: []}, {mode: 'ft' as const, requiredPallets: []}, - {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, ].map(testSuite => describe(`${testSuite.mode.toUpperCase()}`, () => { before(async function() { // eslint-disable-next-line require-await --- a/tests/src/nesting/collectionProperties.test.ts +++ b/tests/src/nesting/collectionProperties.test.ts @@ -20,14 +20,14 @@ describe('Integration Test: Collection Properties', () => { let alice: IKeyringPair; let bob: IKeyringPair; - + before(async () => { await usingPlaygrounds(async (helper, privateKey) => { const donor = await privateKey({filename: __filename}); [alice, bob] = await helper.arrange.createAccounts([200n, 10n], donor); }); }); - + itSub('Properties are initially empty', async ({helper}) => { const collection = await helper.nft.mintCollection(alice); expect(await collection.getProperties()).to.be.empty; @@ -36,7 +36,7 @@ [ {mode: 'nft' as const, requiredPallets: []}, {mode: 'ft' as const, requiredPallets: []}, - {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, ].map(testSuite => describe(`${testSuite.mode.toUpperCase()}`, () => { before(async function() { // eslint-disable-next-line require-await @@ -50,37 +50,37 @@ // As owner await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled; - + await collection.addAdmin(alice, {Substrate: bob.address}); - + // As administrator await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled; - + const properties = await collection.getProperties(); expect(properties).to.include.deep.members([ {key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}, ]); }); - + itSub('Check valid names for collection properties keys', async ({helper}) => { const collection = await helper[testSuite.mode].mintCollection(alice); // alpha symbols await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled; - + // numeric symbols await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled; - + // underscore symbol await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled; - + // dash symbol await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled; - + // dot symbol await expect(collection.setProperties(alice, [{key: 'once.in.a.long.long.while...', value: 'you get a little lost'}])).to.be.fulfilled; - + const properties = await collection.getProperties(); expect(properties).to.include.deep.members([ {key: 'answer', value: ''}, @@ -90,29 +90,29 @@ {key: 'once.in.a.long.long.while...', value: 'you get a little lost'}, ]); }); - + itSub('Changes properties of a collection', async ({helper}) => { const collection = await helper[testSuite.mode].mintCollection(alice); await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled; - + // Mutate the properties await expect(collection.setProperties(alice, [{key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled; - + const properties = await collection.getProperties(); expect(properties).to.include.deep.members([ {key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}, ]); }); - + itSub('Deletes properties of a collection', async ({helper}) => { const collection = await helper[testSuite.mode].mintCollection(alice); await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled; - + await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled; - + const properties = await collection.getProperties(['black_hole', 'electron']); expect(properties).to.be.deep.equal([ {key: 'black_hole', value: 'LIGO'}, @@ -201,11 +201,11 @@ }); })); }); - + describe('Negative Integration Test: Collection Properties', () => { let alice: IKeyringPair; let bob: IKeyringPair; - + before(async () => { await usingPlaygrounds(async (helper, privateKey) => { const donor = await privateKey({filename: __filename}); @@ -216,7 +216,7 @@ [ {mode: 'nft' as const, requiredPallets: []}, {mode: 'ft' as const, requiredPallets: []}, - {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, ].map(testSuite => describe(`${testSuite.mode.toUpperCase()}`, () => { before(async function() { // eslint-disable-next-line require-await @@ -224,21 +224,21 @@ requirePalletsOrSkip(this, helper, testSuite.requiredPallets); }); }); - + itSub('Fails to set properties in a collection if not its onwer/administrator', async ({helper}) => { const collection = await helper[testSuite.mode].mintCollection(alice); await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])) .to.be.rejectedWith(/common\.NoPermission/); - + expect(await collection.getProperties()).to.be.empty; }); - + itSub('Fails to set properties that exceed the limits', async ({helper}) => { const collection = await helper[testSuite.mode].mintCollection(alice); const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber(); - + // Mute the general tx parsing error, too many bytes to process { console.error = () => {}; @@ -246,17 +246,17 @@ {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}, ])).to.be.rejected; } - + expect(await collection.getProperties(['electron'])).to.be.empty; - + await expect(collection.setProperties(alice, [ - {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, - {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, + {key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 18))}, + {key: 'black_hole', value: '0'.repeat(Math.ceil(spaceLimit! / 2))}, ])).to.be.rejectedWith(/common\.NoSpaceForProperty/); - + expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty; }); - + itSub('Fails to set more properties than it is allowed', async ({helper}) => { const collection = await helper[testSuite.mode].mintCollection(alice); @@ -267,10 +267,10 @@ value: Math.random() > 0.5 ? 'high' : 'low', }); } - + await expect(collection.setProperties(alice, propertiesToBeSet)). to.be.rejectedWith(/common\.PropertyLimitReached/); - + expect(await collection.getProperties()).to.be.empty; }); @@ -282,34 +282,34 @@ [{key: 'Mr/Sandman', value: 'Bring me a gene'}], [{key: 'déjà vu', value: 'hmm...'}], ]; - + for (let i = 0; i < invalidProperties.length; i++) { await expect( - collection.setProperties(alice, invalidProperties[i]), + collection.setProperties(alice, invalidProperties[i]), `on rejecting the new badly-named property #${i}`, ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); } - + await expect( - collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), + collection.setProperties(alice, [{key: '', value: 'nothing must not exist'}]), 'on rejecting an unnamed property', ).to.be.rejectedWith(/common\.EmptyPropertyKey/); - + await expect( - collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), + collection.setProperties(alice, [{key: 'CRISPR-Cas9', value: 'rewriting nature!'}]), 'on setting the correctly-but-still-badly-named property', ).to.be.fulfilled; - + const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat(''); - + const properties = await collection.getProperties(keys); expect(properties).to.be.deep.equal([ {key: 'CRISPR-Cas9', value: 'rewriting nature!'}, ]); - + for (let i = 0; i < invalidProperties.length; i++) { await expect( - collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), + collection.deleteProperties(alice, invalidProperties[i].map(propertySet => propertySet.key)), `on trying to delete the non-existent badly-named property #${i}`, ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); } @@ -326,4 +326,3 @@ }); })); }); - \ No newline at end of file --- a/tests/src/nesting/graphs.test.ts +++ b/tests/src/nesting/graphs.test.ts @@ -56,7 +56,7 @@ // to self await expect( tokens[0].nest(alice, tokens[0]), - 'first transaction', + 'first transaction', ).to.be.rejectedWith(/structure\.OuroborosDetected/); // to nested part of graph await expect( --- a/tests/src/nesting/nest.test.ts +++ b/tests/src/nesting/nest.test.ts @@ -36,7 +36,7 @@ const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - + // Create a token to be nested const newToken = await collection.mintToken(alice); @@ -66,7 +66,7 @@ // Create a nested token const tokenC = await collection.mintToken(alice, tokenA.nestingAccount()); expect(await tokenC.getOwner()).to.be.deep.equal(tokenA.nestingAccount().toLowerCase()); - + // Transfer the nested token to another token await expect(tokenC.transferFrom(alice, tokenA.nestingAccount(), tokenB.nestingAccount())).to.be.fulfilled; expect(await tokenC.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); @@ -76,7 +76,7 @@ itSub('Checks token children', async ({helper}) => { const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); const collectionB = await helper.ft.mintCollection(alice); - + const targetToken = await collectionA.mintToken(alice); expect((await targetToken.getChildren()).length).to.be.equal(0, 'Children length check at creation'); @@ -108,7 +108,7 @@ {tokenId: 0, collectionId: collectionB.collectionId}, ], 'Children contents check at nesting #4 (from another collection)') .and.be.length(2, 'Children length check at nesting #4 (from another collection)'); - + // Move part of the fungible token inside token A deeper in the nesting tree await collectionB.transferFrom(alice, targetToken.nestingAccount(), tokenA.nestingAccount(), 1n); expect(await targetToken.getChildren()).to.be.have.deep.members([ --- a/tests/src/nesting/propertyPermissions.test.ts +++ b/tests/src/nesting/propertyPermissions.test.ts @@ -21,94 +21,94 @@ describe('Integration Test: Access Rights to Token Properties', () => { let alice: IKeyringPair; let bob: IKeyringPair; - + before(async () => { await usingPlaygrounds(async (helper, privateKey) => { const donor = await privateKey({filename: __filename}); [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor); }); }); - + itSub('Reads access rights to properties of a collection', async ({helper}) => { const collection = await helper.nft.mintCollection(alice); const propertyRights = (await helper.callRpc('api.query.common.collectionPropertyPermissions', [collection.collectionId])).toJSON(); expect(propertyRights).to.be.empty; }); - - async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { + + async function testSetsAccessRightsToProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true}}])) .to.be.fulfilled; - + await collection.addAdmin(alice, {Substrate: bob.address}); - + await expect(collection.setTokenPropertyPermissions(bob, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}])) .to.be.fulfilled; - + const propertyRights = await collection.getPropertyPermissions(['skullduggery', 'mindgame']); expect(propertyRights).to.include.deep.members([ {key: 'skullduggery', permission: {mutable: true, collectionAdmin: false, tokenOwner: false}}, {key: 'mindgame', permission: {mutable: false, collectionAdmin: true, tokenOwner: false}}, ]); } - + itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) => { await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice)); }); - + itSub.ifWithPallets('Sets access rights to properties of a collection (ReFungible)', [Pallets.ReFungible], async ({helper}) => { await testSetsAccessRightsToProperties(await helper.rft.mintCollection(alice)); }); - + async function testChangesAccessRightsToProperty(collection: UniqueNFTCollection | UniqueRFTCollection) { await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}])) .to.be.fulfilled; - + await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}])) .to.be.fulfilled; - + const propertyRights = await collection.getPropertyPermissions(); expect(propertyRights).to.be.deep.equal([ {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}}, ]); } - + itSub('Changes access rights to properties of a NFT collection', async ({helper}) => { await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice)); }); - + itSub.ifWithPallets('Changes access rights to properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => { await testChangesAccessRightsToProperty(await helper.rft.mintCollection(alice)); }); }); - + describe('Negative Integration Test: Access Rights to Token Properties', () => { let alice: IKeyringPair; let bob: IKeyringPair; - + before(async () => { await usingPlaygrounds(async (helper, privateKey) => { const donor = await privateKey({filename: __filename}); [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor); }); }); - + async function testPreventsFromSettingAccessRightsNotAdminOrOwner(collection: UniqueNFTCollection | UniqueRFTCollection) { await expect(collection.setTokenPropertyPermissions(bob, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}])) .to.be.rejectedWith(/common\.NoPermission/); - + const propertyRights = await collection.getPropertyPermissions(['skullduggery']); expect(propertyRights).to.be.empty; } - + itSub('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async ({helper}) => { await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.nft.mintCollection(alice)); }); - + itSub.ifWithPallets('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', [Pallets.ReFungible], async ({helper}) => { await testPreventsFromSettingAccessRightsNotAdminOrOwner(await helper.rft.mintCollection(alice)); }); - - async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { + + async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { const constitution = []; for (let i = 0; i < 65; i++) { constitution.push({ @@ -116,82 +116,82 @@ permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {}, }); } - + await expect(collection.setTokenPropertyPermissions(alice, constitution)) .to.be.rejectedWith(/common\.PropertyLimitReached/); - + const propertyRights = await collection.getPropertyPermissions(); expect(propertyRights).to.be.empty; } - + itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) => { await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice)); }); - + itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => { await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice)); }); - + async function testPreventAccessRightsModifiedIfConstant(collection: UniqueNFTCollection | UniqueRFTCollection) { await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}])) .to.be.fulfilled; - + await expect(collection.setTokenPropertyPermissions(alice, [{key: 'skullduggery', permission: {collectionAdmin: true}}])) .to.be.rejectedWith(/common\.NoPermission/); - + const propertyRights = await collection.getPropertyPermissions(['skullduggery']); expect(propertyRights).to.deep.equal([ {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}}, ]); } - + itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) => { await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice)); }); - + itSub.ifWithPallets('Prevents access rights to be modified if constant (ReFungible)', [Pallets.ReFungible], async ({helper}) => { await testPreventAccessRightsModifiedIfConstant(await helper.rft.mintCollection(alice)); }); - + async function testPreventsAddingPropertiesWithInvalidNames(collection: UniqueNFTCollection | UniqueRFTCollection) { const invalidProperties = [ [{key: 'skullduggery', permission: {tokenOwner: true}}, {key: 'im possible', permission: {collectionAdmin: true}}], [{key: 'G#4', permission: {tokenOwner: true}}], [{key: 'HÆMILTON', permission: {mutable: false, collectionAdmin: true, tokenOwner: true}}], ]; - + for (let i = 0; i < invalidProperties.length; i++) { await expect( - collection.setTokenPropertyPermissions(alice, invalidProperties[i]), + collection.setTokenPropertyPermissions(alice, invalidProperties[i]), `on setting the new badly-named property #${i}`, ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); } - + await expect( - collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), + collection.setTokenPropertyPermissions(alice, [{key: '', permission: {}}]), 'on rejecting an unnamed property', ).to.be.rejectedWith(/common\.EmptyPropertyKey/); - + const correctKey = '--0x03116e387820CA05'; // PolkadotJS would parse this as an already encoded hex-string await expect( collection.setTokenPropertyPermissions(alice, [ {key: correctKey, permission: {collectionAdmin: true}}, - ]), + ]), 'on setting the correctly-but-still-badly-named property', ).to.be.fulfilled; - + const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat(''); - + const propertyRights = await collection.getPropertyPermissions(keys); expect(propertyRights).to.be.deep.equal([ {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}}, ]); } - + itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) => { await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice)); }); - + itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => { await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice)); }); --- a/tests/src/nesting/tokenProperties.seqtest.ts +++ b/tests/src/nesting/tokenProperties.seqtest.ts @@ -31,7 +31,7 @@ [ {mode: 'nft' as const, pieces: undefined, requiredPallets: []}, - {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, ].map(testSuite => describe(`${testSuite.mode.toUpperCase()}`, () => { before(async function() { // eslint-disable-next-line require-await @@ -39,7 +39,7 @@ requirePalletsOrSkip(this, helper, testSuite.requiredPallets); }); }); - + itSub('force_repair_item preserves valid consumed space', async({helper}) => { const propKey = 'tok-prop'; --- a/tests/src/nesting/tokenProperties.test.ts +++ b/tests/src/nesting/tokenProperties.test.ts @@ -43,12 +43,12 @@ async function mintCollectionWithAllPermissionsAndToken(helper: UniqueHelper, mode: 'NFT' | 'RFT'): Promise<[UniqueNFToken | UniqueRFToken, bigint]> { const collection = await (mode == 'NFT' ? helper.nft : helper.rft).mintCollection(alice, { - tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => + tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => signers.map(signer => {return {key: `${i+1}_${signer.address}`, permission};})), }); return mode == 'NFT' ? [await collection.mintToken(alice), 1n] : [await collection.mintToken(alice, 100n), 100n]; } - + async function testReadsYetEmptyProperties(token: UniqueNFToken | UniqueRFToken) { const properties = await token.getProperties(); expect(properties).to.be.empty; @@ -84,7 +84,7 @@ propertyKeys.push(key); await expect( - token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), + token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), `on adding property #${i} by signer #${j}`, ).to.be.fulfilled; } @@ -117,7 +117,7 @@ for (const permission of permissions) { i++; if (!permission.permission.mutable) continue; - + let j = 0; for (const signer of permission.signers) { j++; @@ -125,12 +125,12 @@ propertyKeys.push(key); await expect( - token.setProperties(signer, [{key, value: 'Serotonin increase'}]), + token.setProperties(signer, [{key, value: 'Serotonin increase'}]), `on adding property #${i} by signer #${j}`, ).to.be.fulfilled; await expect( - token.setProperties(signer, [{key, value: 'Serotonin stable'}]), + token.setProperties(signer, [{key, value: 'Serotonin stable'}]), `on changing property #${i} by signer #${j}`, ).to.be.fulfilled; } @@ -164,7 +164,7 @@ for (const permission of permissions) { i++; if (!permission.permission.mutable) continue; - + let j = 0; for (const signer of permission.signers) { j++; @@ -172,12 +172,12 @@ propertyKeys.push(key); await expect( - token.setProperties(signer, [{key, value: 'Serotonin increase'}]), + token.setProperties(signer, [{key, value: 'Serotonin increase'}]), `on adding property #${i} by signer #${j}`, ).to.be.fulfilled; await expect( - token.deleteProperties(signer, [key]), + token.deleteProperties(signer, [key]), `on deleting property #${i} by signer #${j}`, ).to.be.fulfilled; } @@ -186,7 +186,7 @@ expect(await token.getProperties(propertyKeys)).to.be.empty; expect((await token.getData())!.properties).to.be.empty; } - + itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) => { const [token, amount] = await mintCollectionWithAllPermissionsAndToken(helper, 'NFT'); await testDeletePropertiesAccordingPermission(token, amount); @@ -200,7 +200,7 @@ itSub('Assigns properties to a nested token according to permissions', async ({helper}) => { const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); const collectionB = await helper.nft.mintCollection(alice, { - tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => + tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => signers.map(signer => {return {key: `${i+1}_${signer.address}`, permission};})), }); const targetToken = await collectionA.mintToken(alice); @@ -220,7 +220,7 @@ propertyKeys.push(key); await expect( - nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), + nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), `on adding property #${i} by signer #${j}`, ).to.be.fulfilled; } @@ -238,7 +238,7 @@ itSub('Changes properties of a nested token according to permissions', async ({helper}) => { const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); const collectionB = await helper.nft.mintCollection(alice, { - tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => + tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => signers.map(signer => {return {key: `${i+1}_${signer.address}`, permission};})), }); const targetToken = await collectionA.mintToken(alice); @@ -252,7 +252,7 @@ for (const permission of permissions) { i++; if (!permission.permission.mutable) continue; - + let j = 0; for (const signer of permission.signers) { j++; @@ -260,12 +260,12 @@ propertyKeys.push(key); await expect( - nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), + nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), `on adding property #${i} by signer #${j}`, ).to.be.fulfilled; await expect( - nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), + nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), `on changing property #${i} by signer #${j}`, ).to.be.fulfilled; } @@ -283,7 +283,7 @@ itSub('Deletes properties of a nested token according to permissions', async ({helper}) => { const collectionA = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); const collectionB = await helper.nft.mintCollection(alice, { - tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => + tokenPropertyPermissions: permissions.flatMap(({permission, signers}, i) => signers.map(signer => {return {key: `${i+1}_${signer.address}`, permission};})), }); const targetToken = await collectionA.mintToken(alice); @@ -297,7 +297,7 @@ for (const permission of permissions) { i++; if (!permission.permission.mutable) continue; - + let j = 0; for (const signer of permission.signers) { j++; @@ -305,12 +305,12 @@ propertyKeys.push(key); await expect( - nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), + nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), `on adding property #${i} by signer #${j}`, ).to.be.fulfilled; await expect( - nestedToken.deleteProperties(signer, [key]), + nestedToken.deleteProperties(signer, [key]), `on deleting property #${i} by signer #${j}`, ).to.be.fulfilled; } @@ -323,7 +323,7 @@ [ {mode: 'nft' as const, storage: 'nonfungible' as const, pieces: undefined, requiredPallets: []}, - {mode: 'rft' as const, storage: 'refungible' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, storage: 'refungible' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, ].map(testCase => itSub.ifWithPallets(`Allows modifying a token property multiple times with the same size (${testCase.mode})`, testCase.requiredPallets, async({helper}) => { const propKey = 'tok-prop'; @@ -370,7 +370,7 @@ [ {mode: 'nft' as const, pieces: undefined, requiredPallets: []}, - {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, ].map(testCase => itSub.ifWithPallets(`Adding then removing a token property doesn't change the consumed space (${testCase.mode})`, testCase.requiredPallets, async({helper}) => { const propKey = 'tok-prop'; @@ -404,7 +404,7 @@ [ {mode: 'nft' as const, pieces: undefined, requiredPallets: []}, - {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, ].map(testCase => itSub.ifWithPallets(`Modifying a token property with different sizes correctly changes the consumed space (${testCase.mode})`, testCase.requiredPallets, async({helper}) => { const propKey = 'tok-prop'; @@ -497,12 +497,12 @@ i++; const signer = passage.signers[0]; await expect( - token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), + token.setProperties(signer, [{key: `${i}`, value: 'Serotonin increase'}]), `on adding property ${i} by ${signer.address}`, ).to.be.fulfilled; } - const originalSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const originalSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); return originalSpace; } @@ -515,17 +515,17 @@ if (!forbiddance.permission.mutable) continue; await expect( - token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), + token.setProperties(forbiddance.sinner, [{key: `${i}`, value: 'Serotonin down'}]), `on failing to change property ${i} by the malefactor`, ).to.be.rejectedWith(/common\.NoPermission/); await expect( - token.deleteProperties(forbiddance.sinner, [`${i}`]), + token.deleteProperties(forbiddance.sinner, [`${i}`]), `on failing to delete property ${i} by the malefactor`, ).to.be.rejectedWith(/common\.NoPermission/); } - const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } @@ -548,17 +548,17 @@ if (permission.permission.mutable) continue; await expect( - token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), + token.setProperties(permission.signers[0], [{key: `${i}`, value: 'Serotonin down'}]), `on failing to change property ${i} by signer #0`, ).to.be.rejectedWith(/common\.NoPermission/); await expect( - token.deleteProperties(permission.signers[0], [i.toString()]), + token.deleteProperties(permission.signers[0], [i.toString()]), `on failing to delete property ${i} by signer #0`, ).to.be.rejectedWith(/common\.NoPermission/); } - - const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } @@ -576,23 +576,23 @@ const originalSpace = await prepare(token, pieces); await expect( - token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), + token.setProperties(alice, [{key: 'non-existent', value: 'I exist!'}]), 'on failing to add a previously non-existent property', ).to.be.rejectedWith(/common\.NoPermission/); - + await expect( - token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), + token.collection.setTokenPropertyPermissions(alice, [{key: 'now-existent', permission: {}}]), 'on setting a new non-permitted property', ).to.be.fulfilled; await expect( - token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), + token.setProperties(alice, [{key: 'now-existent', value: 'I exist!'}]), 'on failing to add a property forbidden by the \'None\' permission', ).to.be.rejectedWith(/common\.NoPermission/); expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty; - - const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } @@ -611,9 +611,9 @@ await expect( token.collection.setTokenPropertyPermissions(alice, [ - {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, + {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}}, - ]), + ]), 'on setting new permissions for properties', ).to.be.fulfilled; @@ -625,12 +625,12 @@ } await expect(token.setProperties(alice, [ - {key: 'a_holy_book', value: 'word '.repeat(3277)}, + {key: 'a_holy_book', value: 'word '.repeat(3277)}, {key: 'young_years', value: 'neverending'.repeat(1490)}, ])).to.be.rejectedWith(/common\.NoSpaceForProperty/); - + expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty; - const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } @@ -646,7 +646,7 @@ [ {mode: 'nft' as const, requiredPallets: []}, - {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, ].map(testCase => itSub.ifWithPallets(`Forbids adding too many propeties to a token (${testCase.mode})`, testCase.requiredPallets, async({helper}) => { const collection = await helper[testCase.mode].mintCollection(alice); @@ -667,7 +667,7 @@ [ {mode: 'nft' as const, pieces: undefined, requiredPallets: []}, - {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]}, ].map(testCase => itSub.ifWithPallets(`Forbids force_repair_item from non-sudo (${testCase.mode})`, testCase.requiredPallets, async({helper}) => { const propKey = 'tok-prop'; @@ -712,10 +712,10 @@ async function prepare(helper: UniqueHelper): Promise { const collection = await helper.rft.mintCollection(alice); const token = await collection.mintToken(alice, 100n); - + await collection.addAdmin(alice, {Substrate: bob.address}); await collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable: true, tokenOwner: true}}]); - + return token; } @@ -725,7 +725,7 @@ await token.transfer(alice, {Substrate: charlie.address}, 33n); await expect(token.setProperties(alice, [ - {key: 'fractals', value: 'multiverse'}, + {key: 'fractals', value: 'multiverse'}, ])).to.be.rejectedWith(/common\.NoPermission/); }); @@ -736,13 +736,13 @@ .to.be.fulfilled; await expect(token.setProperties(alice, [ - {key: 'fractals', value: 'multiverse'}, + {key: 'fractals', value: 'multiverse'}, ])).to.be.fulfilled; await token.transfer(alice, {Substrate: charlie.address}, 33n); await expect(token.setProperties(alice, [ - {key: 'fractals', value: 'want to rule the world'}, + {key: 'fractals', value: 'want to rule the world'}, ])).to.be.rejectedWith(/common\.NoPermission/); }); @@ -750,7 +750,7 @@ const token = await prepare(helper); await expect(token.setProperties(alice, [ - {key: 'fractals', value: 'one headline - why believe it'}, + {key: 'fractals', value: 'one headline - why believe it'}, ])).to.be.fulfilled; await token.transfer(alice, {Substrate: charlie.address}, 33n); @@ -768,7 +768,7 @@ .to.be.fulfilled; await expect(token.setProperties(alice, [ - {key: 'fractals', value: 'multiverse'}, + {key: 'fractals', value: 'multiverse'}, ])).to.be.fulfilled; }); }); --- a/tests/src/nesting/unnest.test.ts +++ b/tests/src/nesting/unnest.test.ts @@ -30,7 +30,7 @@ itSub('NFT: allows the owner to successfully unnest a token', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); const targetToken = await collection.mintToken(alice); - + // Create a nested token const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); @@ -49,7 +49,7 @@ const targetToken = await collection.mintToken(alice); const collectionFT = await helper.ft.mintCollection(alice); - + // Nest and unnest await collectionFT.mint(alice, 10n, targetToken.nestingAccount()); await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled; @@ -69,7 +69,7 @@ const targetToken = await collection.mintToken(alice); const collectionRFT = await helper.rft.mintCollection(alice); - + // Nest and unnest const token = await collectionRFT.mintToken(alice, 10n, targetToken.nestingAccount()); await expect(token.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}, 9n), 'while unnesting').to.be.fulfilled; --- a/tests/src/nextSponsoring.test.ts +++ b/tests/src/nextSponsoring.test.ts @@ -39,7 +39,7 @@ // Check with Disabled sponsoring state expect(await token.getNextSponsored({Substrate: alice.address})).to.be.null; - + // Check with Unconfirmed sponsoring state await collection.setSponsor(alice, bob.address); expect(await token.getNextSponsored({Substrate: alice.address})).to.be.null; @@ -52,7 +52,7 @@ await token.transfer(alice, {Substrate: bob.address}); expect(await token.getNextSponsored({Substrate: alice.address})).to.be.lessThanOrEqual(SPONSORING_TIMEOUT); - // Non-existing token + // Non-existing token expect(await collection.getTokenNextSponsored(0, {Substrate: alice.address})).to.be.null; }); @@ -65,7 +65,7 @@ await collection.setSponsor(alice, bob.address); await collection.confirmSponsorship(bob); - + // Check with Confirmed sponsoring state expect(await collection.getTokenNextSponsored(0, {Substrate: alice.address})).to.be.equal(0); @@ -91,7 +91,7 @@ await token.transfer(alice, {Substrate: bob.address}); expect(await token.getNextSponsored({Substrate: alice.address})).to.be.lessThanOrEqual(SPONSORING_TIMEOUT); - // Non-existing token + // Non-existing token expect(await collection.getTokenNextSponsored(0, {Substrate: alice.address})).to.be.null; }); }); --- a/tests/src/refungible.test.ts +++ b/tests/src/refungible.test.ts @@ -32,36 +32,36 @@ [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor); }); }); - + itSub('Create refungible collection and token', 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, 100n); - + const itemCountAfter = await collection.getLastTokenId(); - + // What to expect expect(token?.tokenId).to.be.gte(itemCountBefore); expect(itemCountAfter).to.be.equal(itemCountBefore + 1); expect(itemCountAfter.toString()).to.be.equal(token?.tokenId.toString()); }); - + itSub('Checking RPC methods when interacting with maximum allowed values (MAX_REFUNGIBLE_PIECES)', async ({helper}) => { const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); - + const token = await collection.mintToken(alice, MAX_REFUNGIBLE_PIECES); - + expect(await collection.getTokenBalance(token.tokenId, {Substrate: alice.address})).to.be.equal(MAX_REFUNGIBLE_PIECES); - + await collection.transferToken(alice, token.tokenId, {Substrate: bob.address}, MAX_REFUNGIBLE_PIECES); expect(await collection.getTokenBalance(token.tokenId, {Substrate: bob.address})).to.be.equal(MAX_REFUNGIBLE_PIECES); expect(await token.getTotalPieces()).to.be.equal(MAX_REFUNGIBLE_PIECES); - + await expect(collection.mintToken(alice, MAX_REFUNGIBLE_PIECES + 1n)) .to.eventually.be.rejectedWith(/refungible\.WrongRefungiblePieces/); }); - + itSub('RPC method tokenOwners for refungible collection and token', async ({helper}) => { const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; const facelessCrowd = (await helper.arrange.createAccounts(Array(7).fill(0n), donor)).map(keyring => {return {Substrate: keyring.address};}); @@ -72,32 +72,32 @@ await token.transfer(alice, {Substrate: bob.address}, 1000n); await token.transfer(alice, ethAcc, 900n); - + for (let i = 0; i < 7; i++) { await token.transfer(alice, facelessCrowd[i], 50n * BigInt(i + 1)); - } + } const owners = await token.getTop10Owners(); // What to expect expect(owners).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]); expect(owners.length).to.be.equal(10); - + const [eleven] = await helper.arrange.createAccounts([0n], donor); expect(await token.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true; expect((await token.getTop10Owners()).length).to.be.equal(10); }); - + itSub('Transfer token pieces', async ({helper}) => { const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); const token = await collection.mintToken(alice, 100n); 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.rejectedWith(/common\.TokenValueTooLow/); }); @@ -111,8 +111,8 @@ // {owner: {Substrate: alice.address}, pieces: 100n}, // ]); await helper.rft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, [ - {pieces: 1n}, - {pieces: 2n}, + {pieces: 1n}, + {pieces: 2n}, {pieces: 100n}, ]); const lastTokenId = await collection.getLastTokenId(); @@ -133,7 +133,7 @@ itSub('Burn all pieces', async ({helper}) => { const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); const token = await collection.mintToken(alice, 100n); - + expect(await collection.doesTokenExist(token.tokenId)).to.be.true; expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); @@ -146,7 +146,7 @@ const token = await collection.mintToken(alice, 100n); expect(await collection.doesTokenExist(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; @@ -171,7 +171,7 @@ itSub('Set allowance for token', async ({helper}) => { const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}); const token = await collection.mintToken(alice, 100n); - + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); expect(await token.approve(alice, {Substrate: bob.address}, 60n)).to.be.true; @@ -190,14 +190,14 @@ 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.rejectedWith(/refungible\.RepartitionWhileNotOwningAllPieces/); - + 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); @@ -220,7 +220,7 @@ data: [ collection.collectionId, token.tokenId, - {substrate: alice.address}, + {substrate: alice.address}, 100n, ], }); @@ -239,12 +239,12 @@ data: [ collection.collectionId, token.tokenId, - {substrate: alice.address}, + {substrate: alice.address}, 50n, ], }); }); - + itSub('Create new collection with properties', async ({helper}) => { const properties = [{key: 'key1', value: 'val1'}]; const tokenPropertyPermissions = [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]; @@ -280,7 +280,7 @@ await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow'); await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 10n)).to.be.rejectedWith('common.TokenValueTooLow'); await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow'); - + // 2. Alice cannot transfer non-existing token: await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow'); await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow'); --- a/tests/src/removeCollectionAdmin.test.ts +++ b/tests/src/removeCollectionAdmin.test.ts @@ -94,7 +94,7 @@ itSub('Admin can\'t remove collection admin.', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {name: 'RemoveCollectionAdmin-Neg-4', tokenPrefix: 'RCA'}); - + await collection.addAdmin(alice, {Substrate: bob.address}); await collection.addAdmin(alice, {Substrate: charlie.address}); --- a/tests/src/rpc.test.ts +++ b/tests/src/rpc.test.ts @@ -35,20 +35,20 @@ const owner = (await helper.callRpc('api.rpc.unique.tokenOwner', [collection.collectionId, 0])).toJSON() as any; expect(owner).to.be.null; }); - + itSub('RPC method tokenOwners for fungible collection and token', async ({helper}) => { // Set-up a few token owners of all stripes const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; const facelessCrowd = (await helper.arrange.createAccounts([0n, 0n, 0n, 0n, 0n, 0n, 0n], donor)) .map(i => {return {Substrate: i.address};}); - + const collection = await helper.ft.mintCollection(alice, {name: 'RPC-2', tokenPrefix: 'RPC'}); // mint some maximum (u128) amounts of tokens possible await collection.mint(alice, (1n << 128n) - 1n); - + await collection.transfer(alice, {Substrate: bob.address}, 1000n); await collection.transfer(alice, ethAcc, 900n); - + for (let i = 0; i < facelessCrowd.length; i++) { await collection.transfer(alice, facelessCrowd[i], 1n); } @@ -59,7 +59,7 @@ expect(ids).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]); expect(owners.length == 10).to.be.true; - + // Make sure only 10 results are returned with this RPC const [eleven] = await helper.arrange.createAccounts([0n], donor); expect(await collection.transfer(alice, {Substrate: eleven.address}, 10n)).to.be.true; --- a/tests/src/scheduler.seqtest.ts +++ b/tests/src/scheduler.seqtest.ts @@ -47,7 +47,7 @@ const token = await collection.mintToken(alice); const scheduledId = scheduleKind == 'named' ? helper.arrange.makeScheduledId() : undefined; const blocksBeforeExecution = 4; - + await token.scheduleAfter(blocksBeforeExecution, {scheduledId}) .transfer(alice, {Substrate: bob.address}); const executionBlock = await helper.chain.getLatestBlockNumber() + blocksBeforeExecution + 1; @@ -156,7 +156,7 @@ const maxScheduledPerBlock = 50; let fillScheduledIds = new Array(maxScheduledPerBlock); let extraScheduledId = undefined; - + if (scheduleKind == 'named') { const scheduledIds = helper.arrange.makeScheduledIds(maxScheduledPerBlock + 1); fillScheduledIds = scheduledIds.slice(0, maxScheduledPerBlock); @@ -641,7 +641,7 @@ const balanceBefore = await helper.balance.getSubstrate(bob.address); const scheduled = helper.scheduler.scheduleAfter(waitForBlocks, {scheduledId, priority: 42}); - + await expect(scheduled.balance.transferToSubstrate(alice, bob.address, amount)) .to.be.rejectedWith(/BadOrigin/); --- a/tests/src/setCollectionLimits.test.ts +++ b/tests/src/setCollectionLimits.test.ts @@ -75,7 +75,7 @@ await collection.setLimits(alice, collectionLimits); const collectionInfo1 = await collection.getEffectiveLimits(); - + expect(collectionInfo1.tokenLimit).to.be.equal(tokenLimit); await collection.setLimits(alice, collectionLimits); @@ -108,7 +108,7 @@ [alice, bob] = await helper.arrange.createAccounts([20n, 10n], donor); }); }); - + itSub('execute setCollectionLimits for not exists collection', async ({helper}) => { const nonExistentCollectionId = (1 << 32) - 1; await expect(helper.collection.setLimits( @@ -180,7 +180,7 @@ itSub('Setting the higher token limit fails', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {name: 'SetCollectionLimits-Neg-4', tokenPrefix: 'SCL'}); - + const collectionLimits = { accountTokenOwnershipLimit: accountTokenOwnershipLimit, sponsoredMintSize: sponsoredDataSize, --- a/tests/src/setCollectionSponsor.test.ts +++ b/tests/src/setCollectionSponsor.test.ts @@ -37,7 +37,7 @@ Unconfirmed: bob.address, }); }); - + itSub('Set Fungible collection sponsor', async ({helper}) => { const collection = await helper.ft.mintCollection(alice, {name: 'SetCollectionSponsor-1-FT', tokenPrefix: 'SCS'}); await expect(collection.setSponsor(alice, bob.address)).to.be.not.rejected; @@ -75,7 +75,7 @@ Unconfirmed: charlie.address, }); }); - + itSub('Collection admin add sponsor', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {name: 'SetCollectionSponsor-4', tokenPrefix: 'SCS'}); await collection.addAdmin(alice, {Substrate: bob.address}); --- a/tests/src/setPermissions.test.ts +++ b/tests/src/setPermissions.test.ts @@ -34,11 +34,11 @@ await collection.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true, restricted: [1, 2]}}); await collection.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true, restricted: [1, 2]}}); - + const permissions = (await collection.getData())?.raw.permissions; expect(permissions).to.be.deep.equal({ - access: 'AllowList', - mintMode: true, + access: 'AllowList', + mintMode: true, nesting: {collectionAdmin: true, tokenOwner: true, restricted: [1, 2]}, }); }); @@ -49,16 +49,16 @@ await collection.setPermissions(alice, {access: 'AllowList', nesting: {collectionAdmin: false, tokenOwner: true, restricted: [1, 2]}}); expect((await collection.getData())?.raw.permissions).to.be.deep.equal({ - access: 'AllowList', - mintMode: false, + access: 'AllowList', + mintMode: false, nesting: {collectionAdmin: false, tokenOwner: true, restricted: [1, 2]}, }); await collection.setPermissions(alice, {access: 'Normal', mintMode: false, nesting: {}}); await collection.setPermissions(alice, {access: 'Normal', mintMode: false, nesting: {}}); expect((await collection.getData())?.raw.permissions).to.be.deep.equal({ - access: 'Normal', - mintMode: false, + access: 'Normal', + mintMode: false, nesting: {collectionAdmin: false, tokenOwner: false, restricted: null}, }); }); @@ -67,7 +67,7 @@ const collection = await helper.nft.mintCollection(alice, {name: 'SetPermissions-2', tokenPrefix: 'SP'}); await collection.addAdmin(alice, {Substrate: bob.address}); await collection.setPermissions(bob, {access: 'AllowList', mintMode: true}); - + expect((await collection.getData())?.raw.permissions.access).to.equal('AllowList'); expect((await collection.getData())?.raw.permissions.mintMode).to.equal(true); }); --- a/tests/src/transfer.nload.ts +++ b/tests/src/transfer.nload.ts @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +/* eslint-disable @typescript-eslint/no-floating-promises */ import os from 'os'; import {IKeyringPair} from '@polkadot/types/types'; import {usingPlaygrounds} from './util'; --- a/tests/src/transfer.test.ts +++ b/tests/src/transfer.test.ts @@ -29,7 +29,7 @@ [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor); }); }); - + itSub('Balance transfers and check balance', async ({helper}) => { const alicesBalanceBefore = await helper.balance.getSubstrate(alice.address); const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address); @@ -162,7 +162,7 @@ await expect(collection.transfer(alice, {Substrate: bob.address})) .to.be.rejectedWith(/common\.CollectionNotFound/); }); - + itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => { const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'}); const rft = await collection.mintToken(alice, 10n); @@ -279,7 +279,7 @@ donor = await privateKey({filename: __filename}); }); }); - + itEth('Transfers to self. In case of same frontend', async ({helper}) => { const [owner] = await helper.arrange.createAccounts([10n], donor); const collection = await helper.ft.mintCollection(owner, {}); --- a/tests/src/transferFrom.test.ts +++ b/tests/src/transferFrom.test.ts @@ -44,7 +44,7 @@ await collection.mint(alice, 10n); await collection.approveTokens(alice, {Substrate: bob.address}, 7n); expect(await collection.getApprovedTokens({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(7n); - + await collection.transferFrom(bob, {Substrate: alice.address}, {Substrate: charlie.address}, 6n); expect(await collection.getBalance({Substrate: charlie.address})).to.be.equal(6n); expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(4n); @@ -56,7 +56,7 @@ const rft = await collection.mintToken(alice, 10n); await rft.approve(alice, {Substrate: bob.address}, 7n); expect(await rft.getApprovedPieces({Substrate: alice.address}, {Substrate: bob.address})).to.be.equal(7n); - + await rft.transferFrom(bob, {Substrate: alice.address}, {Substrate: charlie.address}, 6n); expect(await rft.getBalance({Substrate: charlie.address})).to.be.equal(6n); expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(4n); @@ -155,11 +155,11 @@ expect(await nft.isApproved({Substrate: bob.address})).to.be.true; await expect(helper.collection.transferTokenFrom( - bob, - collection.collectionId, - nft.tokenId, - {Substrate: alice.address}, - {Substrate: charlie.address}, + bob, + collection.collectionId, + nft.tokenId, + {Substrate: alice.address}, + {Substrate: charlie.address}, 2n, )).to.be.rejectedWith(/nonfungible\.NonfungibleItemsHaveNoAmount/); expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address}); @@ -202,7 +202,7 @@ await expect(nft.transferFrom( charlie, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.ApprovedValueTooLow/); expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address}); @@ -218,7 +218,7 @@ await expect(collection.transferFrom( charlie, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.ApprovedValueTooLow/); expect(await collection.getBalance({Substrate: alice.address})).to.be.deep.equal(10000n); @@ -236,7 +236,7 @@ await expect(rft.transferFrom( charlie, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.ApprovedValueTooLow/); expect(await rft.getBalance({Substrate: alice.address})).to.be.deep.equal(10000n); @@ -254,7 +254,7 @@ await expect(nft.transferFrom( bob, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.ApprovedValueTooLow/); }); @@ -269,7 +269,7 @@ await expect(collection.transferFrom( alice, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.TokenValueTooLow/); }); @@ -284,7 +284,7 @@ await expect(rft.transferFrom( alice, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.TokenValueTooLow/); }); @@ -300,7 +300,7 @@ await expect(nft.transferFrom( bob, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.ApprovedValueTooLow/); }); @@ -316,7 +316,7 @@ await expect(collection.transferFrom( bob, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.TokenValueTooLow/); }); @@ -332,7 +332,7 @@ await expect(rft.transferFrom( bob, - {Substrate: alice.address}, + {Substrate: alice.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.ApprovedValueTooLow/); }); @@ -345,7 +345,7 @@ await expect(nft.transferFrom( alice, - {Substrate: bob.address}, + {Substrate: bob.address}, {Substrate: charlie.address}, )).to.be.rejectedWith(/common\.ApprovedValueTooLow/); }); --- a/tests/src/util/globalSetup.ts +++ b/tests/src/util/globalSetup.ts @@ -17,10 +17,10 @@ // 2. Create donors for test files await fundFilenamesWithRetries(3) .then((result) => { - if (!result) Promise.reject(); + if (!result) throw Error('Some problems with fundFilenamesWithRetries'); }); - // 3. Configure App Promotion + // 3. Configure App Promotion const missingPallets = helper.fetchMissingPalletNames([Pallets.AppPromotion]); if (missingPallets.length === 0) { const superuser = await privateKey('//Alice'); @@ -38,7 +38,7 @@ } } catch (error) { console.error(error); - Promise.reject(); + throw Error('Error during globalSetup'); } }); }; @@ -78,7 +78,7 @@ if (aliceBalance < MINIMUM_DONOR_FUND * oneToken) { tx.push(helper.executeExtrinsic( - alice, + alice, 'api.tx.balances.transfer', [account.address, DONOR_FUNDING * oneToken], true, --- a/tests/src/util/index.ts +++ b/tests/src/util/index.ts @@ -41,7 +41,7 @@ else { const actualSeed = getTestSeed(seed.filename); let account = helper.util.fromSeed(actualSeed, ss58Format); - // here's to hoping that no + // here's to hoping that no if (!seed.ignoreFundsPresence && ((helper as any)['balance'] == undefined || await (helper as any).balance.getSubstrate(account.address) < MINIMUM_DONOR_FUND)) { console.warn(`${path.basename(seed.filename)}: Not enough funds present on the filename account. Using the default one as the donor instead.`); account = helper.util.fromSeed('//Alice', ss58Format); @@ -115,7 +115,7 @@ export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: string[]) { const missingPallets = helper.fetchMissingPalletNames(requiredPallets); - + if (missingPallets.length > 0) { const skipMsg = `\tSkipping test '${test.test?.title}'.\n\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; console.warn('\x1b[38:5:208m%s\x1b[0m', skipMsg); @@ -124,13 +124,13 @@ } export function itSub(name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => Promise }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) { - (opts.only ? it.only : + (opts.only ? it.only : opts.skip ? it.skip : it)(name, async function () { await usingPlaygrounds(async (helper, privateKey) => { if (opts.requiredPallets) { requirePalletsOrSkip(this, helper, opts.requiredPallets); } - + await cb({helper, privateKey}); }); }); --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -26,7 +26,7 @@ export interface ISubscribeBlockEventsData { number: number; hash: string; - timestamp: number; + timestamp: number; events: IEvent[]; } @@ -56,7 +56,7 @@ connected?: (...args: any[]) => any; disconnected?: (...args: any[]) => any; error?: (...args: any[]) => any; - ready?: (...args: any[]) => any; + ready?: (...args: any[]) => any; decorated?: (...args: any[]) => any; } --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -35,7 +35,7 @@ this.consoleWarn = console.warn; } - enable() { + enable() { const outFn = (printer: any) => (...args: any[]) => { for (const arg of args) { if (typeof arg !== 'string') @@ -45,7 +45,7 @@ } printer(...args); }; - + console.error = outFn(this.consoleErr.bind(console)); console.log = outFn(this.consoleLog.bind(console)); console.warn = outFn(this.consoleWarn.bind(console)); @@ -188,11 +188,11 @@ } /** - * Generates accounts with the specified UNQ token balance + * Generates accounts with the specified UNQ token balance * @param balances balances for generated accounts. Each balance will be multiplied by the token nominal. * @param donor donor account for balances * @returns array of newly created accounts - * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor); + * @example const [acc1, acc2, acc3] = await createAccounts([0n, 10n, 20n], donor); */ createAccounts = async (balances: bigint[], donor: IKeyringPair): Promise => { let nonce = await this.helper.chain.getNonce(donor.address); @@ -212,7 +212,7 @@ } await Promise.all(transactions).catch(_e => {}); - + //#region TODO remove this region, when nonce problem will be solved const checkBalances = async () => { let isSuccess = true; @@ -242,7 +242,7 @@ }; // TODO combine this method and createAccounts into one - createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise => { + createCrowd = async (accountsToCreate: number, withBalance: bigint, donor: IKeyringPair): Promise => { const createAsManyAsCan = async () => { let transactions: any = []; const accounts: IKeyringPair[] = []; @@ -250,9 +250,9 @@ const tokenNominal = this.helper.balance.getOneTokenNominal(); for (let i = 0; i < accountsToCreate; i++) { if (i === 500) { // if there are too many accounts to create - await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled + await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled transactions = []; // - nonce = await this.helper.chain.getNonce(donor.address); // update nonce + nonce = await this.helper.chain.getNonce(donor.address); // update nonce } const recepient = this.helper.util.fromSeed(mnemonicGenerate()); accounts.push(recepient); @@ -262,7 +262,7 @@ nonce++; } } - + const fullfilledAccounts = []; await Promise.allSettled(transactions); for (const account of accounts) { @@ -274,7 +274,7 @@ return fullfilledAccounts; }; - + const crowd: IKeyringPair[] = []; // do up to 5 retries for (let index = 0; index < 5 && accountsToCreate !== 0; index++) { @@ -291,7 +291,7 @@ isDevNode = async () => { let blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON(); if (blockNumber == 0) { - await this.helper.wait.newBlocks(1); + await this.helper.wait.newBlocks(1); blockNumber = (await this.helper.callRpc('api.query.system.number')).toJSON(); } const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [blockNumber])]); @@ -310,15 +310,15 @@ const block2date = await findCreationDate(block2); if(block2date! - block1date! < 9000) return true; }; - + async calculcateFee(payer: ICrossAccountId, promise: () => Promise): Promise { const address = payer.Substrate ? payer.Substrate : await this.helper.address.ethToSubstrate(payer.Ethereum!); - let balance = await this.helper.balance.getSubstrate(address); - + let balance = await this.helper.balance.getSubstrate(address); + await promise(); - + balance -= await this.helper.balance.getSubstrate(address); - + return balance; } @@ -335,7 +335,7 @@ const scheduledId = '0x' + '0'.repeat(prefixSize) + hexId; - return scheduledId; + return scheduledId; } const ids = []; @@ -424,7 +424,7 @@ /** * Wait for specified number of blocks * @param blocksCount number of blocks to wait - * @returns + * @returns */ async newBlocks(blocksCount = 1, timeout?: number): Promise { timeout = timeout ?? blocksCount * 60_000; @@ -457,7 +457,7 @@ await this.waitWithTimeout(promise, timeout); return promise; } - + async forRelayBlockNumber(blockNumber: bigint | number, timeout?: number) { timeout = timeout ?? 30 * 60 * 1000; // eslint-disable-next-line no-async-promise-executor @@ -476,7 +476,7 @@ noScheduledTasks() { const api = this.helper.getApi(); - + // eslint-disable-next-line no-async-promise-executor const promise = new Promise(async resolve => { const unsubscribe = await api.rpc.chain.subscribeNewHeads(async () => { @@ -486,7 +486,7 @@ unsubscribe(); resolve(); } - }); + }); }); return promise; @@ -500,16 +500,16 @@ const blockHash = header.hash; const eventIdStr = `${eventSection}.${eventMethod}`; const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; - + this.helper.logger.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); - + const apiAt = await this.helper.getApi().at(blockHash); const eventRecords = (await apiAt.query.system.events()) as any; - + const neededEvent = eventRecords.toArray().find((r: FrameSystemEventRecord) => { return r.event.section == eventSection && r.event.method == eventMethod; }); - + if (neededEvent) { unsubscribe(); resolve(neededEvent); --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -1103,7 +1103,7 @@ async getPropertiesConsumedSpace(collectionId: number): Promise { const api = this.helper.getApi(); const props = (await api.query.common.collectionProperties(collectionId)).toJSON(); - + return (props! as any).consumedSpace; } @@ -2423,7 +2423,7 @@ /** * Get schedule for recepient of vested transfer * @param address Substrate address of recipient - * @returns + * @returns */ async getVestingSchedules(address: TSubstrateAccount): Promise<{start: bigint, period: bigint, periodCount: bigint, perPeriod: bigint}[]> { const schedule = (await this.helper.callRpc('api.query.vesting.vestingSchedules', [address])).toJSON(); @@ -2506,16 +2506,16 @@ : typeof key === 'bigint' ? hexToU8a(key.toString(16)) : key; - + if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) { throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`); } - + const allowedDecodedLengths = [1, 2, 4, 8, 32, 33]; if (!allowedDecodedLengths.includes(u8a.length)) { throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`); } - + const u8aPrefix = ss58Format < 64 ? new Uint8Array([ss58Format]) : new Uint8Array([ @@ -2524,7 +2524,7 @@ ]); const input = u8aConcat(u8aPrefix, u8a); - + return base58Encode(u8aConcat( input, blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1), @@ -2556,7 +2556,7 @@ if (ethCrossAccount.sub === '0') { return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()}; } - + const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.sub)); return {Substrate: ss58}; } @@ -3074,14 +3074,14 @@ executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise { const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams); - + const mandatorySchedArgs = [ this.blocksNum, this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null, this.options.priority ?? null, scheduledTx, ]; - + let schedArgs; let scheduleFn; @@ -3301,7 +3301,7 @@ async getTokenPropertiesConsumedSpace(tokenId: number): Promise { const api = this.helper.getApi(); const props = (await api.query.nonfungible.tokenProperties(this.collectionId, tokenId)).toJSON(); - + return (props! as any).consumedSpace; } @@ -3419,7 +3419,7 @@ async getTokenPropertiesConsumedSpace(tokenId: number): Promise { const api = this.helper.getApi(); const props = (await api.query.refungible.tokenProperties(this.collectionId, tokenId)).toJSON(); - + return (props! as any).consumedSpace; } --- a/tests/src/vesting.test.ts +++ b/tests/src/vesting.test.ts @@ -73,7 +73,7 @@ expect(balanceRecepient.feeFrozen).to.eq(250n * nominal); expect(balanceRecepient.miscFrozen).to.eq(250n * nominal); expect(balanceRecepient.reserved).to.eq(0n); - + // Wait first schedule ends and first part od second schedule: await helper.wait.forRelayBlockNumber(SCHEDULE_2_START + SCHEDULE_2_PERIOD); await helper.balance.claim(recepient); @@ -84,7 +84,7 @@ expect(balanceRecepient.feeFrozen).to.eq(100n * nominal); expect(balanceRecepient.miscFrozen).to.eq(100n * nominal); expect(balanceRecepient.reserved).to.eq(0n); - + // Schedules list contain 1 vesting: schedule = await helper.balance.getVestingSchedules(recepient.address); expect(schedule).to.has.length(1); @@ -100,7 +100,7 @@ expect(balanceRecepient.feeFrozen).to.eq(0n); expect(balanceRecepient.miscFrozen).to.eq(0n); expect(balanceRecepient.reserved).to.eq(0n); - + // check sender balance does not changed: balanceSender = await helper.balance.getSubstrateFull(sender.address); expect(balanceSender.free / nominal).to.eq(699n); --- a/tests/src/xcm/xcmOpal.test.ts +++ b/tests/src/xcm/xcmOpal.test.ts @@ -37,12 +37,12 @@ const TRANSFER_AMOUNT = 1_000_000_000_000_000_000n; // 10,000.00 (ten thousands) USDT -const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n; +const ASSET_AMOUNT = 1_000_000_000_000_000_000_000n; describeXCM('[XCM] Integration test: Exchanging USDT with Westmint', () => { let alice: IKeyringPair; let bob: IKeyringPair; - + let balanceStmnBefore: bigint; let balanceStmnAfter: bigint; @@ -66,7 +66,7 @@ await usingWestmintPlaygrounds(westmintUrl, async (helper) => { // 350.00 (three hundred fifty) DOT - const fundingAmount = 3_500_000_000_000n; + const fundingAmount = 3_500_000_000_000n; await helper.assets.create(alice, ASSET_ID, alice.address, ASSET_METADATA_MINIMAL_BALANCE); await helper.assets.setMetadata(alice, ASSET_ID, ASSET_METADATA_NAME, ASSET_METADATA_DESCRIPTION, ASSET_METADATA_DECIMALS); @@ -151,7 +151,7 @@ await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); }); - + }); itSub('Should connect and send USDT from Westmint to Opal', async ({helper}) => { @@ -190,7 +190,7 @@ }, { GeneralIndex: ASSET_ID, - }, + }, ]}, }, }, @@ -266,7 +266,7 @@ }, //10_000_000_000_000_000n, TRANSFER_AMOUNT, - ], + ], [ { NativeAssetId: 'Parent', @@ -278,16 +278,16 @@ const feeItem = 1; await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited'); - + // the commission has been paid in parachain native token balanceOpalFinal = await helper.balance.getSubstrate(alice.address); expect(balanceOpalAfter > balanceOpalFinal).to.be.true; await usingWestmintPlaygrounds(westmintUrl, async (helper) => { await helper.wait.newBlocks(3); - + // The USDT token never paid fees. Its amount not changed from begin value. - // Also check that xcm transfer has been succeeded + // Also check that xcm transfer has been succeeded expect((await helper.assets.account(ASSET_ID, alice.address))! == ASSET_AMOUNT).to.be.true; }); }); @@ -341,13 +341,13 @@ await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); }); - + await helper.wait.newBlocks(3); - balanceBobAfter = await helper.balance.getSubstrate(bob.address); + balanceBobAfter = await helper.balance.getSubstrate(bob.address); balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'}); - const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; + const wndFee = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; console.log( 'Relay (Westend) to Opal transaction fees: %s OPL', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore), --- a/tests/src/xcm/xcmQuartz.test.ts +++ b/tests/src/xcm/xcmQuartz.test.ts @@ -38,7 +38,7 @@ const TRANSFER_AMOUNT = 2000000000000000000000000n; -const FUNDING_AMOUNT = 3_500_000_0000_000_000n; +const FUNDING_AMOUNT = 3_500_000_0000_000_000n; const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n; @@ -52,7 +52,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => { let alice: IKeyringPair; let bob: IKeyringPair; - + let balanceStmnBefore: bigint; let balanceStmnAfter: bigint; @@ -81,7 +81,7 @@ }); await usingStateminePlaygrounds(statemineUrl, async (helper) => { - const sovereignFundingAmount = 3_500_000_000n; + const sovereignFundingAmount = 3_500_000_000n; await helper.assets.create( alice, @@ -185,7 +185,7 @@ await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); }); - + }); itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => { @@ -224,7 +224,7 @@ }, { GeneralIndex: USDT_ASSET_ID, - }, + }, ]}, }, }, @@ -267,7 +267,7 @@ console.log( '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore), - ); + ); // commission has not paid in USDT token expect(free).to.be.equal(TRANSFER_AMOUNT); // ... and parachain native token @@ -299,7 +299,7 @@ ForeignAssetId: 0, }, TRANSFER_AMOUNT, - ], + ], [ { NativeAssetId: 'Parent', @@ -311,7 +311,7 @@ const feeItem = 1; await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited'); - + // the commission has been paid in parachain native token balanceQuartzFinal = await helper.balance.getSubstrate(alice.address); console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzFinal - balanceQuartzAfter)); @@ -319,9 +319,9 @@ await usingStateminePlaygrounds(statemineUrl, async (helper) => { await helper.wait.newBlocks(3); - + // The USDT token never paid fees. Its amount not changed from begin value. - // Also check that xcm transfer has been succeeded + // Also check that xcm transfer has been succeeded expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true; }); }); @@ -372,10 +372,10 @@ await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); }); - + await helper.wait.newBlocks(3); - balanceBobAfter = await helper.balance.getSubstrate(bob.address); + balanceBobAfter = await helper.balance.getSubstrate(bob.address); balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'}); const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; --- a/tests/src/xcm/xcmUnique.test.ts +++ b/tests/src/xcm/xcmUnique.test.ts @@ -38,7 +38,7 @@ const TRANSFER_AMOUNT = 2000000000000000000000000n; -const FUNDING_AMOUNT = 3_500_000_0000_000_000n; +const FUNDING_AMOUNT = 3_500_000_0000_000_000n; const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n; @@ -52,7 +52,7 @@ describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => { let alice: IKeyringPair; let bob: IKeyringPair; - + let balanceStmnBefore: bigint; let balanceStmnAfter: bigint; @@ -81,7 +81,7 @@ }); await usingStatemintPlaygrounds(statemintUrl, async (helper) => { - const sovereignFundingAmount = 3_500_000_000n; + const sovereignFundingAmount = 3_500_000_000n; await helper.assets.create( alice, @@ -185,7 +185,7 @@ await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); }); - + }); itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => { @@ -224,7 +224,7 @@ }, { GeneralIndex: USDT_ASSET_ID, - }, + }, ]}, }, }, @@ -267,7 +267,7 @@ console.log( '[Statemint -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore), - ); + ); // commission has not paid in USDT token expect(free).to.be.equal(TRANSFER_AMOUNT); // ... and parachain native token @@ -299,7 +299,7 @@ ForeignAssetId: 0, }, TRANSFER_AMOUNT, - ], + ], [ { NativeAssetId: 'Parent', @@ -311,7 +311,7 @@ const feeItem = 1; await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited'); - + // the commission has been paid in parachain native token balanceUniqueFinal = await helper.balance.getSubstrate(alice.address); console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueFinal - balanceUniqueAfter)); @@ -319,9 +319,9 @@ await usingStatemintPlaygrounds(statemintUrl, async (helper) => { await helper.wait.newBlocks(3); - + // The USDT token never paid fees. Its amount not changed from begin value. - // Also check that xcm transfer has been succeeded + // Also check that xcm transfer has been succeeded expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true; }); }); @@ -372,10 +372,10 @@ await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); }); - + await helper.wait.newBlocks(3); - balanceBobAfter = await helper.balance.getSubstrate(bob.address); + balanceBobAfter = await helper.balance.getSubstrate(bob.address); balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'}); const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore; @@ -909,7 +909,7 @@ const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address); expect(unqRandomAccountAsset).to.be.null; - + balanceForeignUnqTokenFinal = 0n; const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal; --- a/tests/yarn.lock +++ b/tests/yarn.lock @@ -1065,7 +1065,7 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^5.40.1": +"@typescript-eslint/eslint-plugin@^5.47.0": version "5.47.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.0.tgz#dadb79df3b0499699b155839fd6792f16897d910" integrity sha512-AHZtlXAMGkDmyLuLZsRpH3p4G/1iARIwc/T0vIem2YB+xW6pZaXYXzCBnZSF/5fdM97R9QqZWZ+h3iW10XgevQ== @@ -1080,7 +1080,7 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.40.1": +"@typescript-eslint/parser@^5.47.0": version "5.47.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.47.0.tgz#62e83de93499bf4b500528f74bf2e0554e3a6c8d" integrity sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==