From 4b0f7ea4eccee5453da767572d818fc34a44faf5 Mon Sep 17 00:00:00 2001 From: rkv Date: Tue, 04 Oct 2022 08:46:48 +0000 Subject: [PATCH] merged --- --- a/.docker/Dockerfile-chain-dev-unit +++ b/.docker/Dockerfile-chain-dev-unit @@ -23,4 +23,4 @@ WORKDIR /dev_chain -CMD cargo test --features=limit-testing +CMD cargo test --features=limit-testing --workspace --- /dev/null +++ b/.docker/Dockerfile-testnet.j2 @@ -0,0 +1,75 @@ +# ===== Rust builder ===== +FROM ubuntu:20.04 as rust-builder +LABEL maintainer="Unique.Network" + +ENV CARGO_HOME="/cargo-home" +ENV PATH="/cargo-home/bin:$PATH" +ENV TZ=UTC +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN apt-get update && \ + apt-get install -y curl cmake pkg-config libssl-dev git clang llvm libudev-dev && \ + apt-get clean && \ + rm -r /var/lib/apt/lists/* + +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain none + +RUN rustup toolchain uninstall $(rustup toolchain list) && \ + rustup toolchain install {{ RUST_TOOLCHAIN }} && \ + rustup default {{ RUST_TOOLCHAIN }} && \ + rustup target list --installed && \ + rustup show +RUN rustup target add wasm32-unknown-unknown --toolchain {{ RUST_TOOLCHAIN }} + +RUN mkdir /unique_parachain +WORKDIR /unique_parachain + +# ===== BUILD ====== +FROM rust-builder as builder-unique + +ARG PROFILE=release + +WORKDIR /unique_parachain + +RUN git clone -b {{ BRANCH }} https://github.com/UniqueNetwork/unique-chain.git && \ + cd unique-chain && \ + cargo build --features={{ FEATURE }} --$PROFILE + +# ===== RUN ====== + +FROM ubuntu:20.04 + +RUN apt-get -y update && \ + apt-get -y install curl git && \ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash && \ + export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + nvm install v16.16.0 && \ + nvm use v16.16.0 + +RUN git clone https://github.com/uniquenetwork/polkadot-launch -b {{ POLKADOT_LAUNCH_BRANCH }} + +RUN export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + npm install --global yarn && \ + yarn install + +COPY --from=builder-unique /unique_parachain/unique-chain/.docker/testnet-config/launch-config.json /polkadot-launch/launch-config.json +COPY --from=builder-unique /unique_parachain/unique-chain/target/release/unique-collator /unique-chain/target/release/ + +COPY --from=uniquenetwork/builder-polkadot:{{ POLKADOT_BUILD_BRANCH }} /unique_parachain/polkadot/target/release/polkadot /polkadot/target/release/ + +EXPOSE 9844 +EXPOSE 9944 +EXPOSE 9933 +EXPOSE 9833 +EXPOSE 40333 +EXPOSE 30333 + +CMD export NVM_DIR="$HOME/.nvm" && \ + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \ + cd /polkadot-launch && \ + yarn start launch-config.json + + \ No newline at end of file --- /dev/null +++ b/.docker/testnet-config/launch-config.json @@ -0,0 +1,121 @@ +{ + "relaychain": { + "bin": "/polkadot/target/release/polkadot", + "chain": "rococo-local", + "nodes": [ + { + "name": "alice", + "wsPort": 9844, + "rpcPort": 9843, + "port": 30444, + "flags": [ + "-lparachain::candidate_validation=debug", + "-lxcm=trace", + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + }, + { + "name": "bob", + "wsPort": 9855, + "rpcPort": 9854, + "port": 30555, + "flags": [ + "-lparachain::candidate_validation=debug", + "-lxcm=trace", + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + }, + { + "name": "charlie", + "wsPort": 9866, + "rpcPort": 9865, + "port": 30666, + "flags": [ + "-lparachain::candidate_validation=debug", + "-lxcm=trace", + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + }, + { + "name": "dave", + "wsPort": 9877, + "rpcPort": 9876, + "port": 30777, + "flags": [ + "-lparachain::candidate_validation=debug", + "-lxcm=trace", + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + }, + { + "name": "eve", + "wsPort": 9888, + "rpcPort": 9887, + "port": 30888, + "flags": [ + "-lparachain::candidate_validation=debug", + "-lxcm=trace", + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external" + ] + } + ], + "genesis": { + "runtime": { + "runtime_genesis_config": { + "parachainsConfiguration": { + "config": { + "validation_upgrade_frequency": 1, + "validation_upgrade_delay": 1 + } + } + } + } + } + }, + "parachains": [ + { + "bin": "/unique-chain/target/release/unique-collator", + "id": "1000", + "balance": "1000000000000000000000000", + "nodes": [ + { + "port": 31200, + "wsPort": 9944, + "rpcPort": 9933, + "name": "alice", + "flags": [ + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lxcm=trace" + ] + }, + { + "port": 31201, + "wsPort": 9945, + "rpcPort": 9934, + "name": "bob", + "flags": [ + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lxcm=trace" + ] + } + ] + } + ], + "simpleParachains": [], + "hrmpChannels": [], + "finalization": false +} --- a/.github/workflows/ci-develop.yml +++ b/.github/workflows/ci-develop.yml @@ -3,7 +3,7 @@ on: pull_request: branches: [ 'develop' ] - types: [ opened, reopened, synchronize, ready_for_review ] + types: [ opened, reopened, synchronize, ready_for_review, converted_to_draft ] concurrency: group: ${{ github.workflow }}-${{ github.head_ref }} @@ -12,23 +12,28 @@ jobs: yarn-test-dev: + if: github.event.pull_request.draft == false uses: ./.github/workflows/dev-build-tests_v2.yml + unit-test: + if: github.event.pull_request.draft == false uses: ./.github/workflows/unit-test_v2.yml canary: - if: ${{ contains( github.event.pull_request.labels.*.name, 'canary') }} + if: ${{ (github.event.pull_request.draft == false && contains( github.event.pull_request.labels.*.name, 'canary')) }} uses: ./.github/workflows/canary.yml secrets: inherit # pass all secrets xcm: - if: ${{ contains( github.event.pull_request.labels.*.name, 'xcm') }} + if: ${{ (github.event.pull_request.draft == false && contains( github.event.pull_request.labels.*.name, 'xcm')) }} uses: ./.github/workflows/xcm.yml secrets: inherit # pass all secrets codestyle: + if: github.event.pull_request.draft == false uses: ./.github/workflows/codestyle_v2.yml yarn_eslint: + if: github.event.pull_request.draft == false uses: ./.github/workflows/test_codestyle_v2.yml --- /dev/null +++ b/.github/workflows/testnet-build.yml @@ -0,0 +1,144 @@ +name: testnet-build + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + pull_request: + branches: + - master + types: + - opened + - reopened + - synchronize #commit(s) pushed to the pull request + - ready_for_review + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +#Define Workflow variables +env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + + prepare-execution-marix: + + name: Prepare execution matrix + + runs-on: [self-hosted-ci,medium] + outputs: + matrix: ${{ steps.create_matrix.outputs.matrix }} + + 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@v1.0.2 + + - name: Create Execution matrix + uses: fabiocaccamo/create-matrix-action@v2 + id: create_matrix + with: + matrix: | + network {opal}, runtime {opal}, features {opal-runtime} + network {quartz}, runtime {quartz}, features {quartz-runtime} + network {unique}, runtime {unique}, features {unique-runtime} + + testnet-build: + needs: prepare-execution-marix + # The type of runner that the job will run on + runs-on: [self-hosted-ci,medium] + + timeout-minutes: 600 + + 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: ${{fromJson(needs.prepare-execution-marix.outputs.matrix)}} + + steps: + - name: Skip if pull request is in Draft + if: github.event.pull_request.draft == true + run: exit 1 + + - name: Clean Workspace + uses: AutoModality/action-clean@v1.1.0 + + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} #Checking out head commit + + - name: Read .env file + uses: xom9ikk/dotenv@v1.0.2 + + - name: Generate ENV related extend file for docker-compose + uses: cuchi/jinja2-action@v1.2.0 + with: + template: .docker/Dockerfile-testnet.j2 + output_file: .docker/Dockerfile-testnet.${{ matrix.network }}.yml + variables: | + RUST_TOOLCHAIN=${{ env.RUST_TOOLCHAIN }} + NETWORK=${{ matrix.network }} + POLKADOT_BUILD_BRANCH=${{ env.POLKADOT_BUILD_BRANCH }} + POLKADOT_LAUNCH_BRANCH=${{ env.POLKADOT_LAUNCH_BRANCH }} + FEATURE=${{ matrix.features }} + RUNTIME=${{ matrix.runtime }} + BRANCH=${{ github.head_ref }} + + - name: Show build configuration + run: cat .docker/Dockerfile-testnet.${{ matrix.network }}.yml + + - name: Show launch-config configuration + run: cat launch-config.json + + - name: Run find-and-replace to remove slashes from branch name + uses: mad9000/actions-find-and-replace-string@2 + id: branchname + with: + source: ${{ github.head_ref }} + find: '/' + replace: '-' + + - name: Log in to Docker Hub + uses: docker/login-action@v2.0.0 + with: + username: ${{ secrets.CORE_DOCKERHUB_USERNAME }} + password: ${{ secrets.CORE_DOCKERHUB_TOKEN }} + + - name: Pull polkadot docker image + run: docker pull uniquenetwork/builder-polkadot:${{ env.POLKADOT_BUILD_BRANCH }} + + - name: Build the stack + run: cd .docker/ && docker build --file ./Dockerfile-testnet.${{ matrix.network }}.yml --tag uniquenetwork/${{ matrix.network }}-testnet-local:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} --tag uniquenetwork/${{ matrix.network }}-testnet-local:latest . + + - name: Push docker version image + run: docker push uniquenetwork/${{ matrix.network }}-testnet-local:nightly-${{ steps.branchname.outputs.value }}-${{ github.sha }} + + - name: Push docker latest image + run: docker push uniquenetwork/${{ matrix.network }}-testnet-local:latest + + - name: Clean Workspace + if: always() + uses: AutoModality/action-clean@v1.1.0 + + - name: Remove builder cache + if: always() # run this step always + run: | + docker builder prune -f + docker system prune -f --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -139,25 +139,26 @@ save(self) } - /// Set the substrate sponsor of the collection. - /// - /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. - /// - /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. - fn set_collection_sponsor_substrate( - &mut self, - caller: caller, - sponsor: uint256, - ) -> Result { - self.consume_store_reads_and_writes(1, 1)?; + // TODO: Temprorary off. Need refactor + // /// Set the substrate sponsor of the collection. + // /// + // /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. + // /// + // /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. + // fn set_collection_sponsor_substrate( + // &mut self, + // caller: caller, + // sponsor: uint256, + // ) -> Result { + // self.consume_store_reads_and_writes(1, 1)?; - check_is_owner_or_admin(caller, self)?; + // check_is_owner_or_admin(caller, self)?; - let sponsor = convert_uint256_to_cross_account::(sponsor); - self.set_sponsor(sponsor.as_sub().clone()) - .map_err(dispatch_to_evm::)?; - save(self) - } + // let sponsor = convert_uint256_to_cross_account::(sponsor); + // self.set_sponsor(sponsor.as_sub().clone()) + // .map_err(dispatch_to_evm::)?; + // save(self) + // } /// Whether there is a pending sponsor. fn has_collection_pending_sponsor(&self) -> Result { @@ -299,35 +300,37 @@ Ok(crate::eth::collection_id_to_address(self.id)) } - /// Add collection admin by substrate address. - /// @param newAdmin Substrate administrator address. - fn add_collection_admin_substrate( - &mut self, - caller: caller, - new_admin: uint256, - ) -> Result { - self.consume_store_writes(2)?; + // TODO: Temprorary off. Need refactor + // /// Add collection admin by substrate address. + // /// @param newAdmin Substrate administrator address. + // fn add_collection_admin_substrate( + // &mut self, + // caller: caller, + // new_admin: uint256, + // ) -> Result { + // self.consume_store_writes(2)?; - let caller = T::CrossAccountId::from_eth(caller); - let new_admin = convert_uint256_to_cross_account::(new_admin); - >::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::)?; - Ok(()) - } + // let caller = T::CrossAccountId::from_eth(caller); + // let new_admin = convert_uint256_to_cross_account::(new_admin); + // >::toggle_admin(self, &caller, &new_admin, true).map_err(dispatch_to_evm::)?; + // Ok(()) + // } - /// Remove collection admin by substrate address. - /// @param admin Substrate administrator address. - fn remove_collection_admin_substrate( - &mut self, - caller: caller, - admin: uint256, - ) -> Result { - self.consume_store_writes(2)?; + // TODO: Temprorary off. Need refactor + // /// Remove collection admin by substrate address. + // /// @param admin Substrate administrator address. + // fn remove_collection_admin_substrate( + // &mut self, + // caller: caller, + // admin: uint256, + // ) -> Result { + // self.consume_store_writes(2)?; - let caller = T::CrossAccountId::from_eth(caller); - let admin = convert_uint256_to_cross_account::(admin); - >::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::)?; - Ok(()) - } + // let caller = T::CrossAccountId::from_eth(caller); + // let admin = convert_uint256_to_cross_account::(admin); + // >::toggle_admin(self, &caller, &admin, false).map_err(dispatch_to_evm::)?; + // Ok(()) + // } /// Add collection admin. /// @param newAdmin Address of the added administrator. @@ -476,21 +479,22 @@ Ok(()) } - /// Add substrate user to allowed list. - /// - /// @param user User substrate address. - fn add_to_collection_allow_list_substrate( - &mut self, - caller: caller, - user: uint256, - ) -> Result { - self.consume_store_writes(1)?; + // TODO: Temprorary off. Need refactor + // /// Add substrate user to allowed list. + // /// + // /// @param user User substrate address. + // fn add_to_collection_allow_list_substrate( + // &mut self, + // caller: caller, + // user: uint256, + // ) -> Result { + // self.consume_store_writes(1)?; - let caller = T::CrossAccountId::from_eth(caller); - let user = convert_uint256_to_cross_account::(user); - Pallet::::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::)?; - Ok(()) - } + // let caller = T::CrossAccountId::from_eth(caller); + // let user = convert_uint256_to_cross_account::(user); + // Pallet::::toggle_allowlist(self, &caller, &user, true).map_err(dispatch_to_evm::)?; + // Ok(()) + // } /// Remove the user from the allowed list. /// @@ -504,21 +508,22 @@ Ok(()) } - /// Remove substrate user from allowed list. - /// - /// @param user User substrate address. - fn remove_from_collection_allow_list_substrate( - &mut self, - caller: caller, - user: uint256, - ) -> Result { - self.consume_store_writes(1)?; + // TODO: Temprorary off. Need refactor + // /// Remove substrate user from allowed list. + // /// + // /// @param user User substrate address. + // fn remove_from_collection_allow_list_substrate( + // &mut self, + // caller: caller, + // user: uint256, + // ) -> Result { + // self.consume_store_writes(1)?; - let caller = T::CrossAccountId::from_eth(caller); - let user = convert_uint256_to_cross_account::(user); - Pallet::::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::)?; - Ok(()) - } + // let caller = T::CrossAccountId::from_eth(caller); + // let user = convert_uint256_to_cross_account::(user); + // Pallet::::toggle_allowlist(self, &caller, &user, false).map_err(dispatch_to_evm::)?; + // Ok(()) + // } /// Switch permission for minting. /// @@ -551,14 +556,15 @@ Ok(self.is_owner_or_admin(&user)) } - /// Check that substrate account is the owner or admin of the collection - /// - /// @param user account to verify - /// @return "true" if account is the owner or admin - fn is_owner_or_admin_substrate(&self, user: uint256) -> Result { - let user = convert_uint256_to_cross_account::(user); - Ok(self.is_owner_or_admin(&user)) - } + // TODO: Temprorary off. Need refactor + // /// Check that substrate account is the owner or admin of the collection + // /// + // /// @param user account to verify + // /// @return "true" if account is the owner or admin + // fn is_owner_or_admin_substrate(&self, user: uint256) -> Result { + // let user = convert_uint256_to_cross_account::(user); + // Ok(self.is_owner_or_admin(&user)) + // } /// Returns collection type /// @@ -595,18 +601,19 @@ .map_err(dispatch_to_evm::) } - /// Changes collection owner to another substrate account - /// - /// @dev Owner can be changed only by current owner - /// @param newOwner new owner substrate account - fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result { - self.consume_store_writes(1)?; + // TODO: Temprorary off. Need refactor + // /// Changes collection owner to another substrate account + // /// + // /// @dev Owner can be changed only by current owner + // /// @param newOwner new owner substrate account + // fn set_owner_substrate(&mut self, caller: caller, new_owner: uint256) -> Result { + // self.consume_store_writes(1)?; - let caller = T::CrossAccountId::from_eth(caller); - let new_owner = convert_uint256_to_cross_account::(new_owner); - self.set_owner_internal(caller, new_owner) - .map_err(dispatch_to_evm::) - } + // let caller = T::CrossAccountId::from_eth(caller); + // let new_owner = convert_uint256_to_cross_account::(new_owner); + // self.set_owner_internal(caller, new_owner) + // .map_err(dispatch_to_evm::) + // } // TODO: need implement AbiWriter for &Vec // fn collection_admins(&self) -> Result> { --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -18,7 +18,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x47dbc105 +/// @dev the ERC-165 identifier for this interface is 0x3e1e8083 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -72,19 +72,6 @@ dummy = 0; } - /// Set the substrate sponsor of the collection. - /// - /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. - /// - /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0xc74d6751, - /// or in textual repr: setCollectionSponsorSubstrate(uint256) - function setCollectionSponsorSubstrate(uint256 sponsor) public { - require(false, stub_error); - sponsor; - dummy = 0; - } - /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, /// or in textual repr: hasCollectionPendingSponsor() @@ -167,26 +154,6 @@ return 0x0000000000000000000000000000000000000000; } - /// Add collection admin by substrate address. - /// @param newAdmin Substrate administrator address. - /// @dev EVM selector for this function is: 0x5730062b, - /// or in textual repr: addCollectionAdminSubstrate(uint256) - function addCollectionAdminSubstrate(uint256 newAdmin) public { - require(false, stub_error); - newAdmin; - dummy = 0; - } - - /// Remove collection admin by substrate address. - /// @param admin Substrate administrator address. - /// @dev EVM selector for this function is: 0x4048fcf9, - /// or in textual repr: removeCollectionAdminSubstrate(uint256) - function removeCollectionAdminSubstrate(uint256 admin) public { - require(false, stub_error); - admin; - dummy = 0; - } - /// Add collection admin. /// @param newAdmin Address of the added administrator. /// @dev EVM selector for this function is: 0x92e462c7, @@ -262,17 +229,6 @@ /// @dev EVM selector for this function is: 0x67844fe6, /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) public { - require(false, stub_error); - user; - dummy = 0; - } - - /// Add substrate user to allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xd06ad267, - /// or in textual repr: addToCollectionAllowListSubstrate(uint256) - function addToCollectionAllowListSubstrate(uint256 user) public { require(false, stub_error); user; dummy = 0; @@ -289,17 +245,6 @@ dummy = 0; } - /// Remove substrate user from allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xa31913ed, - /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) - function removeFromCollectionAllowListSubstrate(uint256 user) public { - require(false, stub_error); - user; - dummy = 0; - } - /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -324,19 +269,6 @@ return false; } - /// Check that substrate account is the owner or admin of the collection - /// - /// @param user account to verify - /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x68910e00, - /// or in textual repr: isOwnerOrAdminSubstrate(uint256) - function isOwnerOrAdminSubstrate(uint256 user) public view returns (bool) { - require(false, stub_error); - user; - dummy; - return false; - } - /// Returns collection type /// /// @return `Fungible` or `NFT` or `ReFungible` @@ -367,18 +299,6 @@ /// @dev EVM selector for this function is: 0x13af4035, /// or in textual repr: setOwner(address) function setOwner(address newOwner) public { - require(false, stub_error); - newOwner; - dummy = 0; - } - - /// Changes collection owner to another substrate account - /// - /// @dev Owner can be changed only by current owner - /// @param newOwner new owner substrate account - /// @dev EVM selector for this function is: 0xb212138f, - /// or in textual repr: setOwnerSubstrate(uint256) - function setOwnerSubstrate(uint256 newOwner) public { require(false, stub_error); newOwner; dummy = 0; --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -91,7 +91,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x47dbc105 +/// @dev the ERC-165 identifier for this interface is 0x3e1e8083 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -145,19 +145,6 @@ dummy = 0; } - /// Set the substrate sponsor of the collection. - /// - /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. - /// - /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0xc74d6751, - /// or in textual repr: setCollectionSponsorSubstrate(uint256) - function setCollectionSponsorSubstrate(uint256 sponsor) public { - require(false, stub_error); - sponsor; - dummy = 0; - } - /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, /// or in textual repr: hasCollectionPendingSponsor() @@ -240,26 +227,6 @@ return 0x0000000000000000000000000000000000000000; } - /// Add collection admin by substrate address. - /// @param newAdmin Substrate administrator address. - /// @dev EVM selector for this function is: 0x5730062b, - /// or in textual repr: addCollectionAdminSubstrate(uint256) - function addCollectionAdminSubstrate(uint256 newAdmin) public { - require(false, stub_error); - newAdmin; - dummy = 0; - } - - /// Remove collection admin by substrate address. - /// @param admin Substrate administrator address. - /// @dev EVM selector for this function is: 0x4048fcf9, - /// or in textual repr: removeCollectionAdminSubstrate(uint256) - function removeCollectionAdminSubstrate(uint256 admin) public { - require(false, stub_error); - admin; - dummy = 0; - } - /// Add collection admin. /// @param newAdmin Address of the added administrator. /// @dev EVM selector for this function is: 0x92e462c7, @@ -335,17 +302,6 @@ /// @dev EVM selector for this function is: 0x67844fe6, /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) public { - require(false, stub_error); - user; - dummy = 0; - } - - /// Add substrate user to allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xd06ad267, - /// or in textual repr: addToCollectionAllowListSubstrate(uint256) - function addToCollectionAllowListSubstrate(uint256 user) public { require(false, stub_error); user; dummy = 0; @@ -362,17 +318,6 @@ dummy = 0; } - /// Remove substrate user from allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xa31913ed, - /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) - function removeFromCollectionAllowListSubstrate(uint256 user) public { - require(false, stub_error); - user; - dummy = 0; - } - /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -397,19 +342,6 @@ return false; } - /// Check that substrate account is the owner or admin of the collection - /// - /// @param user account to verify - /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x68910e00, - /// or in textual repr: isOwnerOrAdminSubstrate(uint256) - function isOwnerOrAdminSubstrate(uint256 user) public view returns (bool) { - require(false, stub_error); - user; - dummy; - return false; - } - /// Returns collection type /// /// @return `Fungible` or `NFT` or `ReFungible` @@ -440,18 +372,6 @@ /// @dev EVM selector for this function is: 0x13af4035, /// or in textual repr: setOwner(address) function setOwner(address newOwner) public { - require(false, stub_error); - newOwner; - dummy = 0; - } - - /// Changes collection owner to another substrate account - /// - /// @dev Owner can be changed only by current owner - /// @param newOwner new owner substrate account - /// @dev EVM selector for this function is: 0xb212138f, - /// or in textual repr: setOwnerSubstrate(uint256) - function setOwnerSubstrate(uint256 newOwner) public { require(false, stub_error); newOwner; dummy = 0; --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -91,7 +91,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x47dbc105 +/// @dev the ERC-165 identifier for this interface is 0x3e1e8083 contract Collection is Dummy, ERC165 { /// Set collection property. /// @@ -145,19 +145,6 @@ dummy = 0; } - /// Set the substrate sponsor of the collection. - /// - /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. - /// - /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0xc74d6751, - /// or in textual repr: setCollectionSponsorSubstrate(uint256) - function setCollectionSponsorSubstrate(uint256 sponsor) public { - require(false, stub_error); - sponsor; - dummy = 0; - } - /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, /// or in textual repr: hasCollectionPendingSponsor() @@ -240,26 +227,6 @@ return 0x0000000000000000000000000000000000000000; } - /// Add collection admin by substrate address. - /// @param newAdmin Substrate administrator address. - /// @dev EVM selector for this function is: 0x5730062b, - /// or in textual repr: addCollectionAdminSubstrate(uint256) - function addCollectionAdminSubstrate(uint256 newAdmin) public { - require(false, stub_error); - newAdmin; - dummy = 0; - } - - /// Remove collection admin by substrate address. - /// @param admin Substrate administrator address. - /// @dev EVM selector for this function is: 0x4048fcf9, - /// or in textual repr: removeCollectionAdminSubstrate(uint256) - function removeCollectionAdminSubstrate(uint256 admin) public { - require(false, stub_error); - admin; - dummy = 0; - } - /// Add collection admin. /// @param newAdmin Address of the added administrator. /// @dev EVM selector for this function is: 0x92e462c7, @@ -335,17 +302,6 @@ /// @dev EVM selector for this function is: 0x67844fe6, /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) public { - require(false, stub_error); - user; - dummy = 0; - } - - /// Add substrate user to allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xd06ad267, - /// or in textual repr: addToCollectionAllowListSubstrate(uint256) - function addToCollectionAllowListSubstrate(uint256 user) public { require(false, stub_error); user; dummy = 0; @@ -362,17 +318,6 @@ dummy = 0; } - /// Remove substrate user from allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xa31913ed, - /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) - function removeFromCollectionAllowListSubstrate(uint256 user) public { - require(false, stub_error); - user; - dummy = 0; - } - /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -397,19 +342,6 @@ return false; } - /// Check that substrate account is the owner or admin of the collection - /// - /// @param user account to verify - /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x68910e00, - /// or in textual repr: isOwnerOrAdminSubstrate(uint256) - function isOwnerOrAdminSubstrate(uint256 user) public view returns (bool) { - require(false, stub_error); - user; - dummy; - return false; - } - /// Returns collection type /// /// @return `Fungible` or `NFT` or `ReFungible` @@ -440,18 +372,6 @@ /// @dev EVM selector for this function is: 0x13af4035, /// or in textual repr: setOwner(address) function setOwner(address newOwner) public { - require(false, stub_error); - newOwner; - dummy = 0; - } - - /// Changes collection owner to another substrate account - /// - /// @dev Owner can be changed only by current owner - /// @param newOwner new owner substrate account - /// @dev EVM selector for this function is: 0xb212138f, - /// or in textual repr: setOwnerSubstrate(uint256) - function setOwnerSubstrate(uint256 newOwner) public { require(false, stub_error); newOwner; dummy = 0; --- a/tests/package.json +++ b/tests/package.json @@ -88,8 +88,11 @@ "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts", "testEthCreateNFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createNFTCollection.test.ts", "testEthCreateRFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createRFTCollection.test.ts", + "testEthNFT": "mocha --timeout 9999999 -r ts-node/register ./**/eth/nonFungible.test.ts", "testRFT": "mocha --timeout 9999999 -r ts-node/register ./**/refungible.test.ts", + "testEthRFT": "mocha --timeout 9999999 -r ts-node/register ./**/eth/reFungible.test.ts ./**/eth/reFungibleToken.test.ts", "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts", + "testEthFT": "mocha --timeout 9999999 -r ts-node/register ./**/eth/fungible.test.ts", "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts", "testPromotion": "mocha --timeout 9999999 -r ts-node/register ./**/app-promotion.test.ts", "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json", --- a/tests/src/change-collection-owner.test.ts +++ b/tests/src/change-collection-owner.test.ts @@ -31,11 +31,11 @@ itSub('Changing owner changes owner address', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); const beforeChanging = await helper.collection.getData(collection.collectionId); - expect(beforeChanging?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(alice.address)); + expect(beforeChanging?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(alice.address)); await collection.changeOwner(alice, bob.address); const afterChanging = await helper.collection.getData(collection.collectionId); - expect(afterChanging?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(bob.address)); + expect(afterChanging?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(bob.address)); }); }); @@ -60,7 +60,7 @@ await expect(changeOwnerTx()).to.be.rejectedWith(/common\.NoPermission/); const afterChanging = await helper.collection.getData(collection.collectionId); - expect(afterChanging?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(bob.address)); + expect(afterChanging?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(bob.address)); }); itSub('New collectionOwner has access to sponsorship management operations in the collection', async ({helper}) => { @@ -68,7 +68,7 @@ await collection.changeOwner(alice, bob.address); const afterChanging = await helper.collection.getData(collection.collectionId); - expect(afterChanging?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(bob.address)); + expect(afterChanging?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(bob.address)); await collection.setSponsor(bob, charlie.address); await collection.confirmSponsorship(charlie); @@ -97,7 +97,7 @@ await collection.changeOwner(alice, bob.address); await collection.changeOwner(bob, charlie.address); const collectionData = await collection.getData(); - expect(collectionData?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(charlie.address)); + expect(collectionData?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(charlie.address)); }); }); @@ -140,7 +140,7 @@ await expect(changeOwnerTx()).to.be.rejectedWith(/common\.NoPermission/); const afterChanging = await helper.collection.getData(collection.collectionId); - expect(afterChanging?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(bob.address)); + expect(afterChanging?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(bob.address)); const setSponsorTx = async () => collection.setSponsor(alice, charlie.address); const confirmSponsorshipTx = async () => collection.confirmSponsorship(alice); --- a/tests/src/createCollection.test.ts +++ b/tests/src/createCollection.test.ts @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +import {IKeyringPair} from '@polkadot/types/types'; import {usingPlaygrounds, expect, itSub, Pallets} from './util/playgrounds'; -import {IKeyringPair} from '@polkadot/types/types'; import {ICollectionCreationOptions, IProperty} from './util/playgrounds/types'; -import {DevUniqueHelper} from './util/playgrounds/unique.dev'; +import {UniqueHelper} from './util/playgrounds/unique'; -async function mintCollectionHelper(helper: DevUniqueHelper, signer: IKeyringPair, options: ICollectionCreationOptions, type?: 'nft' | 'fungible' | 'refungible') { +async function mintCollectionHelper(helper: UniqueHelper, signer: IKeyringPair, options: ICollectionCreationOptions, type?: 'nft' | 'fungible' | 'refungible') { let collection; if (type === 'nft') { collection = await helper.nft.mintCollection(signer, options); @@ -29,7 +29,7 @@ collection = await helper.rft.mintCollection(signer, options); } const data = await collection.getData(); - expect(data?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(signer.address)); + expect(data?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(signer.address)); expect(data?.name).to.be.equal(options.name); expect(data?.description).to.be.equal(options.description); expect(data?.raw.tokenPrefix).to.be.equal(options.tokenPrefix); @@ -54,32 +54,27 @@ }); }); itSub('Create new NFT collection', async ({helper}) => { - await mintCollectionHelper(helper, alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}, 'nft'); }); itSub('Create new NFT collection whith collection_name of maximum length (64 bytes)', async ({helper}) => { - await mintCollectionHelper(helper, alice, {name: 'A'.repeat(64), description: 'descr', tokenPrefix: 'COL'}, 'nft'); }); itSub('Create new NFT collection whith collection_description of maximum length (256 bytes)', async ({helper}) => { - await mintCollectionHelper(helper, alice, {name: 'name', description: 'A'.repeat(256), tokenPrefix: 'COL'}, 'nft'); }); itSub('Create new NFT collection whith token_prefix of maximum length (16 bytes)', async ({helper}) => { - await mintCollectionHelper(helper, alice, {name: 'name', description: 'descr', tokenPrefix: 'A'.repeat(16)}, 'nft'); }); + itSub('Create new Fungible collection', async ({helper}) => { - await mintCollectionHelper(helper, alice, {name: 'name', description: 'descr', tokenPrefix: 'COL'}, 'fungible'); }); + itSub.ifWithPallets('Create new ReFungible collection', [Pallets.ReFungible], async ({helper}) => { - await mintCollectionHelper(helper, alice, {name: 'name', description: 'descr', tokenPrefix: 'COL'}, 'refungible'); }); itSub('create new collection with properties', async ({helper}) => { - await mintCollectionHelper(helper, alice, { name: 'name', description: 'descr', tokenPrefix: 'COL', properties: [{key: 'key1', value: 'val1'}], @@ -88,7 +83,6 @@ }); itSub('Create new collection with extra fields', async ({helper}) => { - const collection = await mintCollectionHelper(helper, alice, {name: 'name', description: 'descr', tokenPrefix: 'COL'}, 'fungible'); await collection.setPermissions(alice, {access: 'AllowList'}); await collection.setLimits(alice, {accountTokenOwnershipLimit: 3}); @@ -96,7 +90,7 @@ const limits = await collection.getEffectiveLimits(); const raw = data?.raw; - expect(data?.normalizedOwner).to.be.equal(helper.util.normalizeSubstrateAddress(alice.address)); + expect(data?.normalizedOwner).to.be.equal(helper.address.normalizeSubstrate(alice.address)); expect(data?.name).to.be.equal('name'); expect(data?.description).to.be.equal('descr'); expect(raw.permissions.access).to.be.equal('AllowList'); @@ -105,7 +99,6 @@ }); itSub('New collection is not external', async ({helper}) => { - const collection = await helper.nft.mintCollection(alice, {name: 'name', description: 'descr', tokenPrefix: 'COL'}); const data = await collection.getData(); expect(data?.raw.readOnly).to.be.false; @@ -131,12 +124,11 @@ await expect(mintCollectionTx()).to.be.rejectedWith('Verification Error'); }); itSub('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async ({helper}) => { - const mintCollectionTx = async () => 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 = async () => helper.nft.mintCollection(alice, {name: 'name', description: 'descr', tokenPrefix: 'COL', limits: {tokenLimit: 0}}); await expect(mintCollectionTx()).to.be.rejectedWith(/common\.CollectionTokenLimitExceeded/); }); --- a/tests/src/createItem.test.ts +++ b/tests/src/createItem.test.ts @@ -15,24 +15,14 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; - -import { - createCollection, - itApi, - normalizeAccountId, - getCreateItemResult, - CrossAccountId, -} from './util/helpers'; - import {usingPlaygrounds, expect, itSub, Pallets} from './util/playgrounds'; -import {IProperty} from './util/playgrounds/types'; -import {executeTransaction} from './substrate/substrate-api'; -import {DevUniqueHelper} from './util/playgrounds/unique.dev'; +import {IProperty, ICrossAccountId} from './util/playgrounds/types'; +import {UniqueHelper} from './util/playgrounds/unique'; -async function mintTokenHelper(helper: DevUniqueHelper, collection: any, signer: IKeyringPair, owner: CrossAccountId, type: 'nft' | 'fungible' | 'refungible'='nft', properties?: IProperty[]) { +async function mintTokenHelper(helper: UniqueHelper, collection: any, signer: IKeyringPair, owner: ICrossAccountId, type: 'nft' | 'fungible' | 'refungible'='nft', properties?: IProperty[]) { let token; const itemCountBefore = await helper.collection.getLastTokenId(collection.collectionId); - const itemBalanceBefore = (await helper.api!.rpc.unique.balance(collection.collectionId, owner, 0)).toBigInt(); + const itemBalanceBefore = (await helper.callRpc('api.rpc.unique.balance', [collection.collectionId, owner, 0])).toBigInt(); if (type === 'nft') { token = await collection.mintToken(signer, owner, properties); } else if (type === 'fungible') { @@ -42,7 +32,7 @@ } const itemCountAfter = await helper.collection.getLastTokenId(collection.collectionId); - const itemBalanceAfter = (await helper.api!.rpc.unique.balance(collection.collectionId, owner, 0)).toBigInt(); + const itemBalanceAfter = (await helper.callRpc('api.rpc.unique.balance', [collection.collectionId, owner, 0])).toBigInt(); if (type === 'fungible') { expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n); @@ -75,27 +65,22 @@ }); itSub('Check events on create new item in Fungible collection', async ({helper}) => { const {collectionId} = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'}, 0); - const api = helper.api!; - - - const to = normalizeAccountId(alice); + const to = {Substrate: alice.address}; { const createData = {fungible: {value: 100}}; - const tx = api.tx.unique.createItem(collectionId, to, createData as any); - const events = await executeTransaction(api, alice, tx); - const result = getCreateItemResult(events); - expect(result.amount).to.be.equal(100); - expect(result.collectionId).to.be.equal(collectionId); - expect(result.recipient).to.be.deep.equal(to); + const events = await helper.executeExtrinsic(alice, 'api.tx.unique.createItem', [collectionId, to, createData as any]); + const result = helper.util.extractTokensFromCreationResult(events); + expect(result.tokens[0].amount).to.be.equal(100n); + expect(result.tokens[0].collectionId).to.be.equal(collectionId); + expect(result.tokens[0].owner).to.be.deep.equal(to); } { const createData = {fungible: {value: 50}}; - const tx = api.tx.unique.createItem(collectionId, to, createData as any); - const events = await executeTransaction(api, alice, tx); - const result = getCreateItemResult(events); - expect(result.amount).to.be.equal(50); - expect(result.collectionId).to.be.equal(collectionId); - expect(result.recipient).to.be.deep.equal(to); + const events = await helper.executeExtrinsic(alice, 'api.tx.unique.createItem', [collectionId, to, createData as any]); + const result = helper.util.extractTokensFromCreationResult(events); + expect(result.tokens[0].amount).to.be.equal(50n); + expect(result.tokens[0].collectionId).to.be.equal(collectionId); + expect(result.tokens[0].owner).to.be.deep.equal(to); } }); itSub.ifWithPallets('Create new item in ReFungible collection', [Pallets.ReFungible], async ({helper}) => { @@ -162,12 +147,12 @@ const amount = 1n; const token = await mintTokenHelper(helper, collection, alice, {Substrate: bob.address}); { - const totalPieces = await helper.api?.rpc.unique.totalPieces(collection.collectionId, token.tokenId); + const totalPieces = await helper.callRpc('api.rpc.unique.totalPieces', [collection.collectionId, token.tokenId]); expect(totalPieces?.unwrap().toBigInt()).to.be.equal(amount); } await token.transfer(bob, {Substrate: alice.address}); { - const totalPieces = await helper.api?.rpc.unique.totalPieces(collection.collectionId, token.tokenId); + const totalPieces = await helper.callRpc('api.rpc.unique.totalPieces', [collection.collectionId, token.tokenId]); expect(totalPieces?.unwrap().toBigInt()).to.be.equal(amount); } }); @@ -267,21 +252,21 @@ itSub('Check total pieces for invalid Fungible token', async ({helper}) => { const collection = await helper.ft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}, 0); const invalidTokenId = 1_000_000; - expect((await helper.api?.rpc.unique.totalPieces(collection.collectionId, invalidTokenId))?.isNone).to.be.true; - expect((await helper.api?.rpc.unique.tokenData(collection.collectionId, invalidTokenId))?.pieces.toBigInt()).to.be.equal(0n); + expect((await helper.callRpc('api.rpc.unique.totalPieces', [collection.collectionId, invalidTokenId]))?.isNone).to.be.true; + expect((await helper.callRpc('api.rpc.unique.tokenData', [collection.collectionId, invalidTokenId]))?.pieces.toBigInt()).to.be.equal(0n); }); itSub('Check total pieces for invalid NFT token', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); const invalidTokenId = 1_000_000; - expect((await helper.api?.rpc.unique.totalPieces(collection.collectionId, invalidTokenId))?.isNone).to.be.true; - expect((await helper.api?.rpc.unique.tokenData(collection.collectionId, invalidTokenId))?.pieces.toBigInt()).to.be.equal(0n); + expect((await helper.callRpc('api.rpc.unique.totalPieces', [collection.collectionId, invalidTokenId]))?.isNone).to.be.true; + expect((await helper.callRpc('api.rpc.unique.tokenData', [collection.collectionId, invalidTokenId]))?.pieces.toBigInt()).to.be.equal(0n); }); itSub.ifWithPallets('Check total pieces for invalid Refungible token', [Pallets.ReFungible], async ({helper}) => { const collection = await helper.rft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); const invalidTokenId = 1_000_000; - expect((await helper.api?.rpc.unique.totalPieces(collection.collectionId, invalidTokenId))?.isNone).to.be.true; - expect((await helper.api?.rpc.unique.tokenData(collection.collectionId, invalidTokenId))?.pieces.toBigInt()).to.be.equal(0n); + expect((await helper.callRpc('api.rpc.unique.totalPieces', [collection.collectionId, invalidTokenId]))?.isNone).to.be.true; + expect((await helper.callRpc('api.rpc.unique.tokenData', [collection.collectionId, invalidTokenId]))?.pieces.toBigInt()).to.be.equal(0n); }); }); --- a/tests/src/createMultipleItems.test.ts +++ b/tests/src/createMultipleItems.test.ts @@ -15,12 +15,8 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import { - normalizeAccountId, -} from './util/helpers'; import {usingPlaygrounds, expect, Pallets, itSub} from './util/playgrounds'; - describe('Integration Test createMultipleItems(collection_id, owner, items_data):', () => { let alice: IKeyringPair; @@ -48,7 +44,7 @@ const tokens = await helper.nft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, args); for (const [i, token] of tokens.entries()) { const tokenData = await token.getData(); - expect(tokenData?.normalizedOwner).to.be.deep.equal({Substrate: helper.util.normalizeSubstrateAddress(alice.address)}); + expect(tokenData?.normalizedOwner.Substrate).to.be.deep.equal(helper.address.normalizeSubstrate(alice.address)); expect(tokenData?.properties[0].value).to.be.equal(args[i].properties[0].value); } }); @@ -116,7 +112,7 @@ const tokens = await helper.nft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, args); for (const [i, token] of tokens.entries()) { const tokenData = await token.getData(); - expect(tokenData?.normalizedOwner).to.be.deep.equal({Substrate: helper.util.normalizeSubstrateAddress(alice.address)}); + expect(tokenData?.normalizedOwner.Substrate).to.be.deep.equal(helper.address.normalizeSubstrate(alice.address)); expect(tokenData?.properties[0].value).to.be.equal(args[i].properties[0].value); } }); @@ -138,7 +134,7 @@ const tokens = await helper.nft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, args); for (const [i, token] of tokens.entries()) { const tokenData = await token.getData(); - expect(tokenData?.normalizedOwner).to.be.deep.equal({Substrate: helper.util.normalizeSubstrateAddress(alice.address)}); + expect(tokenData?.normalizedOwner.Substrate).to.be.deep.equal(helper.address.normalizeSubstrate(alice.address)); expect(tokenData?.properties[0].value).to.be.equal(args[i].properties[0].value); } }); @@ -160,7 +156,7 @@ const tokens = await helper.nft.mintMultipleTokensWithOneOwner(alice, collection.collectionId, {Substrate: alice.address}, args); for (const [i, token] of tokens.entries()) { const tokenData = await token.getData(); - expect(tokenData?.normalizedOwner).to.be.deep.equal({Substrate: helper.util.normalizeSubstrateAddress(alice.address)}); + expect(tokenData?.normalizedOwner.Substrate).to.be.equal(helper.address.normalizeSubstrate(alice.address)); expect(tokenData?.properties[0].value).to.be.equal(args[i].properties[0].value); } }); @@ -275,8 +271,11 @@ }); const types = ['NFT', 'Fungible', 'ReFungible']; - const mintTx = helper.api?.tx.unique.createMultipleItems(collectionId, normalizeAccountId(alice.address), types); - await expect(helper.signTransaction(alice, mintTx)).to.be.rejected; + await expect(helper.executeExtrinsic( + alice, + 'api.tx.unique.createMultipleItems', + [collectionId, {Substrate: alice.address}, types], + )).to.be.rejectedWith(/nonfungible\.NotNonfungibleDataUsedToMintFungibleCollectionToken/); }); itSub('Create tokens with different data limits <> maximum data limit', async ({helper}) => { --- a/tests/src/creditFeesToTreasury.test.ts +++ b/tests/src/creditFeesToTreasury.test.ts @@ -16,11 +16,6 @@ import './interfaces/augment-api-consts'; import {IKeyringPair} from '@polkadot/types/types'; -import { - UNIQUE, -} from './util/helpers'; - -import {default as waitNewBlocks} from './substrate/wait-new-blocks'; import {ApiPromise} from '@polkadot/api'; import {usingPlaygrounds, expect, itSub} from './util/playgrounds'; @@ -63,7 +58,7 @@ itSub('Total issuance does not change', async ({helper}) => { const api = helper.api!; await skipInflationBlock(api); - await waitNewBlocks(api, 1); + await helper.wait.newBlocks(1); const totalBefore = (await api.query.balances.totalIssuance()).toBigInt(); @@ -75,9 +70,8 @@ }); itSub('Sender balance decreased by fee+sent amount, Treasury balance increased by fee', async ({helper}) => { - const api = helper.api!; - await skipInflationBlock(api); - await waitNewBlocks(api, 1); + await skipInflationBlock(helper.api!); + await helper.wait.newBlocks(1); const treasuryBalanceBefore = await helper.balance.getSubstrate(TREASURY); const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); @@ -96,7 +90,7 @@ itSub('Treasury balance increased by failed tx fee', async ({helper}) => { const api = helper.api!; - await waitNewBlocks(api, 1); + await helper.wait.newBlocks(1); const treasuryBalanceBefore = await helper.balance.getSubstrate(TREASURY); const bobBalanceBefore = await helper.balance.getSubstrate(bob.address); @@ -113,9 +107,8 @@ }); itSub('NFT Transactions also send fees to Treasury', async ({helper}) => { - const api = helper.api!; - await skipInflationBlock(api); - await waitNewBlocks(api, 1); + await skipInflationBlock(helper.api!); + await helper.wait.newBlocks(1); const treasuryBalanceBefore = await helper.balance.getSubstrate(TREASURY); const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); @@ -131,9 +124,9 @@ }); itSub('Fees are sane', async ({helper}) => { - const api = helper.api!; - await skipInflationBlock(api); - await waitNewBlocks(api, 1); + const unique = helper.balance.getOneTokenNominal(); + await skipInflationBlock(helper.api!); + await helper.wait.newBlocks(1); const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); @@ -142,14 +135,13 @@ const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address); const fee = aliceBalanceBefore - aliceBalanceAfter; - expect(fee / UNIQUE < BigInt(Math.ceil(saneMaximumFee + createCollectionDeposit))).to.be.true; - expect(fee / UNIQUE < BigInt(Math.ceil(saneMinimumFee + createCollectionDeposit))).to.be.true; + expect(fee / unique < BigInt(Math.ceil(saneMaximumFee + createCollectionDeposit))).to.be.true; + expect(fee / unique < BigInt(Math.ceil(saneMinimumFee + createCollectionDeposit))).to.be.true; }); itSub('NFT Transfer fee is close to 0.1 Unique', async ({helper}) => { - const api = helper.api!; - await skipInflationBlock(api); - await waitNewBlocks(api, 1); + await skipInflationBlock(helper.api!); + await helper.wait.newBlocks(1); const collection = await helper.nft.mintCollection(alice, { name: 'test', @@ -163,7 +155,7 @@ await token.transfer(alice, {Substrate: bob.address}); const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address); - const fee = Number(aliceBalanceBefore - aliceBalanceAfter) / Number(UNIQUE); + const fee = Number(aliceBalanceBefore - aliceBalanceAfter) / Number(helper.balance.getOneTokenNominal()); const expectedTransferFee = 0.1; // fee drifts because of NextFeeMultiplier const tolerance = 0.001; --- a/tests/src/destroyCollection.test.ts +++ b/tests/src/destroyCollection.test.ts @@ -15,10 +15,7 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import { - Pallets, -} from './util/helpers'; -import {itSub, expect, usingPlaygrounds} from './util/playgrounds'; +import {itSub, expect, usingPlaygrounds, Pallets} from './util/playgrounds'; describe('integration test: ext. destroyCollection():', () => { let alice: IKeyringPair; --- a/tests/src/eth/allowlist.test.ts +++ b/tests/src/eth/allowlist.test.ts @@ -89,20 +89,21 @@ expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.false; }); - itEth('Collection allowlist can be added and removed by [sub] address', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const user = donor; + // TODO: Temprorary off. Need refactor + // itEth('Collection allowlist can be added and removed by [sub] address', async ({helper}) => { + // const owner = await helper.eth.createAccountWithBalance(donor); + // const user = donor; - const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); - const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + // const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); + // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + // await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); + // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; - await collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - }); + // await collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); + // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + // }); itEth('Collection allowlist can not be add and remove [eth] address by not owner', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); @@ -122,21 +123,22 @@ expect(await collectionEvm.methods.allowed(user).call({from: owner})).to.be.true; }); - itEth('Collection allowlist can not be add and remove [sub] address by not owner', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const notOwner = await helper.eth.createAccountWithBalance(donor); - const user = donor; + // TODO: Temprorary off. Need refactor + // itEth('Collection allowlist can not be add and remove [sub] address by not owner', async ({helper}) => { + // const owner = await helper.eth.createAccountWithBalance(donor); + // const notOwner = await helper.eth.createAccountWithBalance(donor); + // const user = donor; - const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); - const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + // const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); + // const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - await expect(collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission'); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); + // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + // await expect(collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; + // await collectionEvm.methods.addToCollectionAllowListSubstrate(user.addressRaw).send({from: owner}); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; - await expect(collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission'); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; - }); + // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + // await expect(collectionEvm.methods.removeFromCollectionAllowListSubstrate(user.addressRaw).call({from: notOwner})).to.be.rejectedWith('NoPermission'); + // expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; + // }); }); --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -13,7 +13,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x47dbc105 +/// @dev the ERC-165 identifier for this interface is 0x3e1e8083 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -48,15 +48,6 @@ /// @dev EVM selector for this function is: 0x7623402e, /// or in textual repr: setCollectionSponsor(address) function setCollectionSponsor(address sponsor) external; - - /// Set the substrate sponsor of the collection. - /// - /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. - /// - /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0xc74d6751, - /// or in textual repr: setCollectionSponsorSubstrate(uint256) - function setCollectionSponsorSubstrate(uint256 sponsor) external; /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, @@ -112,18 +103,6 @@ /// or in textual repr: contractAddress() function contractAddress() external view returns (address); - /// Add collection admin by substrate address. - /// @param newAdmin Substrate administrator address. - /// @dev EVM selector for this function is: 0x5730062b, - /// or in textual repr: addCollectionAdminSubstrate(uint256) - function addCollectionAdminSubstrate(uint256 newAdmin) external; - - /// Remove collection admin by substrate address. - /// @param admin Substrate administrator address. - /// @dev EVM selector for this function is: 0x4048fcf9, - /// or in textual repr: removeCollectionAdminSubstrate(uint256) - function removeCollectionAdminSubstrate(uint256 admin) external; - /// Add collection admin. /// @param newAdmin Address of the added administrator. /// @dev EVM selector for this function is: 0x92e462c7, @@ -174,13 +153,6 @@ /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) external; - /// Add substrate user to allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xd06ad267, - /// or in textual repr: addToCollectionAllowListSubstrate(uint256) - function addToCollectionAllowListSubstrate(uint256 user) external; - /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -188,13 +160,6 @@ /// or in textual repr: removeFromCollectionAllowList(address) function removeFromCollectionAllowList(address user) external; - /// Remove substrate user from allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xa31913ed, - /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) - function removeFromCollectionAllowListSubstrate(uint256 user) external; - /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -209,14 +174,6 @@ /// @dev EVM selector for this function is: 0x9811b0c7, /// or in textual repr: isOwnerOrAdmin(address) function isOwnerOrAdmin(address user) external view returns (bool); - - /// Check that substrate account is the owner or admin of the collection - /// - /// @param user account to verify - /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x68910e00, - /// or in textual repr: isOwnerOrAdminSubstrate(uint256) - function isOwnerOrAdminSubstrate(uint256 user) external view returns (bool); /// Returns collection type /// @@ -240,14 +197,6 @@ /// @dev EVM selector for this function is: 0x13af4035, /// or in textual repr: setOwner(address) function setOwner(address newOwner) external; - - /// Changes collection owner to another substrate account - /// - /// @dev Owner can be changed only by current owner - /// @param newOwner new owner substrate account - /// @dev EVM selector for this function is: 0xb212138f, - /// or in textual repr: setOwnerSubstrate(uint256) - function setOwnerSubstrate(uint256 newOwner) external; } /// @dev the ERC-165 identifier for this interface is 0x63034ac5 --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -62,7 +62,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x47dbc105 +/// @dev the ERC-165 identifier for this interface is 0x3e1e8083 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -97,15 +97,6 @@ /// @dev EVM selector for this function is: 0x7623402e, /// or in textual repr: setCollectionSponsor(address) function setCollectionSponsor(address sponsor) external; - - /// Set the substrate sponsor of the collection. - /// - /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. - /// - /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0xc74d6751, - /// or in textual repr: setCollectionSponsorSubstrate(uint256) - function setCollectionSponsorSubstrate(uint256 sponsor) external; /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, @@ -161,18 +152,6 @@ /// or in textual repr: contractAddress() function contractAddress() external view returns (address); - /// Add collection admin by substrate address. - /// @param newAdmin Substrate administrator address. - /// @dev EVM selector for this function is: 0x5730062b, - /// or in textual repr: addCollectionAdminSubstrate(uint256) - function addCollectionAdminSubstrate(uint256 newAdmin) external; - - /// Remove collection admin by substrate address. - /// @param admin Substrate administrator address. - /// @dev EVM selector for this function is: 0x4048fcf9, - /// or in textual repr: removeCollectionAdminSubstrate(uint256) - function removeCollectionAdminSubstrate(uint256 admin) external; - /// Add collection admin. /// @param newAdmin Address of the added administrator. /// @dev EVM selector for this function is: 0x92e462c7, @@ -223,13 +202,6 @@ /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) external; - /// Add substrate user to allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xd06ad267, - /// or in textual repr: addToCollectionAllowListSubstrate(uint256) - function addToCollectionAllowListSubstrate(uint256 user) external; - /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -237,13 +209,6 @@ /// or in textual repr: removeFromCollectionAllowList(address) function removeFromCollectionAllowList(address user) external; - /// Remove substrate user from allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xa31913ed, - /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) - function removeFromCollectionAllowListSubstrate(uint256 user) external; - /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -258,14 +223,6 @@ /// @dev EVM selector for this function is: 0x9811b0c7, /// or in textual repr: isOwnerOrAdmin(address) function isOwnerOrAdmin(address user) external view returns (bool); - - /// Check that substrate account is the owner or admin of the collection - /// - /// @param user account to verify - /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x68910e00, - /// or in textual repr: isOwnerOrAdminSubstrate(uint256) - function isOwnerOrAdminSubstrate(uint256 user) external view returns (bool); /// Returns collection type /// @@ -289,14 +246,6 @@ /// @dev EVM selector for this function is: 0x13af4035, /// or in textual repr: setOwner(address) function setOwner(address newOwner) external; - - /// Changes collection owner to another substrate account - /// - /// @dev Owner can be changed only by current owner - /// @param newOwner new owner substrate account - /// @dev EVM selector for this function is: 0xb212138f, - /// or in textual repr: setOwnerSubstrate(uint256) - function setOwnerSubstrate(uint256 newOwner) external; } /// @dev anonymous struct --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -62,7 +62,7 @@ } /// @title A contract that allows you to work with collections. -/// @dev the ERC-165 identifier for this interface is 0x47dbc105 +/// @dev the ERC-165 identifier for this interface is 0x3e1e8083 interface Collection is Dummy, ERC165 { /// Set collection property. /// @@ -97,15 +97,6 @@ /// @dev EVM selector for this function is: 0x7623402e, /// or in textual repr: setCollectionSponsor(address) function setCollectionSponsor(address sponsor) external; - - /// Set the substrate sponsor of the collection. - /// - /// @dev In order for sponsorship to work, it must be confirmed on behalf of the sponsor. - /// - /// @param sponsor Substrate address of the sponsor from whose account funds will be debited for operations with the contract. - /// @dev EVM selector for this function is: 0xc74d6751, - /// or in textual repr: setCollectionSponsorSubstrate(uint256) - function setCollectionSponsorSubstrate(uint256 sponsor) external; /// Whether there is a pending sponsor. /// @dev EVM selector for this function is: 0x058ac185, @@ -161,18 +152,6 @@ /// or in textual repr: contractAddress() function contractAddress() external view returns (address); - /// Add collection admin by substrate address. - /// @param newAdmin Substrate administrator address. - /// @dev EVM selector for this function is: 0x5730062b, - /// or in textual repr: addCollectionAdminSubstrate(uint256) - function addCollectionAdminSubstrate(uint256 newAdmin) external; - - /// Remove collection admin by substrate address. - /// @param admin Substrate administrator address. - /// @dev EVM selector for this function is: 0x4048fcf9, - /// or in textual repr: removeCollectionAdminSubstrate(uint256) - function removeCollectionAdminSubstrate(uint256 admin) external; - /// Add collection admin. /// @param newAdmin Address of the added administrator. /// @dev EVM selector for this function is: 0x92e462c7, @@ -223,13 +202,6 @@ /// or in textual repr: addToCollectionAllowList(address) function addToCollectionAllowList(address user) external; - /// Add substrate user to allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xd06ad267, - /// or in textual repr: addToCollectionAllowListSubstrate(uint256) - function addToCollectionAllowListSubstrate(uint256 user) external; - /// Remove the user from the allowed list. /// /// @param user Address of a removed user. @@ -237,13 +209,6 @@ /// or in textual repr: removeFromCollectionAllowList(address) function removeFromCollectionAllowList(address user) external; - /// Remove substrate user from allowed list. - /// - /// @param user User substrate address. - /// @dev EVM selector for this function is: 0xa31913ed, - /// or in textual repr: removeFromCollectionAllowListSubstrate(uint256) - function removeFromCollectionAllowListSubstrate(uint256 user) external; - /// Switch permission for minting. /// /// @param mode Enable if "true". @@ -258,14 +223,6 @@ /// @dev EVM selector for this function is: 0x9811b0c7, /// or in textual repr: isOwnerOrAdmin(address) function isOwnerOrAdmin(address user) external view returns (bool); - - /// Check that substrate account is the owner or admin of the collection - /// - /// @param user account to verify - /// @return "true" if account is the owner or admin - /// @dev EVM selector for this function is: 0x68910e00, - /// or in textual repr: isOwnerOrAdminSubstrate(uint256) - function isOwnerOrAdminSubstrate(uint256 user) external view returns (bool); /// Returns collection type /// @@ -289,14 +246,6 @@ /// @dev EVM selector for this function is: 0x13af4035, /// or in textual repr: setOwner(address) function setOwner(address newOwner) external; - - /// Changes collection owner to another substrate account - /// - /// @dev Owner can be changed only by current owner - /// @param newOwner new owner substrate account - /// @dev EVM selector for this function is: 0xb212138f, - /// or in textual repr: setOwnerSubstrate(uint256) - function setOwnerSubstrate(uint256 newOwner) external; } /// @dev anonymous struct --- a/tests/src/eth/collectionSponsoring.test.ts +++ b/tests/src/eth/collectionSponsoring.test.ts @@ -40,25 +40,26 @@ ]); }); - itWeb3('Set substrate sponsor', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionHelpers = evmCollectionHelpers(web3, owner); - let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const sponsor = privateKeyWrapper('//Alice'); - const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + // TODO: Temprorary off. Need refactor + // itWeb3('Set substrate sponsor', async ({api, web3, privateKeyWrapper}) => { + // const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + // const collectionHelpers = evmCollectionHelpers(web3, owner); + // let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send(); + // const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); + // const sponsor = privateKeyWrapper('//Alice'); + // const collectionEvm = evmCollection(web3, owner, collectionIdAddress); - expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; - result = await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner}); - expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true; + // expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + // result = await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner}); + // expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.true; - const confirmTx = await api.tx.unique.confirmSponsorship(collectionId); - await submitTransactionAsync(sponsor, confirmTx); - expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; + // const confirmTx = await api.tx.unique.confirmSponsorship(collectionId); + // await submitTransactionAsync(sponsor, confirmTx); + // expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; - const sponsorTuple = await collectionEvm.methods.collectionSponsor().call({from: owner}); - expect(bigIntToSub(api, BigInt(sponsorTuple[1]))).to.be.eq(sponsor.address); - }); + // const sponsorTuple = await collectionEvm.methods.collectionSponsor().call({from: owner}); + // expect(bigIntToSub(api, BigInt(sponsorTuple[1]))).to.be.eq(sponsor.address); + // }); itWeb3('Remove sponsor', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); @@ -150,60 +151,61 @@ } }); - itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionHelpers = evmCollectionHelpers(web3, owner); - const result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const sponsor = privateKeyWrapper('//Alice'); - const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + // TODO: Temprorary off. Need refactor + // itWeb3('Sponsoring collection from substrate address via access list', async ({api, web3, privateKeyWrapper}) => { + // const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + // const collectionHelpers = evmCollectionHelpers(web3, owner); + // const result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send(); + // const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); + // const sponsor = privateKeyWrapper('//Alice'); + // const collectionEvm = evmCollection(web3, owner, collectionIdAddress); - await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner}); + // await collectionEvm.methods.setCollectionSponsorSubstrate(sponsor.addressRaw).send({from: owner}); - const confirmTx = await api.tx.unique.confirmSponsorship(collectionId); - await submitTransactionAsync(sponsor, confirmTx); + // const confirmTx = await api.tx.unique.confirmSponsorship(collectionId); + // await submitTransactionAsync(sponsor, confirmTx); - const user = createEthAccount(web3); - const nextTokenId = await collectionEvm.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); + // const user = createEthAccount(web3); + // 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.addToCollectionAllowList(user).send({from: owner}); - await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); + // await collectionEvm.methods.setCollectionAccess(1 /*'AllowList'*/).send({from: owner}); + // await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner}); + // await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); - const ownerBalanceBefore = await ethBalanceViaSub(api, owner); - const sponsorBalanceBefore = (await getBalance(api, [sponsor.address]))[0]; + // const ownerBalanceBefore = await ethBalanceViaSub(api, owner); + // const sponsorBalanceBefore = (await getBalance(api, [sponsor.address]))[0]; - { - const nextTokenId = await collectionEvm.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - const result = await collectionEvm.methods.mintWithTokenURI( - user, - nextTokenId, - 'Test URI', - ).send({from: user}); - const events = normalizeEvents(result.events); + // { + // const nextTokenId = await collectionEvm.methods.nextTokenId().call(); + // expect(nextTokenId).to.be.equal('1'); + // const result = await collectionEvm.methods.mintWithTokenURI( + // user, + // nextTokenId, + // 'Test URI', + // ).send({from: user}); + // const events = normalizeEvents(result.events); - expect(events).to.be.deep.equal([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: user, - tokenId: nextTokenId, - }, - }, - ]); + // expect(events).to.be.deep.equal([ + // { + // address: collectionIdAddress, + // event: 'Transfer', + // args: { + // from: '0x0000000000000000000000000000000000000000', + // to: user, + // tokenId: nextTokenId, + // }, + // }, + // ]); - const ownerBalanceAfter = await ethBalanceViaSub(api, owner); - const sponsorBalanceAfter = (await getBalance(api, [sponsor.address]))[0]; + // const ownerBalanceAfter = await ethBalanceViaSub(api, owner); + // const sponsorBalanceAfter = (await getBalance(api, [sponsor.address]))[0]; - expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); - expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); - expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true; - } - }); + // expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); + // expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); + // expect(sponsorBalanceBefore > sponsorBalanceAfter).to.be.true; + // } + // }); itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); --- a/tests/src/eth/fungible.test.ts +++ b/tests/src/eth/fungible.test.ts @@ -14,204 +14,132 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {approveExpectSuccess, createCollection, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers'; -import fungibleAbi from './fungibleAbi.json'; -import {expect} from 'chai'; -import {submitTransactionAsync} from '../substrate/substrate-api'; +import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds'; +import {IKeyringPair} from '@polkadot/types/types'; describe('Fungible: Information getting', () => { - itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); }); - const alice = privateKeyWrapper('//Alice'); + }); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('totalSupply', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n); - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Substrate: alice.address}); - - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller); const totalSupply = await contract.methods.totalSupply().call(); - expect(totalSupply).to.equal('200'); }); - itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('balanceOf', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n, {Ethereum: caller}); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: caller}); - - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller); const balance = await contract.methods.balanceOf(caller).call(); - expect(balance).to.equal('200'); }); }); describe('Fungible: Plain calls', () => { - itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - const collection = await createCollection(api, alice, { - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); }); + }); - const receiver = createEthAccount(web3); + itEth('Can perform mint()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.addAdmin(alice, {Ethereum: owner}); - const collectionIdAddress = collectionIdToAddress(collection.collectionId); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner}); - await submitTransactionAsync(alice, changeAdminTx); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); - const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0}); - const result = await collectionContract.methods.mint(receiver, 100).send(); - const events = normalizeEvents(result.events); + const result = await contract.methods.mint(receiver, 100).send(); - expect(events).to.be.deep.equal([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - value: '100', - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.value).to.equal('100'); }); - - itWeb3('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - const collection = await createCollection(api, alice, { - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver1 = createEthAccount(web3); - const receiver2 = createEthAccount(web3); - const receiver3 = createEthAccount(web3); - - const collectionIdAddress = collectionIdToAddress(collection.collectionId); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner}); - await submitTransactionAsync(alice, changeAdminTx); + itEth('Can perform mintBulk()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const bulkSize = 3; + const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount()); + const collection = await helper.ft.mintCollection(alice); + await collection.addAdmin(alice, {Ethereum: owner}); - const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0}); - const result = await collectionContract.methods.mintBulk([ - [receiver1, 10], - [receiver2, 20], - [receiver3, 30], - ]).send(); - const events = normalizeEvents(result.events); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); - expect(events).to.be.deep.contain({ - address:collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver1, - value: '10', - }, - }); - - expect(events).to.be.deep.contain({ - address:collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver2, - value: '20', - }, - }); - - expect(events).to.be.deep.contain({ - address:collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver3, - value: '30', - }, - }); + const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => ( + [receivers[i], (i + 1) * 10] + ))).send(); + const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value); + for (let i = 0; i < bulkSize; i++) { + const event = events[i]; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.equal(receivers[i]); + expect(event.returnValues.value).to.equal(String(10 * (i + 1))); + } }); - itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - const collection = await createCollection(api, alice, { - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, - }); - - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner}); - await submitTransactionAsync(alice, changeAdminTx); - const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('Can perform burn()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.ft.mintCollection(alice); + await collection.addAdmin(alice, {Ethereum: owner}); - const collectionIdAddress = collectionIdToAddress(collection.collectionId); - const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0}); - await collectionContract.methods.mint(receiver, 100).send(); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); + await contract.methods.mint(receiver, 100).send(); - const result = await collectionContract.methods.burnFrom(receiver, 49).send({from: receiver}); + const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver}); - const events = normalizeEvents(result.events); - - expect(events).to.be.deep.equal([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: receiver, - to: '0x0000000000000000000000000000000000000000', - value: '49', - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal(receiver); + expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.value).to.equal('49'); - const balance = await collectionContract.methods.balanceOf(receiver).call(); + const balance = await contract.methods.balanceOf(receiver).call(); expect(balance).to.equal('51'); }); - itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('Can perform approve()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n, {Ethereum: owner}); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner}); - - const spender = createEthAccount(web3); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS}); - { const result = await contract.methods.approve(spender, 100).send({from: owner}); - const events = normalizeEvents(result.events); - expect(events).to.be.deep.equal([ - { - address, - event: 'Approval', - args: { - owner, - spender, - value: '100', - }, - }, - ]); + const event = result.events.Approval; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.owner).to.be.equal(owner); + expect(event.returnValues.spender).to.be.equal(spender); + expect(event.returnValues.value).to.be.equal('100'); } { @@ -220,51 +148,32 @@ } }); - itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner}); - - const spender = createEthAccount(web3); - await transferBalanceToEth(api, alice, spender); - - const receiver = createEthAccount(web3); + itEth('Can perform transferFrom()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); await contract.methods.approve(spender, 100).send(); { const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender}); - const events = normalizeEvents(result.events); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: receiver, - value: '49', - }, - }, - { - address, - event: 'Approval', - args: { - owner, - spender, - value: '51', - }, - }, - ]); + + let event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(owner); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('49'); + + event = result.events.Approval; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.owner).to.be.equal(owner); + expect(event.returnValues.spender).to.be.equal(spender); + expect(event.returnValues.value).to.be.equal('51'); } { @@ -278,38 +187,23 @@ } }); - itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - name: 'token name', - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner}); - - const receiver = createEthAccount(web3); - await transferBalanceToEth(api, alice, receiver); + itEth('Can perform transfer()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); { const result = await contract.methods.transfer(receiver, 50).send({from: owner}); - const events = normalizeEvents(result.events); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: receiver, - value: '50', - }, - }, - ]); + + const event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(owner); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('50'); } { @@ -325,162 +219,141 @@ }); describe('Fungible: Fees', () => { - itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'Fungible', decimalPoints: 0}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); }); - const alice = privateKeyWrapper('//Alice'); + }); + + itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n, {Ethereum: owner}); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const spender = createEthAccount(web3); - - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS}); - - const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, 100).send({from: owner})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); - - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner}); + itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); await contract.methods.approve(spender, 100).send({from: owner}); - const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - - itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); - - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner}); + itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner); - const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, 100).send({from: owner})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); }); describe('Fungible: Substrate calls', () => { - itWeb3('Events emitted for approve()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'Fungible', decimalPoints: 0}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); }); - const alice = privateKeyWrapper('//Alice'); + }); - const receiver = createEthAccount(web3); + itEth('Events emitted for approve()', async ({helper}) => { + const receiver = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n); - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft'); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address); - - const events = await recordEvents(contract, async () => { - await approveExpectSuccess(collection, 0, alice, {Ethereum: receiver}, 100); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); + await collection.approveTokens(alice, {Ethereum: receiver}, 100n); - expect(events).to.be.deep.equal([ - { - address, - event: 'Approval', - args: { - owner: subToEth(alice.address), - spender: receiver, - value: '100', - }, - }, - ]); + const event = events[0]; + expect(event.event).to.be.equal('Approval'); + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.spender).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('100'); }); - itWeb3('Events emitted for transferFrom()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); + itEth('Events emitted for transferFrom()', async ({helper}) => { + const [bob] = await helper.arrange.createAccounts([10n], donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n); + await collection.approveTokens(alice, {Substrate: bob.address}, 100n); - const receiver = createEthAccount(web3); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft'); - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}); - await approveExpectSuccess(collection, 0, alice, bob.address, 100); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); + }); + await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address); + let event = events[0]; + expect(event.event).to.be.equal('Transfer'); + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('51'); - const events = await recordEvents(contract, async () => { - await transferFromExpectSuccess(collection, 0, bob, alice, {Ethereum: receiver}, 51, 'Fungible'); - }); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: subToEth(alice.address), - to: receiver, - value: '51', - }, - }, - { - address, - event: 'Approval', - args: { - owner: subToEth(alice.address), - spender: subToEth(bob.address), - value: '49', - }, - }, - ]); + event = events[1]; + expect(event.event).to.be.equal('Approval'); + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address)); + expect(event.returnValues.value).to.be.equal('49'); }); - itWeb3('Events emitted for transfer()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'Fungible', decimalPoints: 0}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('Events emitted for transfer()', async ({helper}) => { + const receiver = helper.eth.createAccount(); + const collection = await helper.ft.mintCollection(alice); + await collection.mint(alice, 200n); - const receiver = createEthAccount(web3); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'ft'); - await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}); - - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(fungibleAbi as any, address); - - const events = await recordEvents(contract, async () => { - await transferExpectSuccess(collection, 0, alice, {Ethereum:receiver}, 51, 'Fungible'); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); + await collection.transfer(alice, {Ethereum:receiver}, 51n); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: subToEth(alice.address), - to: receiver, - value: '51', - }, - }, - ]); + const event = events[0]; + expect(event.event).to.be.equal('Transfer'); + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('51'); }); }); --- a/tests/src/eth/fungibleAbi.json +++ b/tests/src/eth/fungibleAbi.json @@ -60,27 +60,9 @@ }, { "inputs": [ - { "internalType": "uint256", "name": "newAdmin", "type": "uint256" } - ], - "name": "addCollectionAdminSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", "name": "user", "type": "address" } ], "name": "addToCollectionAllowList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "addToCollectionAllowListSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -214,15 +196,6 @@ { "internalType": "address", "name": "user", "type": "address" } ], "name": "isOwnerOrAdmin", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "isOwnerOrAdminSubstrate", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" @@ -271,15 +244,6 @@ "type": "function" }, { - "inputs": [ - { "internalType": "uint256", "name": "admin", "type": "uint256" } - ], - "name": "removeCollectionAdminSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "inputs": [], "name": "removeCollectionSponsor", "outputs": [], @@ -291,15 +255,6 @@ { "internalType": "address", "name": "user", "type": "address" } ], "name": "removeFromCollectionAllowList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "removeFromCollectionAllowListSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -380,27 +335,9 @@ }, { "inputs": [ - { "internalType": "uint256", "name": "sponsor", "type": "uint256" } - ], - "name": "setCollectionSponsorSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "newOwner", "type": "uint256" } - ], - "name": "setOwnerSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -14,72 +14,78 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers'; -import nonFungibleAbi from './nonFungibleAbi.json'; -import {expect} from 'chai'; -import {submitTransactionAsync} from '../substrate/substrate-api'; +import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util/playgrounds'; +import {IKeyringPair} from '@polkadot/types/types'; +import {Contract} from 'web3-eth-contract'; describe('NFT: Information getting', () => { - itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([10n], donor); }); - const alice = privateKeyWrapper('//Alice'); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + }); + + itEth('totalSupply', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {}); + await collection.mintToken(alice); - await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address}); + const caller = await helper.eth.createAccountWithBalance(donor); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller); const totalSupply = await contract.methods.totalSupply().call(); expect(totalSupply).to.equal('1'); }); - itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('balanceOf', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {}); + const caller = await helper.eth.createAccountWithBalance(donor); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum:caller}); - await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller}); - await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller}); + await collection.mintToken(alice, {Ethereum: caller}); + await collection.mintToken(alice, {Ethereum: caller}); + await collection.mintToken(alice, {Ethereum: caller}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller); const balance = await contract.methods.balanceOf(caller).call(); expect(balance).to.equal('3'); }); - itWeb3('ownerOf', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('ownerOf', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {}); + const caller = await helper.eth.createAccountWithBalance(donor); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: caller}); + const token = await collection.mintToken(alice, {Ethereum: caller}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); - const owner = await contract.methods.ownerOf(tokenId).call(); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller); + + const owner = await contract.methods.ownerOf(token.tokenId).call(); expect(owner).to.equal(caller); }); }); describe('Check ERC721 token URI for NFT', () => { - itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', '').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress); + let donor: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (_helper, privateKey) => { + donor = privateKey('//Alice'); + }); + }); + + async function setup(helper: EthUniqueHelper, tokenPrefix: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send(); + const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); @@ -88,162 +94,73 @@ nextTokenId, ).send(); - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); + if (propertyKey && propertyValue) { + // Set URL or suffix + await contract.methods.setProperty(nextTokenId, propertyKey, Buffer.from(propertyValue)).send(); + } - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(nextTokenId); + return {contract, nextTokenId}; + } + + itEth('Empty tokenURI', async ({helper}) => { + const {contract, nextTokenId} = await setup(helper, ''); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal(''); }); - itWeb3('TokenURI from url', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress); - - const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mint( - receiver, - nextTokenId, - ).send(); - - // Set URL - await contract.methods.setProperty(nextTokenId, 'url', Buffer.from('Token URI')).send(); - - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); - + itEth('TokenURI from url', async ({helper}) => { + const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'url', 'Token URI'); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI'); }); - itWeb3('TokenURI from baseURI + tokenId', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress); - - const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mint( - receiver, - nextTokenId, - ).send(); - - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); - + itEth('TokenURI from baseURI + tokenId', async ({helper}) => { + const {contract, nextTokenId} = await setup(helper, 'BaseURI_'); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + nextTokenId); }); - itWeb3('TokenURI from baseURI + suffix', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress); - - const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mint( - receiver, - nextTokenId, - ).send(); - - // Set suffix + itEth('TokenURI from baseURI + suffix', async ({helper}) => { const suffix = '/some/suffix'; - await contract.methods.setProperty(nextTokenId, 'suffix', Buffer.from(suffix)).send(); - - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); - + const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'suffix', suffix); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix); }); }); describe('NFT: Plain calls', () => { - itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createNonfungibleCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress); + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([10n], donor); + }); + }); + + itEth('Can perform mint()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + + const {collectionAddress} = await helper.eth.createNonfungibleCollection(owner, 'Minty', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mintWithTokenURI( + const result = await contract.methods.mintWithTokenURI( receiver, nextTokenId, 'Test URI', ).send(); - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(nextTokenId); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); @@ -254,7 +171,8 @@ }); //TODO: CORE-302 add eth methods - itWeb3.skip('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => { + /* todo:playgrounds skipped test! + itWeb3.skip('Can perform mintBulk()', async ({helper}) => { const collection = await createCollectionExpectSuccess({ mode: {type: 'NFT'}, }); @@ -316,107 +234,70 @@ expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2'); } }); - - itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); + */ - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('Can perform burn()', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner}); + const collection = await helper.nft.mintCollection(alice, {}); + const {tokenId} = await collection.mintToken(alice, {Ethereum: caller}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller); { - const result = await contract.methods.burn(tokenId).send({from: owner}); - const events = normalizeEvents(result.events); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: '0x0000000000000000000000000000000000000000', - tokenId: tokenId.toString(), - }, - }, - ]); + const result = await contract.methods.burn(tokenId).send({from: caller}); + + const event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(caller); + expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`); } }); - itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner}); + itEth('Can perform approve()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = helper.eth.createAccount(); - const spender = createEthAccount(web3); + const collection = await helper.nft.mintCollection(alice, {}); + const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); { - const result = await contract.methods.approve(spender, tokenId).send({from: owner, ...GAS_ARGS}); - const events = normalizeEvents(result.events); + const result = await contract.methods.approve(spender, tokenId).send({from: owner}); - expect(events).to.be.deep.equal([ - { - address, - event: 'Approval', - args: { - owner, - approved: spender, - tokenId: tokenId.toString(), - }, - }, - ]); + const event = result.events.Approval; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.owner).to.be.equal(owner); + expect(event.returnValues.approved).to.be.equal(spender); + expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`); } }); - itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); + itEth('Can perform transferFrom()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner}); - - const spender = createEthAccount(web3); - await transferBalanceToEth(api, alice, spender); - - const receiver = createEthAccount(web3); + const collection = await helper.nft.mintCollection(alice, {}); + const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); await contract.methods.approve(spender, tokenId).send({from: owner}); { const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: spender}); - const events = normalizeEvents(result.events); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + + const event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(owner); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`); } { @@ -430,37 +311,24 @@ } }); - itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner}); + itEth('Can perform transfer()', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {}); + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); - const receiver = createEthAccount(web3); - await transferBalanceToEth(api, alice, receiver); + const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); { const result = await contract.methods.transfer(receiver, tokenId).send({from: owner}); - const events = normalizeEvents(result.events); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + + const event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(owner); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(`${tokenId}`); } { @@ -476,237 +344,209 @@ }); describe('NFT: Fees', () => { - itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([10n], donor); }); - const alice = privateKeyWrapper('//Alice'); + }); + + itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = helper.eth.createAccount(); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const spender = createEthAccount(web3); + const collection = await helper.nft.mintCollection(alice, {}); + const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS}); - - const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, tokenId).send({from: owner})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, tokenId).send({from: owner})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); - - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = await helper.eth.createAccountWithBalance(donor); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner}); + const collection = await helper.nft.mintCollection(alice, {}); + const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner); await contract.methods.approve(spender, tokenId).send({from: owner}); - const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, tokenId).send({from: spender})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); + const collection = await helper.nft.mintCollection(alice, {}); + const {tokenId} = await collection.mintToken(alice, {Ethereum: owner}); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: owner}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', owner); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: owner, ...GAS_ARGS}); - - const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, tokenId).send({from: owner})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); }); describe('NFT: Substrate calls', () => { - itWeb3('Events emitted for mint()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); }); - const alice = privateKeyWrapper('//Alice'); + }); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address); + itEth('Events emitted for mint()', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); - let tokenId: number; - const events = await recordEvents(contract, async () => { - tokenId = await createItemExpectSuccess(alice, collection, 'NFT'); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); + const {tokenId} = await collection.mintToken(alice); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: subToEth(alice.address), - tokenId: tokenId!.toString(), - }, - }, - ]); + const event = events[0]; + expect(event.event).to.be.equal('Transfer'); + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.tokenId).to.be.equal(tokenId.toString()); }); - itWeb3('Events emitted for burn()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, + itEth('Events emitted for burn()', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {}); + const token = await collection.mintToken(alice); + + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); + + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); - const alice = privateKeyWrapper('//Alice'); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address); + await token.burn(alice); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT'); - const events = await recordEvents(contract, async () => { - await burnItemExpectSuccess(alice, collection, tokenId); - }); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: subToEth(alice.address), - to: '0x0000000000000000000000000000000000000000', - tokenId: tokenId.toString(), - }, - }, - ]); + const event = events[0]; + expect(event.event).to.be.equal('Transfer'); + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString()); }); - itWeb3('Events emitted for approve()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('Events emitted for approve()', async ({helper}) => { + const receiver = helper.eth.createAccount(); - const receiver = createEthAccount(web3); - - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT'); - - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address); + const collection = await helper.nft.mintCollection(alice, {}); + const token = await collection.mintToken(alice); - const events = await recordEvents(contract, async () => { - await approveExpectSuccess(collection, tokenId, alice, {Ethereum: receiver}, 1); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); + + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); - expect(events).to.be.deep.equal([ - { - address, - event: 'Approval', - args: { - owner: subToEth(alice.address), - approved: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + await token.approve(alice, {Ethereum: receiver}); + + const event = events[0]; + expect(event.event).to.be.equal('Approval'); + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.approved).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(token.tokenId.toString()); }); - itWeb3('Events emitted for transferFrom()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); + itEth('Events emitted for transferFrom()', async ({helper}) => { + const [bob] = await helper.arrange.createAccounts([10n], donor); + const receiver = helper.eth.createAccount(); - const receiver = createEthAccount(web3); - - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT'); - await approveExpectSuccess(collection, tokenId, alice, bob.address, 1); - - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address); + const collection = await helper.nft.mintCollection(alice, {}); + const token = await collection.mintToken(alice); + await token.approve(alice, {Substrate: bob.address}); - const events = await recordEvents(contract, async () => { - await transferFromExpectSuccess(collection, tokenId, bob, alice, {Ethereum: receiver}, 1, 'NFT'); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); + + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: subToEth(alice.address), - to: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}); + + const event = events[0]; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`); }); - itWeb3('Events emitted for transfer()', async ({web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth('Events emitted for transfer()', async ({helper}) => { + const receiver = helper.eth.createAccount(); - const receiver = createEthAccount(web3); + const collection = await helper.nft.mintCollection(alice, {}); + const token = await collection.mintToken(alice); - const tokenId = await createItemExpectSuccess(alice, collection, 'NFT'); - - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address); - - const events = await recordEvents(contract, async () => { - await transferExpectSuccess(collection, tokenId, alice, {Ethereum: receiver}, 1, 'NFT'); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); + + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: subToEth(alice.address), - to: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + await token.transfer(alice, {Ethereum: receiver}); + + const event = events[0]; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`); }); }); describe('Common metadata', () => { - itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - name: 'token name', - mode: {type: 'NFT'}, + let donor: IKeyringPair; + let alice: IKeyringPair; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); }); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + }); + + itEth('Returns collection name', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.nft.mintCollection(alice, {name: 'oh River', tokenPrefix: 'CHANGE'}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller); const name = await contract.methods.name().call(); - - expect(name).to.equal('token name'); + expect(name).to.equal('oh River'); }); - itWeb3('Returns symbol name', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - tokenPrefix: 'TOK', - mode: {type: 'NFT'}, - }); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('Returns symbol name', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.nft.mintCollection(alice, {name: 'oh River', tokenPrefix: 'CHANGE'}); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller); const symbol = await contract.methods.symbol().call(); - - expect(symbol).to.equal('TOK'); + expect(symbol).to.equal('CHANGE'); }); }); \ No newline at end of file --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -91,27 +91,9 @@ }, { "inputs": [ - { "internalType": "uint256", "name": "newAdmin", "type": "uint256" } - ], - "name": "addCollectionAdminSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", "name": "user", "type": "address" } ], "name": "addToCollectionAllowList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "addToCollectionAllowListSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -273,15 +255,6 @@ { "internalType": "address", "name": "user", "type": "address" } ], "name": "isOwnerOrAdmin", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "isOwnerOrAdminSubstrate", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" @@ -385,15 +358,6 @@ "type": "function" }, { - "inputs": [ - { "internalType": "uint256", "name": "admin", "type": "uint256" } - ], - "name": "removeCollectionAdminSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "inputs": [], "name": "removeCollectionSponsor", "outputs": [], @@ -405,15 +369,6 @@ { "internalType": "address", "name": "user", "type": "address" } ], "name": "removeFromCollectionAllowList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "removeFromCollectionAllowListSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -527,27 +482,9 @@ }, { "inputs": [ - { "internalType": "uint256", "name": "sponsor", "type": "uint256" } - ], - "name": "setCollectionSponsorSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "newOwner", "type": "uint256" } - ], - "name": "setOwnerSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" --- a/tests/src/eth/payable.test.ts +++ b/tests/src/eth/payable.test.ts @@ -50,7 +50,7 @@ await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n); - await helper.eth.callEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount); + await helper.eth.sendEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount); expect(await contract.methods.getCollected().call()).to.be.equal(weiCount); }); --- a/tests/src/eth/reFungible.test.ts +++ b/tests/src/eth/reFungible.test.ts @@ -14,33 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {createCollectionExpectSuccess, UNIQUE, requirePallets, Pallets} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, tokenIdToAddress, uniqueRefungibleToken} from './util/helpers'; -import {expect} from 'chai'; +import {Pallets, requirePalletsOrSkip} from '../util/playgrounds'; +import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds'; +import {IKeyringPair} from '@polkadot/types/types'; describe('Refungible: Information getting', () => { + let donor: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + + donor = privateKey('//Alice'); + }); }); - itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('totalSupply', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'TotalSupply', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const nextTokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, nextTokenId).send(); const totalSupply = await contract.methods.totalSupply().call(); expect(totalSupply).to.equal('1'); }); - itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('balanceOf', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'BalanceOf', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); { const nextTokenId = await contract.methods.nextTokenId().call(); @@ -56,38 +58,30 @@ } const balance = await contract.methods.balanceOf(caller).call(); - expect(balance).to.equal('3'); }); - itWeb3('ownerOf', async ({api, web3, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('ownerOf', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'OwnerOf', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); const owner = await contract.methods.ownerOf(tokenId).call(); - expect(owner).to.equal(caller); }); - itWeb3('ownerOf after burn', async ({api, web3, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('ownerOf after burn', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'OwnerOf-AfterBurn', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); - - const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); + const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); @@ -95,80 +89,67 @@ await tokenContract.methods.burnFrom(caller, 1).send(); const owner = await contract.methods.ownerOf(tokenId).call(); - expect(owner).to.equal(receiver); }); - itWeb3('ownerOf for partial ownership', async ({api, web3, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('ownerOf for partial ownership', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Partial-OwnerOf', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); - - const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); + const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); const owner = await contract.methods.ownerOf(tokenId).call(); - expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'); }); }); describe('Refungible: Plain calls', () => { + let donor: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + + donor = privateKey('//Alice'); + }); }); - itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); + itEth('Can perform mint()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Minty', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mintWithTokenURI( + const result = await contract.methods.mintWithTokenURI( receiver, nextTokenId, 'Test URI', ).send(); - const events = normalizeEvents(result.events); - - expect(events).to.include.deep.members([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.tokenId).to.equal(nextTokenId); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); }); - itWeb3('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('Can perform mintBulk()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'MintBulky', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); - const receiver = createEthAccount(web3); - { const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); @@ -180,37 +161,15 @@ [+nextTokenId + 2, 'Test URI 2'], ], ).send(); - const events = normalizeEvents(result.events); - expect(events).to.include.deep.members([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: String(+nextTokenId + 1), - }, - }, - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: String(+nextTokenId + 2), - }, - }, - ]); + const events = result.events.Transfer; + for (let i = 0; i < 2; i++) { + const event = events[i]; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i)); + } expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0'); expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1'); @@ -218,76 +177,54 @@ } }); - itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('Can perform burn()', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Burny', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); { const result = await contract.methods.burn(tokenId).send(); - const events = normalizeEvents(result.events); - expect(events).to.include.deep.members([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: caller, - to: '0x0000000000000000000000000000000000000000', - tokenId: tokenId.toString(), - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal(caller); + expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.tokenId).to.equal(tokenId.toString()); } }); - itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); - - const receiver = createEthAccount(web3); + itEth('Can perform transferFrom()', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'TransferFromy', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); + const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId); await contract.methods.mint(caller, tokenId).send(); - const address = tokenIdToAddress(collectionId, tokenId); - const tokenContract = uniqueRefungibleToken(web3, address, caller); + const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, caller); await tokenContract.methods.repartition(15).send(); { - const erc20Events = await recordEvents(tokenContract, async () => { - const result = await contract.methods.transferFrom(caller, receiver, tokenId).send(); - const events = normalizeEvents(result.events); - expect(events).to.include.deep.members([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: caller, - to: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + const tokenEvents: any = []; + tokenContract.events.allEvents((_: any, event: any) => { + tokenEvents.push(event); }); + const result = await contract.methods.transferFrom(caller, receiver, tokenId).send(); + + let event = result.events.Transfer; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal(caller); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.tokenId).to.equal(tokenId.toString()); - expect(erc20Events).to.include.deep.members([ - { - address, - event: 'Transfer', - args: { - from: caller, - to: receiver, - value: '15', - }, - }, - ]); + event = tokenEvents[0]; + expect(event.address).to.equal(tokenAddress); + expect(event.returnValues.from).to.equal(caller); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.value).to.equal('15'); } { @@ -301,32 +238,23 @@ } }); - itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); - - const receiver = createEthAccount(web3); + itEth('Can perform transfer()', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); { const result = await contract.methods.transfer(receiver, tokenId).send(); - const events = normalizeEvents(result.events); - expect(events).to.include.deep.members([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: caller, - to: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + + const event = result.events.Transfer; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal(caller); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.tokenId).to.equal(tokenId.toString()); } { @@ -340,141 +268,127 @@ } }); - itWeb3('transfer event on transfer from partial ownership to full ownership', async ({api, web3, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry-Partial-to-Full', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); - const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); + const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); - const events = await recordEvents(contract, async () => - await tokenContract.methods.transfer(receiver, 1).send()); - expect(events).to.deep.equal([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF', - to: receiver, - tokenId: tokenId.toString(), - }, - }, - ]); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); + }); + await tokenContract.methods.transfer(receiver, 1).send(); + + const event = events[0]; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.tokenId).to.equal(tokenId.toString()); }); - itWeb3('transfer event on transfer from full ownership to partial ownership', async ({api, web3, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Transferry-Full-to-Partial', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); - const tokenAddress = tokenIdToAddress(collectionId, tokenId); - const tokenContract = uniqueRefungibleToken(web3, tokenAddress, caller); + const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller); await tokenContract.methods.repartition(2).send(); - const events = await recordEvents(contract, async () => - await tokenContract.methods.transfer(receiver, 1).send()); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); + }); + await tokenContract.methods.transfer(receiver, 1).send(); - expect(events).to.deep.equal([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: caller, - to: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF', - tokenId: tokenId.toString(), - }, - }, - ]); + const event = events[0]; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal(caller); + expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'); + expect(event.returnValues.tokenId).to.equal(tokenId.toString()); }); }); describe('RFT: Fees', () => { + let donor: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + + donor = privateKey('//Alice'); + }); }); - itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); - - const receiver = createEthAccount(web3); + itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Feeful-Transfer-From', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); - const cost = await recordEthFee(api, caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send()); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send()); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); expect(cost > 0n); }); - itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); - - const receiver = createEthAccount(web3); + itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Feeful-Transfer', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); - const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send()); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send()); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); expect(cost > 0n); }); }); describe('Common metadata', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); - }); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); - itWeb3('Returns collection name', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - name: 'token name', - mode: {type: 'ReFungible'}, + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); }); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + }); - const address = collectionIdToAddress(collection); - const contract = evmCollection(web3, caller, address, {type: 'ReFungible'}); + itEth('Returns collection name', async ({helper}) => { + const caller = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice, {name: 'Leviathan', tokenPrefix: '11'}); + + const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller); const name = await contract.methods.name().call(); - - expect(name).to.equal('token name'); + expect(name).to.equal('Leviathan'); }); - itWeb3('Returns symbol name', async ({api, web3, privateKeyWrapper}) => { - const collection = await createCollectionExpectSuccess({ - tokenPrefix: 'TOK', - mode: {type: 'ReFungible'}, - }); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const address = collectionIdToAddress(collection); - const contract = evmCollection(web3, caller, address, {type: 'ReFungible'}); + itEth('Returns symbol name', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Leviathan', '', '12'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const symbol = await contract.methods.symbol().call(); - - expect(symbol).to.equal('TOK'); + expect(symbol).to.equal('12'); }); }); --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -91,27 +91,9 @@ }, { "inputs": [ - { "internalType": "uint256", "name": "newAdmin", "type": "uint256" } - ], - "name": "addCollectionAdminSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", "name": "user", "type": "address" } ], "name": "addToCollectionAllowList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "addToCollectionAllowListSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -273,15 +255,6 @@ { "internalType": "address", "name": "user", "type": "address" } ], "name": "isOwnerOrAdmin", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "isOwnerOrAdminSubstrate", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", "type": "function" @@ -385,15 +358,6 @@ "type": "function" }, { - "inputs": [ - { "internalType": "uint256", "name": "admin", "type": "uint256" } - ], - "name": "removeCollectionAdminSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { "inputs": [], "name": "removeCollectionSponsor", "outputs": [], @@ -405,15 +369,6 @@ { "internalType": "address", "name": "user", "type": "address" } ], "name": "removeFromCollectionAllowList", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "user", "type": "uint256" } - ], - "name": "removeFromCollectionAllowListSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -527,27 +482,9 @@ }, { "inputs": [ - { "internalType": "uint256", "name": "sponsor", "type": "uint256" } - ], - "name": "setCollectionSponsorSubstrate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ { "internalType": "address", "name": "newOwner", "type": "address" } ], "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "uint256", "name": "newOwner", "type": "uint256" } - ], - "name": "setOwnerSubstrate", "outputs": [], "stateMutability": "nonpayable", "type": "function" --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -14,82 +14,76 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE, requirePallets, Pallets} from '../util/helpers'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createRFTCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueRefungible, uniqueRefungibleToken} from './util/helpers'; +import {Pallets, requirePalletsOrSkip} from '../util/playgrounds'; +import {EthUniqueHelper, expect, itEth, usingEthPlaygrounds} from './util/playgrounds'; +import {IKeyringPair} from '@polkadot/types/types'; +import {Contract} from 'web3-eth-contract'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -chai.use(chaiAsPromised); -const expect = chai.expect; +describe('Refungible token: Information getting', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; -describe('Refungible token: Information getting', () => { before(async function() { - await requirePallets(this, [Pallets.ReFungible]); - }); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); - itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; - - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([20n], donor); + }); + }); - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId; + itEth('totalSupply', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'}); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller}); - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, caller); + const contract = helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, caller); const totalSupply = await contract.methods.totalSupply().call(); - expect(totalSupply).to.equal('200'); }); - itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; + itEth('balanceOf', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'}); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller}); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId; - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, caller); + const contract = helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, caller); const balance = await contract.methods.balanceOf(caller).call(); - expect(balance).to.equal('200'); }); - itWeb3('decimals', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('decimals', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.rft.mintCollection(alice, {tokenPrefix: 'MUON'}); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: caller}); - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; - - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: caller})).itemId; - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, caller); + const contract = helper.ethNativeContract.rftTokenById(collection.collectionId, tokenId, caller); const decimals = await contract.methods.decimals().call(); - expect(decimals).to.equal('0'); }); }); // FIXME: Need erc721 for ReFubgible. describe('Check ERC721 token URI for ReFungible', () => { + let donor: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + + donor = privateKey('//Alice'); + }); }); - itWeb3('Empty tokenURI', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', '').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); + async function setup(helper: EthUniqueHelper, tokenPrefix: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send(); + const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); result = await contract.methods.mint( @@ -97,166 +91,71 @@ nextTokenId, ).send(); - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); + if (propertyKey && propertyValue) { + // Set URL or suffix + await contract.methods.setProperty(nextTokenId, propertyKey, Buffer.from(propertyValue)).send(); + } + + const event = result.events.Transfer; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(nextTokenId); - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); + return {contract, nextTokenId}; + } + itEth('Empty tokenURI', async ({helper}) => { + const {contract, nextTokenId} = await setup(helper, ''); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal(''); }); - - itWeb3('TokenURI from url', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); - const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mint( - receiver, - nextTokenId, - ).send(); - - // Set URL - await contract.methods.setProperty(nextTokenId, 'url', Buffer.from('Token URI')).send(); - - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); - + itEth('TokenURI from url', async ({helper}) => { + const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'url', 'Token URI'); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Token URI'); }); - - itWeb3('TokenURI from baseURI + tokenId', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); - const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mint( - receiver, - nextTokenId, - ).send(); - - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); - + itEth('TokenURI from baseURI + tokenId', async ({helper}) => { + const {contract, nextTokenId} = await setup(helper, 'BaseURI_'); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + nextTokenId); }); - itWeb3('TokenURI from baseURI + suffix', async ({web3, api, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, owner); - let result = await helper.methods.createERC721MetadataCompatibleCollection('Mint collection', '1', '1', 'BaseURI_').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const receiver = createEthAccount(web3); - const contract = evmCollection(web3, owner, collectionIdAddress, {type: 'ReFungible'}); - - const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - result = await contract.methods.mint( - receiver, - nextTokenId, - ).send(); - - // Set suffix + itEth('TokenURI from baseURI + suffix', async ({helper}) => { const suffix = '/some/suffix'; - await contract.methods.setProperty(nextTokenId, 'suffix', Buffer.from(suffix)).send(); - - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: receiver, - tokenId: nextTokenId, - }, - }, - ]); - + const {contract, nextTokenId} = await setup(helper, 'BaseURI_', 'suffix', suffix); expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('BaseURI_' + suffix); }); }); describe('Refungible: Plain calls', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([50n], donor); + }); }); - itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('Can perform approve()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner}); - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; - - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; - - const address = tokenIdToAddress(collectionId, tokenId); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); - const spender = createEthAccount(web3); - - const contract = uniqueRefungibleToken(web3, address, owner); - { const result = await contract.methods.approve(spender, 100).send({from: owner}); - const events = normalizeEvents(result.events); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Approval', - args: { - owner, - spender, - value: '100', - }, - }, - ]); + const event = result.events.Approval; + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.owner).to.be.equal(owner); + expect(event.returnValues.spender).to.be.equal(spender); + expect(event.returnValues.value).to.be.equal('100'); } { @@ -265,49 +164,31 @@ } }); - itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; + itEth('Can perform transferFrom()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner}); - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; - - const spender = createEthAccount(web3); - await transferBalanceToEth(api, alice, spender); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); - const receiver = createEthAccount(web3); - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); - await contract.methods.approve(spender, 100).send(); { const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender}); - const events = normalizeEvents(result.events); - expect(events).to.include.deep.members([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: receiver, - value: '49', - }, - }, - { - address, - event: 'Approval', - args: { - owner, - spender, - value: '51', - }, - }, - ]); + let event = result.events.Transfer; + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.from).to.be.equal(owner); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('49'); + + event = result.events.Approval; + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.owner).to.be.equal(owner); + expect(event.returnValues.spender).to.be.equal(spender); + expect(event.returnValues.value).to.be.equal('51'); } { @@ -321,36 +202,22 @@ } }); - itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('Can perform transfer()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner}); - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; - - const receiver = createEthAccount(web3); - await transferBalanceToEth(api, alice, receiver); - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); { const result = await contract.methods.transfer(receiver, 50).send({from: owner}); - const events = normalizeEvents(result.events); - expect(events).to.include.deep.members([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: receiver, - value: '50', - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.from).to.be.equal(owner); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('50'); } { @@ -364,311 +231,262 @@ } }); - itWeb3('Can perform repartition()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; + itEth('Can perform repartition()', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 100n, {Ethereum: owner}); - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); - const receiver = createEthAccount(web3); - await transferBalanceToEth(api, alice, receiver); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId; - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); - await contract.methods.repartition(200).send({from: owner}); expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(200); await contract.methods.transfer(receiver, 110).send({from: owner}); expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(90); expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(110); - await expect(contract.methods.repartition(80).send({from: owner})).to.eventually.be.rejected; + await expect(contract.methods.repartition(80).send({from: owner})).to.eventually.be.rejected; // Transaction is reverted await contract.methods.transfer(receiver, 90).send({from: owner}); expect(+await contract.methods.balanceOf(owner).call()).to.be.equal(0); expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(200); await contract.methods.repartition(150).send({from: receiver}); - await expect(contract.methods.transfer(owner, 160).send({from: receiver})).to.eventually.be.rejected; + await expect(contract.methods.transfer(owner, 160).send({from: receiver})).to.eventually.be.rejected; // Transaction is reverted expect(+await contract.methods.balanceOf(receiver).call()).to.be.equal(150); }); - itWeb3('Can repartition with increased amount', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId; + itEth('Can repartition with increased amount', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 100n, {Ethereum: owner}); - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); const result = await contract.methods.repartition(200).send(); - const events = normalizeEvents(result.events); - expect(events).to.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: '0x0000000000000000000000000000000000000000', - to: owner, - value: '100', - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.be.equal(owner); + expect(event.returnValues.value).to.be.equal('100'); }); - itWeb3('Can repartition with decreased amount', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('Can repartition with decreased amount', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 100n, {Ethereum: owner}); - const collectionId = (await createCollection(api, alice, {name: 'token name', mode: {type: 'ReFungible'}})).collectionId; - - const owner = createEthAccount(web3); - await transferBalanceToEth(api, alice, owner); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 100n, {Ethereum: owner})).itemId; - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); const result = await contract.methods.repartition(50).send(); - const events = normalizeEvents(result.events); - expect(events).to.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: owner, - to: '0x0000000000000000000000000000000000000000', - value: '50', - }, - }, - ]); + const event = result.events.Transfer; + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.from).to.be.equal(owner); + expect(event.returnValues.to).to.be.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.value).to.be.equal('50'); }); - itWeb3('Receiving Transfer event on burning into full ownership', async ({web3, api, privateKeyWrapper}) => { - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const helper = evmCollectionHelpers(web3, caller); - const result = await helper.methods.createRFTCollection('Mint collection', '6', '6').send(); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const contract = evmCollection(web3, caller, collectionIdAddress, {type: 'ReFungible'}); + itEth('Receiving Transfer event on burning into full ownership', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const receiver = await helper.eth.createAccountWithBalance(donor); + const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(caller, 'Devastation', '6', '6'); + const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller); const tokenId = await contract.methods.nextTokenId().call(); await contract.methods.mint(caller, tokenId).send(); + const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId); + const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, caller); - const address = tokenIdToAddress(collectionId, tokenId); - - const tokenContract = uniqueRefungibleToken(web3, address, caller); await tokenContract.methods.repartition(2).send(); await tokenContract.methods.transfer(receiver, 1).send(); - const events = await recordEvents(contract, async () => - await tokenContract.methods.burnFrom(caller, 1).send()); - expect(events).to.deep.equal([ - { - address: collectionIdAddress, - event: 'Transfer', - args: { - from: '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF', - to: receiver, - tokenId, - }, - }, - ]); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); + }); + await tokenContract.methods.burnFrom(caller, 1).send(); + + const event = events[0]; + expect(event.address).to.be.equal(collectionAddress); + expect(event.returnValues.from).to.be.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.tokenId).to.be.equal(tokenId); }); }); describe('Refungible: Fees', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); + + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([50n], donor); + }); }); - itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 100n, {Ethereum: owner}); - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const spender = createEthAccount(web3); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); - - const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, 100).send({from: owner})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; + itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const spender = await helper.eth.createAccountWithBalance(donor); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner}); - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); await contract.methods.approve(spender, 100).send({from: owner}); - const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const {tokenId} = await collection.mintToken(alice, 200n, {Ethereum: owner}); - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress, owner); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n, {Ethereum: owner})).itemId; - - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address, owner); - - const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, 100).send({from: owner})); - expect(cost < BigInt(0.2 * Number(UNIQUE))); + const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner})); + expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); }); describe('Refungible: Substrate calls', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); - }); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); - itWeb3('Events emitted for approve()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - - const receiver = createEthAccount(web3); + donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([50n], donor); + }); + }); - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId; + itEth('Events emitted for approve()', async ({helper}) => { + const receiver = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 200n); - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress); - const events = await recordEvents(contract, async () => { - expect(await approve(api, collectionId, tokenId, alice, {Ethereum: receiver}, 100n)).to.be.true; + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); }); + expect(await token.approve(alice, {Ethereum: receiver}, 100n)).to.be.true; - expect(events).to.be.deep.equal([ - { - address, - event: 'Approval', - args: { - owner: subToEth(alice.address), - spender: receiver, - value: '100', - }, - }, - ]); + const event = events[0]; + expect(event.event).to.be.equal('Approval'); + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.spender).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('100'); }); - itWeb3('Events emitted for transferFrom()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('Events emitted for transferFrom()', async ({helper}) => { + const [bob] = await helper.arrange.createAccounts([10n], donor); + const receiver = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 200n); + await token.approve(alice, {Substrate: bob.address}, 100n); - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; - const bob = privateKeyWrapper('//Bob'); + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress); - const receiver = createEthAccount(web3); + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); + }); - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId; - expect(await approve(api, collectionId, tokenId, alice, bob.address, 100n)).to.be.true; + expect(await token.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n)).to.be.true; - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address); + let event = events[0]; + expect(event.event).to.be.equal('Transfer'); + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('51'); - const events = await recordEvents(contract, async () => { - expect(await transferFrom(api, collectionId, tokenId, bob, alice, {Ethereum: receiver}, 51n)).to.be.true; - }); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: subToEth(alice.address), - to: receiver, - value: '51', - }, - }, - { - address, - event: 'Approval', - args: { - owner: subToEth(alice.address), - spender: subToEth(bob.address), - value: '49', - }, - }, - ]); + event = events[1]; + expect(event.event).to.be.equal('Approval'); + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address)); + expect(event.returnValues.value).to.be.equal('49'); }); - itWeb3('Events emitted for transfer()', async ({web3, api, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + itEth('Events emitted for transfer()', async ({helper}) => { + const receiver = helper.eth.createAccount(); + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 200n); - const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; + const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId); + const contract = helper.ethNativeContract.rftToken(tokenAddress); - const receiver = createEthAccount(web3); - - const tokenId = (await createRefungibleToken(api, alice, collectionId, 200n)).itemId; + const events: any = []; + contract.events.allEvents((_: any, event: any) => { + events.push(event); + }); - const address = tokenIdToAddress(collectionId, tokenId); - const contract = uniqueRefungibleToken(web3, address); + expect(await token.transfer(alice, {Ethereum: receiver}, 51n)).to.be.true; - const events = await recordEvents(contract, async () => { - expect(await transfer(api, collectionId, tokenId, alice, {Ethereum: receiver}, 51n)).to.be.true; - }); - - expect(events).to.be.deep.equal([ - { - address, - event: 'Transfer', - args: { - from: subToEth(alice.address), - to: receiver, - value: '51', - }, - }, - ]); + const event = events[0]; + expect(event.event).to.be.equal('Transfer'); + expect(event.address).to.be.equal(tokenAddress); + expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address)); + expect(event.returnValues.to).to.be.equal(receiver); + expect(event.returnValues.value).to.be.equal('51'); }); }); describe('ERC 1633 implementation', () => { + let donor: IKeyringPair; + before(async function() { - await requirePallets(this, [Pallets.ReFungible]); - }); + await usingEthPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); - itWeb3('Default parent token address and id', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + donor = privateKey('//Alice'); + }); + }); - const {collectionIdAddress, collectionId} = await createRFTCollection(api, web3, owner); - const refungibleContract = uniqueRefungible(web3, collectionIdAddress, owner); - const refungibleTokenId = await refungibleContract.methods.nextTokenId().call(); - await refungibleContract.methods.mint(owner, refungibleTokenId).send(); + itEth('Default parent token address and id', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); - const rftTokenAddress = tokenIdToAddress(collectionId, refungibleTokenId); - const refungibleTokenContract = uniqueRefungibleToken(web3, rftTokenAddress, owner); + const {collectionId, collectionAddress} = await helper.eth.createRefungibleCollection(owner, 'Sands', '', 'GRAIN'); + const collectionContract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); + + const tokenId = await collectionContract.methods.nextTokenId().call(); + await collectionContract.methods.mint(owner, tokenId).send(); + const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId); + const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner); - const tokenAddress = await refungibleTokenContract.methods.parentToken().call(); - const tokenId = await refungibleTokenContract.methods.parentTokenId().call(); - expect(tokenAddress).to.be.equal(collectionIdAddress); - expect(tokenId).to.be.equal(refungibleTokenId); + expect(await tokenContract.methods.parentToken().call()).to.be.equal(collectionAddress); + expect(await tokenContract.methods.parentTokenId().call()).to.be.equal(tokenId); }); }); --- a/tests/src/eth/util/playgrounds/index.ts +++ b/tests/src/eth/util/playgrounds/index.ts @@ -12,6 +12,7 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import {requirePalletsOrSkip} from '../../../util/playgrounds'; chai.use(chaiAsPromised); export const expect = chai.expect; @@ -35,15 +36,26 @@ } }; -export async function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { - let i: any = it; - if (opts.only) i = i.only; - else if (opts.skip) i = i.skip; - i(name, async () => { +export async function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) { + (opts.only ? it.only : + opts.skip ? it.skip : it)(name, async function() { await usingEthPlaygrounds(async (helper, privateKey) => { + if (opts.requiredPallets) { + requirePalletsOrSkip(this, helper, opts.requiredPallets); + } + await cb({helper, privateKey}); }); }); } + +export async function itEthIfWithPallet(name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) { + return itEth(name, cb, {requiredPallets: required, ...opts}); +} + itEth.only = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {only: true}); -itEth.skip = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {skip: true}); \ No newline at end of file +itEth.skip = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {skip: true}); + +itEthIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEthIfWithPallet(name, required, cb, {only: true}); +itEthIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEthIfWithPallet(name, required, cb, {skip: true}); +itEth.ifWithPallets = itEthIfWithPallet; --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -27,6 +27,7 @@ import refungibleAbi from '../../reFungibleAbi.json'; import refungibleTokenAbi from '../../reFungibleTokenAbi.json'; import contractHelpersAbi from './../contractHelpersAbi.json'; +import {TEthereumAccount} from '../../../util/playgrounds/types'; class EthGroupBase { helper: EthUniqueHelper; @@ -43,13 +44,13 @@ return {error: `File not found: ${path}`}; }; - const knownImports = {} as any; + const knownImports = {} as {[key: string]: string}; for(const imp of imports) { knownImports[imp.solPath] = (await readFile(imp.fsPath)).toString(); } return function(path: string) { - if(knownImports.hasOwnPropertyDescriptor(path)) return {contents: knownImports[path]}; + if(path in knownImports) return {contents: knownImports[path]}; return {error: `File not found: ${path}`}; }; } @@ -116,13 +117,17 @@ return new web3.eth.Contract(abi as any, address, {gas: this.helper.eth.DEFAULT_GAS, ...(caller ? {from: caller} : {})}); } - rftTokenByAddress(address: string, caller?: string): Contract { + collectionById(collectionId: number, mode: 'nft' | 'rft' | 'ft', caller?: string): Contract { + return this.collection(this.helper.ethAddress.fromCollectionId(collectionId), mode, caller); + } + + rftToken(address: string, caller?: string): Contract { const web3 = this.helper.getWeb3(); return new web3.eth.Contract(refungibleTokenAbi as any, address, {gas: this.helper.eth.DEFAULT_GAS, ...(caller ? {from: caller} : {})}); } - rftToken(collectionId: number, tokenId: number, caller?: string): Contract { - return this.rftTokenByAddress(this.helper.ethAddress.fromTokenId(collectionId, tokenId), caller); + rftTokenById(collectionId: number, tokenId: number, caller?: string): Contract { + return this.rftToken(this.helper.ethAddress.fromTokenId(collectionId, tokenId), caller); } } @@ -148,7 +153,7 @@ return await this.helper.balance.transferToSubstrate(donor, evmToAddress(recepient), amount * (inTokens ? this.helper.balance.getOneTokenNominal() : 1n)); } - async callEVM(signer: IKeyringPair, contractAddress: string, abi: any, value: string, gasLimit?: number) { + async sendEVM(signer: IKeyringPair, contractAddress: string, abi: string, value: string, gasLimit?: number) { if(!gasLimit) gasLimit = this.DEFAULT_GAS; const web3 = this.helper.getWeb3(); const gasPrice = await web3.eth.getGasPrice(); @@ -159,6 +164,10 @@ true, ); } + + async callEVM(signer: TEthereumAccount, contractAddress: string, abi: string) { + return await this.helper.callRpc('api.rpc.eth.call', [{from: signer, to: contractAddress, data: abi}]); + } async createNonfungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> { const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer); @@ -171,6 +180,17 @@ return {collectionId, collectionAddress}; } + async createRefungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> { + const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer); + + const result = await collectionHelper.methods.createRFTCollection(name, description, tokenPrefix).send(); + + const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress); + + return {collectionId, collectionAddress}; + } + async deployCollectorContract(signer: string): Promise { return await this.helper.ethContract.deployByCode(signer, 'Collector', ` // SPDX-License-Identifier: UNLICENSED @@ -215,6 +235,16 @@ } `); } + + async recordCallFee(user: string, call: () => Promise): Promise { + const before = await this.helper.balance.getEthereum(user); + await call(); + // In dev mode, the transaction might not finish processing in time + await this.helper.wait.newBlocks(1); + const after = await this.helper.balance.getEthereum(user); + + return before - after; + } } class EthAddressGroup extends EthGroupBase { @@ -240,7 +270,7 @@ } fromTokenId(collectionId: number, tokenId: number): string { - return this.helper.util.getNestingTokenAddress(collectionId, tokenId); + return this.helper.util.getTokenAddress({collectionId, tokenId}); } normalizeAddress(address: string): string { --- a/tests/src/nesting/graphs.test.ts +++ b/tests/src/nesting/graphs.test.ts @@ -1,9 +1,22 @@ -import {ApiPromise} from '@polkadot/api'; +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + import {IKeyringPair} from '@polkadot/types/types'; -import {expect} from 'chai'; -import {tokenIdToCross} from '../eth/util/helpers'; -import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import {getCreateCollectionResult, transferExpectSuccess, setCollectionLimitsExpectSuccess} from '../util/helpers'; +import {expect, itSub, usingPlaygrounds} from '../util/playgrounds'; +import {UniqueHelper, UniqueNFToken} from '../util/playgrounds/unique'; /** * ```dot @@ -12,46 +25,47 @@ * 8 -> 5 * ``` */ -async function buildComplexObjectGraph(api: ApiPromise, sender: IKeyringPair): Promise { - const events = await executeTransaction(api, sender, api.tx.unique.createCollectionEx({mode: 'NFT', permissions: {nesting: {tokenOwner: true}}})); - const {collectionId} = getCreateCollectionResult(events); +async function buildComplexObjectGraph(helper: UniqueHelper, sender: IKeyringPair): Promise { + const collection = await helper.nft.mintCollection(sender, {permissions: {nesting: {tokenOwner: true}}}); + const tokens = await collection.mintMultipleTokens(sender, Array(8).fill({owner: {Substrate: sender.address}})); - await executeTransaction(api, sender, api.tx.unique.createMultipleItemsEx(collectionId, {NFT: Array(8).fill({owner: {Substrate: sender.address}})})); - - await transferExpectSuccess(collectionId, 8, sender, tokenIdToCross(collectionId, 5)); - - await transferExpectSuccess(collectionId, 7, sender, tokenIdToCross(collectionId, 6)); - await transferExpectSuccess(collectionId, 6, sender, tokenIdToCross(collectionId, 5)); - await transferExpectSuccess(collectionId, 5, sender, tokenIdToCross(collectionId, 2)); + await tokens[7].nest(sender, tokens[4]); + await tokens[6].nest(sender, tokens[5]); + await tokens[5].nest(sender, tokens[4]); + await tokens[4].nest(sender, tokens[1]); + await tokens[3].nest(sender, tokens[2]); + await tokens[2].nest(sender, tokens[1]); + await tokens[1].nest(sender, tokens[0]); - await transferExpectSuccess(collectionId, 4, sender, tokenIdToCross(collectionId, 3)); - await transferExpectSuccess(collectionId, 3, sender, tokenIdToCross(collectionId, 2)); - await transferExpectSuccess(collectionId, 2, sender, tokenIdToCross(collectionId, 1)); - - return collectionId; + return tokens; } describe('Graphs', () => { - it('Ouroboros can\'t be created in a complex graph', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const collection = await buildComplexObjectGraph(api, alice); - const tokenTwoParent = tokenIdToCross(collection, 1); + let alice: IKeyringPair; - // to self - await expect( - executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 1), collection, 1, 1)), - 'first transaction', - ).to.be.rejectedWith(/structure\.OuroborosDetected/); - // to nested part of graph - await expect( - executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 5), collection, 1, 1)), - 'second transaction', - ).to.be.rejectedWith(/structure\.OuroborosDetected/); - await expect( - executeTransaction(api, alice, api.tx.unique.transferFrom(tokenTwoParent, tokenIdToCross(collection, 8), collection, 2, 1)), - 'third transaction', - ).to.be.rejectedWith(/structure\.OuroborosDetected/); + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([10n], donor); }); }); + + itSub('Ouroboros can\'t be created in a complex graph', async ({helper}) => { + const tokens = await buildComplexObjectGraph(helper, alice); + + // to self + await expect( + tokens[0].nest(alice, tokens[0]), + 'first transaction', + ).to.be.rejectedWith(/structure\.OuroborosDetected/); + // to nested part of graph + await expect( + tokens[0].nest(alice, tokens[4]), + 'second transaction', + ).to.be.rejectedWith(/structure\.OuroborosDetected/); + await expect( + tokens[1].transferFrom(alice, tokens[0].nestingAccount(), tokens[7].nestingAccount()), + 'third transaction', + ).to.be.rejectedWith(/structure\.OuroborosDetected/); + }); }); --- a/tests/src/nesting/migration-check.test.ts +++ b/tests/src/nesting/migration-check.test.ts @@ -8,7 +8,8 @@ import find from 'find-process'; // todo un-skip for migrations -describe.skip('Migration testing', () => { +// todo:playgrounds skipped, this one is outdated. Probably to be deleted/replaced. +describe.skip('Migration testing: Properties', () => { let alice: IKeyringPair; before(async() => { --- a/tests/src/nesting/nest.test.ts +++ b/tests/src/nesting/nest.test.ts @@ -1,840 +1,667 @@ -import {expect} from 'chai'; -import {tokenIdToAddress} from '../eth/util/helpers'; -import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import { - addCollectionAdminExpectSuccess, - addToAllowListExpectSuccess, - createCollectionExpectSuccess, - createItemExpectSuccess, - enableAllowListExpectSuccess, - enablePublicMintingExpectSuccess, - getTokenChildren, - getTokenOwner, - getTopmostTokenOwner, - normalizeAccountId, - setCollectionPermissionsExpectSuccess, - transferExpectFailure, - transferExpectSuccess, - transferFromExpectSuccess, - setCollectionLimitsExpectSuccess, - requirePallets, - Pallets, -} from '../util/helpers'; -import {IKeyringPair} from '@polkadot/types/types'; +// 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. -let alice: IKeyringPair; -let bob: IKeyringPair; -let charlie: IKeyringPair; +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {expect, itSub, Pallets, usingPlaygrounds} from '../util/playgrounds'; + describe('Integration Test: Composite nesting tests', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + before(async () => { - await usingApi(async (_, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor); }); }); - it('Performs the full suite: bundles a token, transfers, and unnests', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + itSub('Performs the full suite: bundles a token, transfers, and unnests', 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 createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Create an immediately nested token + 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); - // Create a token to be nested - const newToken = await createItemExpectSuccess(alice, collection, 'NFT'); - - // Nest - await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Nest + await newToken.nest(alice, targetToken); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - // Move bundle to different user - await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address}); - expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address}); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Move bundle to different user + await targetToken.transfer(alice, {Substrate: bob.address}); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - // Unnest - await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address}); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address}); - }); + // Unnest + await newToken.unnest(bob, targetToken, {Substrate: bob.address}); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address}); }); - - it('Transfers an already bundled token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const tokenA = await createItemExpectSuccess(alice, collection, 'NFT'); - const tokenB = await createItemExpectSuccess(alice, collection, 'NFT'); - - // Create a nested token - const tokenC = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)}); - expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenA).toLowerCase()}); + itSub('Transfers an already bundled token', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); + const tokenA = await collection.mintToken(alice); + const tokenB = await collection.mintToken(alice); - // Transfer the nested token to another token - await expect(executeTransaction( - api, - alice, - api.tx.unique.transferFrom( - normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenA)}), - normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenB)}), - collection, - tokenC, - 1, - ), - )).to.not.be.rejected; - expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenB).toLowerCase()}); - }); + // 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}); + expect(await tokenC.getOwner()).to.be.deep.equal(tokenB.nestingAccount().toLowerCase()); }); - it('Checks token children', async () => { - await usingApi(async api => { - const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionLimitsExpectSuccess(alice, collectionA, {ownerCanTransfer: true}); - await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {tokenOwner: true}}); - const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + 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'); - const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)}; - let children = await getTokenChildren(api, collectionA, targetToken); - expect(children.length).to.be.equal(0, 'Children length check at creation'); + // Create a nested NFT token + const tokenA = await collectionA.mintToken(alice, targetToken.nestingAccount()); + expect(await targetToken.getChildren()).to.have.deep.members([ + {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId}, + ], 'Children contents check at nesting #1').and.be.length(1, 'Children length check at nesting #1'); - // Create a nested NFT token - const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress); - children = await getTokenChildren(api, collectionA, targetToken); - expect(children.length).to.be.equal(1, 'Children length check at nesting #1'); - expect(children).to.have.deep.members([ - {token: tokenA, collection: collectionA}, - ], 'Children contents check at nesting #1'); + // Create then nest + const tokenB = await collectionA.mintToken(alice); + await tokenB.nest(alice, targetToken); + expect(await targetToken.getChildren()).to.have.deep.members([ + {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId}, + {tokenId: tokenB.tokenId, collectionId: collectionA.collectionId}, + ], 'Children contents check at nesting #2').and.be.length(2, 'Children length check at nesting #2'); - // Create then nest - const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT'); - await transferExpectSuccess(collectionA, tokenB, alice, targetAddress); - children = await getTokenChildren(api, collectionA, targetToken); - expect(children.length).to.be.equal(2, 'Children length check at nesting #2'); - expect(children).to.have.deep.members([ - {token: tokenA, collection: collectionA}, - {token: tokenB, collection: collectionA}, - ], 'Children contents check at nesting #2'); + // Move token B to a different user outside the nesting tree + await tokenB.unnest(alice, targetToken, {Substrate: bob.address}); + expect(await targetToken.getChildren()).to.be.have.deep.members([ + {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId}, + ], 'Children contents check at nesting #3 (unnesting)').and.be.length(1, 'Children length check at nesting #3 (unnesting)'); - // Move token B to a different user outside the nesting tree - await transferFromExpectSuccess(collectionA, tokenB, alice, targetAddress, bob); - children = await getTokenChildren(api, collectionA, targetToken); - expect(children.length).to.be.equal(1, 'Children length check at unnesting'); - expect(children).to.be.have.deep.members([ - {token: tokenA, collection: collectionA}, - ], 'Children contents check at unnesting'); + // Create a fungible token in another collection and then nest + await collectionB.mint(alice, 10n); + await collectionB.transfer(alice, targetToken.nestingAccount(), 2n); + expect(await targetToken.getChildren()).to.be.have.deep.members([ + {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId}, + {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([ + {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId}, + {tokenId: 0, collectionId: collectionB.collectionId}, + ], 'Children contents check at nesting #5 (deeper)').and.be.length(2, 'Children length check at nesting #5 (deeper)'); + expect(await tokenA.getChildren()).to.be.have.deep.members([ + {tokenId: 0, collectionId: collectionB.collectionId}, + ], 'Children contents check at nesting #5.5 (deeper)').and.be.length(1, 'Children length check at nesting #5.5 (deeper)'); - // Create a fungible token in another collection and then nest - const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible'); - await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible'); - children = await getTokenChildren(api, collectionA, targetToken); - expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)'); - expect(children).to.be.have.deep.members([ - {token: tokenA, collection: collectionA}, - {token: tokenC, collection: collectionB}, - ], 'Children contents check at nesting #3 (from another collection)'); - - // Move the fungible token inside token A deeper in the nesting tree - await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible'); - children = await getTokenChildren(api, collectionA, targetToken); - expect(children.length).to.be.equal(1, 'Children length check at deeper nesting'); - expect(children).to.be.have.deep.members([ - {token: tokenA, collection: collectionA}, - ], 'Children contents check at deeper nesting'); - }); + // Move the remaining 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([ + {tokenId: tokenA.tokenId, collectionId: collectionA.collectionId}, + ], 'Children contents check at nesting #6 (deeper)').and.be.length(1, 'Children length check at nesting #6 (deeper)'); + expect(await tokenA.getChildren()).to.be.have.deep.members([ + {tokenId: 0, collectionId: collectionB.collectionId}, + ], 'Children contents check at nesting #6.5 (deeper)').and.be.length(1, 'Children length check at nesting #6.5 (deeper)'); }); }); -describe('Integration Test: Various token type nesting', async () => { +describe('Integration Test: Various token type nesting', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + before(async () => { - await usingApi(async (_, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - charlie = privateKeyWrapper('//Charlie'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob, charlie] = await helper.arrange.createAccounts([50n, 10n, 10n], donor); }); }); - it('Admin (NFT): allows an Admin to nest a token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}}); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address); + itSub('Admin (NFT): allows an Admin to nest a token', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true}}}); + await collection.addAdmin(alice, {Substrate: bob.address}); + const targetToken = await collection.mintToken(alice, {Substrate: charlie.address}); - // Create a nested token - const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address}); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Create an immediately nested token + const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount()); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - // Create a token to be nested and nest - const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); - await transferExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address}); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); - }); + // Create a token to be nested and nest + const newToken = await collection.mintToken(bob); + await newToken.nest(bob, targetToken); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); }); - it('Admin (NFT): Admin and Token Owner can operate together', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, collectionAdmin: true}}); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address); + itSub('Admin (NFT): Admin and Token Owner can operate together', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true, tokenOwner: true}}}); + await collection.addAdmin(alice, {Substrate: bob.address}); + const targetToken = await collection.mintToken(alice, {Substrate: charlie.address}); - // Create a nested token by an administrator - const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address}); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Create an immediately nested token by an administrator + const nestedToken = await collection.mintToken(bob, targetToken.nestingAccount()); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - // Create a token and allow the owner to nest too - const newToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address); - await transferExpectSuccess(collection, newToken, charlie, {Ethereum: tokenIdToAddress(collection, nestedToken)}); - expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address}); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, nestedToken).toLowerCase()}); - }); + // Create a token to be nested and nest + const newToken = await collection.mintToken(alice, {Substrate: charlie.address}); + await newToken.nest(charlie, targetToken); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); }); - it('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async () => { - await usingApi(async api => { - const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await addCollectionAdminExpectSuccess(alice, collectionA, bob.address); - const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await addCollectionAdminExpectSuccess(alice, collectionB, bob.address); - await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA, collectionB]}}); - const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT', charlie.address); + itSub('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async ({helper}) => { + const collectionA = await helper.nft.mintCollection(alice); + await collectionA.addAdmin(alice, {Substrate: bob.address}); + const collectionB = await helper.nft.mintCollection(alice); + await collectionB.addAdmin(alice, {Substrate: bob.address}); + await collectionA.setPermissions(alice, {nesting: {collectionAdmin: true, restricted:[collectionB.collectionId]}}); + const targetToken = await collectionA.mintToken(alice, {Substrate: charlie.address}); - // Create a nested token - const nestedToken = await createItemExpectSuccess(bob, collectionB, 'NFT', {Ethereum: tokenIdToAddress(collectionA, targetToken)}); - expect(await getTopmostTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Substrate: charlie.address}); - expect(await getTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()}); + // Create an immediately nested token + const nestedToken = await collectionB.mintToken(bob, targetToken.nestingAccount()); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - // Create a token to be nested and nest - const newToken = await createItemExpectSuccess(bob, collectionB, 'NFT'); - await transferExpectSuccess(collectionB, newToken, bob, {Ethereum: tokenIdToAddress(collectionA, targetToken)}); - expect(await getTopmostTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: charlie.address}); - expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()}); - }); + // Create a token to be nested and nest + const newToken = await collectionB.mintToken(bob); + await newToken.nest(bob, targetToken); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); }); // ---------- Non-Fungible ---------- - it('NFT: allows an Owner to nest/unnest their token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + itSub('NFT: allows an Owner to nest/unnest their token', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}}); + await collection.addToAllowList(alice, {Substrate: charlie.address}); + const targetToken = await collection.mintToken(charlie); + await collection.addToAllowList(alice, targetToken.nestingAccount()); - // Create a nested token - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Create an immediately nested token + const nestedToken = await collection.mintToken(charlie, targetToken.nestingAccount()); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - // Create a token to be nested and nest - const newToken = await createItemExpectSuccess(alice, collection, 'NFT'); - await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); - }); + // Create a token to be nested and nest + const newToken = await collection.mintToken(charlie); + await newToken.nest(charlie, targetToken); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); }); - it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + itSub('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => { + const collectionA = await helper.nft.mintCollection(alice); + const collectionB = await helper.nft.mintCollection(alice); + //await collectionB.addAdmin(alice, {Substrate: bob.address}); + const targetToken = await collectionA.mintToken(alice, {Substrate: charlie.address}); - // Create a nested token - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + await collectionA.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted:[collectionB.collectionId]}}); + await collectionA.addToAllowList(alice, {Substrate: charlie.address}); + await collectionA.addToAllowList(alice, targetToken.nestingAccount()); + + await collectionB.setPermissions(alice, {access: 'AllowList', mintMode: true}); + await collectionB.addToAllowList(alice, {Substrate: charlie.address}); + await collectionB.addToAllowList(alice, targetToken.nestingAccount()); - // Create a token to be nested and nest - const newToken = await createItemExpectSuccess(alice, collection, 'NFT'); - await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)}); - expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); - }); + // Create an immediately nested token + const nestedToken = await collectionB.mintToken(charlie, targetToken.nestingAccount()); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); + + // Create a token to be nested and nest + const newToken = await collectionB.mintToken(charlie); + await newToken.nest(charlie, targetToken); + expect(await newToken.getTopmostOwner()).to.be.deep.equal({Substrate: charlie.address}); + expect(await newToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); }); // ---------- Fungible ---------- - it('Fungible: allows an Owner to nest/unnest their token', async () => { - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address}); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + itSub('Fungible: allows an Owner to nest/unnest their token', async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}}); + const collectionFT = await helper.ft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address}); - const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + await collectionNFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionNFT.addToAllowList(alice, targetToken.nestingAccount()); - // Create a nested token - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionFT, - targetAddress, - {Fungible: {Value: 10}}, - ))).to.not.be.rejected; + await collectionFT.setPermissions(alice, {access: 'AllowList', mintMode: true}); + await collectionFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionFT.addToAllowList(alice, targetToken.nestingAccount()); - // Nest a new token - const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); - await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible'); - }); + // Create an immediately nested token + await collectionFT.mint(charlie, 5n, targetToken.nestingAccount()); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n); + + // Create a token to be nested and nest + await collectionFT.mint(charlie, 5n); + await collectionFT.transfer(charlie, targetToken.nestingAccount(), 2n); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(7n); }); - it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => { - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address}); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + itSub('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionFT = await helper.ft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address}); - const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted:[collectionFT.collectionId]}}); + await collectionNFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionNFT.addToAllowList(alice, targetToken.nestingAccount()); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted: [collectionFT]}}); + await collectionFT.setPermissions(alice, {access: 'AllowList', mintMode: true}); + await collectionFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionFT.addToAllowList(alice, targetToken.nestingAccount()); - // Create a nested token - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionFT, - targetAddress, - {Fungible: {Value: 10}}, - ))).to.not.be.rejected; + // Create an immediately nested token + await collectionFT.mint(charlie, 5n, targetToken.nestingAccount()); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n); - // Nest a new token - const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); - await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible'); - }); + // Create a token to be nested and nest + await collectionFT.mint(charlie, 5n); + await collectionFT.transfer(charlie, targetToken.nestingAccount(), 2n); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(7n); }); // ---------- Re-Fungible ---------- - it('ReFungible: allows an Owner to nest/unnest their token', async function() { - await requirePallets(this, [Pallets.ReFungible]); + itSub.ifWithPallets('ReFungible: allows an Owner to nest/unnest their token', [Pallets.ReFungible], async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}}); + const collectionRFT = await helper.rft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address}); - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address}); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + await collectionNFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionNFT.addToAllowList(alice, targetToken.nestingAccount()); - const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + await collectionRFT.setPermissions(alice, {access: 'AllowList', mintMode: true}); + await collectionRFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionRFT.addToAllowList(alice, targetToken.nestingAccount()); - // Create a nested token - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionRFT, - targetAddress, - {ReFungible: {pieces: 100}}, - ))).to.not.be.rejected; + // Create an immediately nested token + const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAccount()); + expect(await nestedToken.getBalance(targetToken.nestingAccount())).to.be.equal(5n); - // Nest a new token - const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible'); - }); + // Create a token to be nested and nest + const newToken = await collectionRFT.mintToken(charlie, 5n); + await newToken.transfer(charlie, targetToken.nestingAccount(), 2n); + expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(2n); }); - it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async function() { - await requirePallets(this, [Pallets.ReFungible]); + itSub.ifWithPallets('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', [Pallets.ReFungible], async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionRFT = await helper.rft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice, {Substrate: charlie.address}); - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address}); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted:[collectionRFT.collectionId]}}); + await collectionNFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionNFT.addToAllowList(alice, targetToken.nestingAccount()); - const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + await collectionRFT.setPermissions(alice, {access: 'AllowList', mintMode: true}); + await collectionRFT.addToAllowList(alice, {Substrate: charlie.address}); + await collectionRFT.addToAllowList(alice, targetToken.nestingAccount()); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}}); + // Create an immediately nested token + const nestedToken = await collectionRFT.mintToken(charlie, 5n, targetToken.nestingAccount()); + expect(await nestedToken.getBalance(targetToken.nestingAccount())).to.be.equal(5n); - // Create a nested token - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionRFT, - targetAddress, - {ReFungible: {pieces: 100}}, - ))).to.not.be.rejected; - - // Nest a new token - const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible'); - }); + // Create a token to be nested and nest + const newToken = await collectionRFT.mintToken(charlie, 5n); + await newToken.transfer(charlie, targetToken.nestingAccount(), 2n); + expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(2n); }); }); -describe('Negative Test: Nesting', async() => { +describe('Negative Test: Nesting', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + before(async () => { - await usingApi(async (_, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob] = await helper.arrange.createAccounts([100n, 50n], donor); }); }); - it('Disallows excessive token nesting', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); - - const maxNestingLevel = 5; - let prevToken = targetToken; + itSub('Disallows excessive token nesting', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); + let token = await collection.mintToken(alice); - // Create a nested-token matryoshka - for (let i = 0; i < maxNestingLevel; i++) { - const nestedToken = await createItemExpectSuccess( - alice, - collection, - 'NFT', - {Ethereum: tokenIdToAddress(collection, prevToken)}, - ); + const maxNestingLevel = 5; - prevToken = nestedToken; - } - - // The nesting depth is limited by `maxNestingLevel` - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collection, - {Ethereum: tokenIdToAddress(collection, prevToken)}, - {nft: {}} as any, - )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/); + // Create a nested-token matryoshka + for (let i = 0; i < maxNestingLevel; i++) { + token = await collection.mintToken(alice, token.nestingAccount()); + } - expect(await getTopmostTokenOwner(api, collection, prevToken)).to.be.deep.equal({Substrate: alice.address}); - }); + // The nesting depth is limited by `maxNestingLevel` + await expect(collection.mintToken(alice, token.nestingAccount())) + .to.be.rejectedWith(/structure\.DepthLimit/); + expect(await token.getTopmostOwner()).to.be.deep.equal({Substrate: alice.address}); + expect(await token.getChildren()).to.be.length(0); }); // ---------- Admin ------------ - it('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + itSub('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); + await collection.addAdmin(alice, {Substrate: bob.address}); + const targetToken = await collection.mintToken(alice); - // Try to create a nested token as collection admin when it's disallowed - await expect(executeTransaction(api, bob, api.tx.unique.createItem( - collection, - {Ethereum: tokenIdToAddress(collection, targetToken)}, - {nft: {}} as any, - )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Try to create an immediately nested token as collection admin when it's disallowed + await expect(collection.mintToken(bob, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); - await expect(executeTransaction( - api, - bob, - api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1), - ), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address}); - }); + // Try to create a token to be nested and nest + const newToken = await collection.mintToken(bob); + await expect(newToken.nest(bob, targetToken)) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address}); }); - it('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}}); - await addToAllowListExpectSuccess(alice, collection, bob.address); - await enableAllowListExpectSuccess(alice, collection); - await enablePublicMintingExpectSuccess(alice, collection); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + itSub('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true}}}); + const targetToken = await collection.mintToken(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, targetToken.nestingAccount()); + + // Try to create a nested token as token owner when it's disallowed + await expect(collection.mintToken(bob, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - // Try to create a nested token as collection admin when it's disallowed - await expect(executeTransaction(api, bob, api.tx.unique.createItem( - collection, - {Ethereum: tokenIdToAddress(collection, targetToken)}, - {nft: {}} as any, - )), 'while creating nested token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); + // Try to create a token to be nested and nest + const newToken = await collection.mintToken(bob); + await expect(newToken.nest(bob, targetToken)) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); - await expect(executeTransaction( - api, - bob, - api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1), - ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address}); - }); + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: bob.address}); }); - it('Admin (NFT): disallows an Admin to nest and unnest someone else\'s token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}}); + itSub('Admin (NFT): disallows an Admin to unnest someone else\'s token', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {limits: {ownerCanTransfer: true}, permissions: {access: 'AllowList', mintMode: true, nesting: {collectionAdmin: true}}}); + //await collection.addAdmin(alice, {Substrate: bob.address}); + const targetToken = await collection.mintToken(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, targetToken.nestingAccount()); - await addToAllowListExpectSuccess(alice, collection, bob.address); - await enableAllowListExpectSuccess(alice, collection); - await enablePublicMintingExpectSuccess(alice, collection); - - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(bob, collection, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}; + // Try to nest somebody else's token + const newToken = await collection.mintToken(bob); + await expect(newToken.nest(alice, targetToken)) + .to.be.rejectedWith(/common\.NoPermission/); - // Try to nest somebody else's token - const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); - await expect(executeTransaction( - api, - alice, - api.tx.unique.transferFrom(targetAddress, {Substrate: bob.address}, collection, newToken, 1), - ), 'while nesting another\'s token token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address}); + // Try to unnest a token belonging to someone else as collection admin + const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); + await expect(nestedToken.unnest(alice, targetToken, {Substrate: bob.address})) + .to.be.rejectedWith(/common\.AddressNotInAllowlist/); - // Nest a token as admin and try to unnest it, now belonging to someone else - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress); - await expect(executeTransaction( - api, - alice, - api.tx.unique.transferFrom(targetAddress, normalizeAccountId(alice), collection, nestedToken, 1), - ), 'while unnesting another\'s token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal(targetAddress); - expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address}); - }); + expect(await targetToken.getChildren()).to.be.length(1); + expect(await nestedToken.getTopmostOwner()).to.be.deep.equal({Substrate: bob.address}); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); }); - it('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async () => { - await usingApi(async api => { - const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA]}}); + itSub('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async ({helper}) => { + const collectionA = await helper.nft.mintCollection(alice); + const collectionB = await helper.nft.mintCollection(alice); + await collectionA.setPermissions(alice, {nesting: {collectionAdmin: true, restricted: [collectionA.collectionId]}}); + const targetToken = await collectionA.mintToken(alice); - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT'); + // Try to create a nested token from another collection + await expect(collectionB.mintToken(alice, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collectionB, 'NFT'); - await expect(executeTransaction( - api, - alice, - api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionA, targetToken)}, collectionB, newToken, 1), - ), 'while nesting a foreign token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address}); - }); + // Create a token in another collection yet to be nested and try to nest + const newToken = await collectionB.mintToken(alice); + await expect(newToken.nest(alice, targetToken)) + .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address}); }); // ---------- Non-Fungible ---------- - it('NFT: disallows to nest token if nesting is disabled', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + itSub('NFT: disallows to nest token if nesting is disabled', async ({helper}) => { + // Collection is implicitly not allowed nesting at creation + const collection = await helper.nft.mintCollection(alice); + const targetToken = await collection.mintToken(alice); - // Try to create a nested token - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collection, - {Ethereum: tokenIdToAddress(collection, targetToken)}, - {nft: {}} as any, - )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); + // Try to create a nested token as token owner when it's disallowed + await expect(collection.mintToken(alice, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - // Create a token to be nested - const newToken = await createItemExpectSuccess(alice, collection, 'NFT'); - // Try to nest - await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - }); + // Try to create a token to be nested and nest + const newToken = await collection.mintToken(alice); + await expect(newToken.nest(alice, targetToken)) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address}); }); - it('NFT: disallows a non-Owner to nest someone else\'s token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); + itSub('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const targetToken = await collection.mintToken(alice); - await addToAllowListExpectSuccess(alice, collection, bob.address); - await enableAllowListExpectSuccess(alice, collection); - await enablePublicMintingExpectSuccess(alice, collection); - - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(bob, collection, 'NFT'); + await collection.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}); + await collection.addToAllowList(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, targetToken.nestingAccount()); - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collection, - {Ethereum: tokenIdToAddress(collection, targetToken)}, - {nft: {}} as any, - )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Try to create a token to be nested and nest + const newToken = await collection.mintToken(alice); + await expect(newToken.nest(bob, targetToken)).to.be.rejectedWith(/common\.NoPermission/); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collection, 'NFT'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - }); + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address}); }); - it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}}); + itSub('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const targetToken = await collection.mintToken(alice); - await addToAllowListExpectSuccess(alice, collection, bob.address); - await enableAllowListExpectSuccess(alice, collection); - await enablePublicMintingExpectSuccess(alice, collection); + await collection.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}); + await collection.addToAllowList(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, targetToken.nestingAccount()); - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(bob, collection, 'NFT'); + const collectionB = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true}}); + await collectionB.addToAllowList(alice, {Substrate: bob.address}); + await collectionB.addToAllowList(alice, targetToken.nestingAccount()); - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collection, - {Ethereum: tokenIdToAddress(collection, targetToken)}, - {nft: {}} as any, - )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Try to create a token to be nested and nest + const newToken = await collectionB.mintToken(alice); + await expect(newToken.nest(bob, targetToken)).to.be.rejectedWith(/common\.NoPermission/); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collection, 'NFT'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - }); + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getOwner()).to.be.deep.equal({Substrate: alice.address}); }); - it('NFT: disallows to nest token in an unlisted collection', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[]}}); + itSub('NFT: disallows to nest token in an unlisted collection', async ({helper}) => { + // Create collection with restricted nesting -- even self is not allowed + const collection = await helper.nft.mintCollection(alice, {permissions: {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted: []}}}); + const targetToken = await collection.mintToken(alice, {Substrate: bob.address}); - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + await collection.addToAllowList(alice, {Substrate: bob.address}); + await collection.addToAllowList(alice, targetToken.nestingAccount()); - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collection, - {Ethereum: tokenIdToAddress(collection, targetToken)}, - {nft: {}} as any, - )), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collection, 'NFT'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); - }); + // Try to mint in own collection after allowlisting the accounts + await expect(collection.mintToken(bob, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); }); // ---------- Fungible ---------- - - it('Fungible: disallows to nest token if nesting is disabled', async () => { - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}}); - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; - - const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - // Try to create a nested token - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionFT, - targetAddress, - {Fungible: {Value: 10}}, - )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); + itSub('Fungible: disallows to nest token if nesting is disabled', async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionFT = await helper.ft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice); - // Create a token to be nested - const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); - // Try to nest - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Try to create an immediately nested token + await expect(collectionFT.mint(alice, 5n, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - // Create another token to be nested - const newToken2 = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); - // Try to nest inside a fungible token - await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionFT, newToken)}, collectionFT, newToken2, 1)), 'while nesting new token inside fungible').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/); - }); + // Try to create a token to be nested and nest + await collectionFT.mint(alice, 5n); + await expect(collectionFT.transfer(alice, targetToken.nestingAccount(), 2n)) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(5n); }); - it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => { - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}}); + itSub('Fungible: disallows a non-Owner to unnest someone else\'s token', async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true, tokenOwner: true}}}); + const collectionFT = await helper.ft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address}); - await addToAllowListExpectSuccess(alice, collectionNFT, bob.address); - await enableAllowListExpectSuccess(alice, collectionNFT); - await enablePublicMintingExpectSuccess(alice, collectionNFT); + // Nest some tokens as Alice into Bob's token + await collectionFT.mint(alice, 5n, targetToken.nestingAccount()); - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; - - const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionFT, - targetAddress, - {Fungible: {Value: 10}}, - )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - }); + // Try to pull it out + await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n)) + .to.be.rejectedWith(/common\.ApprovedValueTooLow/); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n); }); - it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => { - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await addToAllowListExpectSuccess(alice, collectionNFT, bob.address); - await enableAllowListExpectSuccess(alice, collectionNFT); - await enablePublicMintingExpectSuccess(alice, collectionNFT); - - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + itSub('Fungible: disallows a non-Owner to unnest someone else\'s token (Restricted nesting)', async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionFT = await helper.ft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice, {Substrate: bob.address}); - const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionFT]}}); + await collectionNFT.setPermissions(alice, {nesting: {collectionAdmin: true, tokenOwner: true, restricted: [collectionFT.collectionId]}}); - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionFT, - targetAddress, - {Fungible: {Value: 10}}, - )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Nest some tokens as Alice into Bob's token + await collectionFT.mint(alice, 5n, targetToken.nestingAccount()); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - }); + // Try to pull it out as Alice still + await expect(collectionFT.transferFrom(alice, targetToken.nestingAccount(), {Substrate: bob.address}, 1n)) + .to.be.rejectedWith(/common\.ApprovedValueTooLow/); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(5n); }); - it('Fungible: disallows to nest token in an unlisted collection', async () => { - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}}); - - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + itSub('Fungible: disallows to nest token in an unlisted collection', async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {collectionAdmin: true, tokenOwner: true, restricted: []}}}); + const collectionFT = await helper.ft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice); - const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + // Try to mint an immediately nested token + await expect(collectionFT.mint(alice, 5n, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionFT, - targetAddress, - {Fungible: {Value: 10}}, - )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + // Mint a token and try to nest it + await collectionFT.mint(alice, 5n); + await expect(collectionFT.transfer(alice, targetToken.nestingAccount(), 1n)) + .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - }); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(0n); + expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(5n); }); // ---------- Re-Fungible ---------- - - it('ReFungible: disallows to nest token if nesting is disabled', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}}); - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; - - const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - - // Create a nested token - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionRFT, - targetAddress, - {ReFungible: {pieces: 100}}, - )), 'while creating a nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); + itSub.ifWithPallets('ReFungible: disallows to nest token if nesting is disabled', [Pallets.ReFungible], async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionRFT = await helper.rft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice); - // Create a token to be nested - const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - // Try to nest - await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100); - // Try to nest - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Try to create an immediately nested token + await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - // Create another token to be nested - const newToken2 = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - // Try to nest inside a fungible token - await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionRFT, newToken)}, collectionRFT, newToken2, 1)), 'while nesting new token inside refungible').to.be.rejectedWith(/refungible\.RefungibleDisallowsNesting/); - }); + // Try to create a token to be nested and nest + const token = await collectionRFT.mintToken(alice, 5n); + await expect(token.transfer(alice, targetToken.nestingAccount(), 2n)) + .to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n); }); - - it('ReFungible: disallows a non-Owner to nest someone else\'s token', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}}); + itSub.ifWithPallets('ReFungible: disallows a non-Owner to nest someone else\'s token', [Pallets.ReFungible], async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionRFT = await helper.rft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice); - await addToAllowListExpectSuccess(alice, collectionNFT, bob.address); - await enableAllowListExpectSuccess(alice, collectionNFT); - await enablePublicMintingExpectSuccess(alice, collectionNFT); + await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true}}); + await collectionNFT.addToAllowList(alice, {Substrate: bob.address}); + await collectionNFT.addToAllowList(alice, targetToken.nestingAccount()); - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + // Try to create a token to be nested and nest + const newToken = await collectionRFT.mintToken(alice); + await expect(newToken.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.TokenValueTooLow/); - const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getBalance({Substrate: alice.address})).to.be.equal(1n); - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionRFT, - targetAddress, - {ReFungible: {pieces: 100}}, - )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Nest some tokens as Alice into Bob's token + await newToken.transfer(alice, targetToken.nestingAccount()); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - }); + // Try to pull it out + await expect(newToken.transferFrom(bob, targetToken.nestingAccount(), {Substrate: alice.address}, 1n)) + .to.be.rejectedWith(/common\.ApprovedValueTooLow/); + expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(1n); }); - it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async function() { - await requirePallets(this, [Pallets.ReFungible]); + itSub.ifWithPallets('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', [Pallets.ReFungible], async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice); + const collectionRFT = await helper.rft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice); - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await addToAllowListExpectSuccess(alice, collectionNFT, bob.address); - await enableAllowListExpectSuccess(alice, collectionNFT); - await enablePublicMintingExpectSuccess(alice, collectionNFT); + await collectionNFT.setPermissions(alice, {access: 'AllowList', mintMode: true, nesting: {tokenOwner: true, restricted: [collectionRFT.collectionId]}}); + await collectionNFT.addToAllowList(alice, {Substrate: bob.address}); + await collectionNFT.addToAllowList(alice, targetToken.nestingAccount()); - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; + // Try to create a token to be nested and nest + const newToken = await collectionRFT.mintToken(alice); + await expect(newToken.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.TokenValueTooLow/); - const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}}); + expect(await targetToken.getChildren()).to.be.length(0); + expect(await newToken.getBalance({Substrate: alice.address})).to.be.equal(1n); - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionRFT, - targetAddress, - {ReFungible: {pieces: 100}}, - )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + // Nest some tokens as Alice into Bob's token + await newToken.transfer(alice, targetToken.nestingAccount()); - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); - }); + // Try to pull it out + await expect(newToken.transferFrom(bob, targetToken.nestingAccount(), {Substrate: alice.address}, 1n)) + .to.be.rejectedWith(/common\.ApprovedValueTooLow/); + expect(await newToken.getBalance(targetToken.nestingAccount())).to.be.equal(1n); }); - it('ReFungible: disallows to nest token to an unlisted collection', async function() { - await requirePallets(this, [Pallets.ReFungible]); + itSub.ifWithPallets('ReFungible: disallows to nest token to an unlisted collection', [Pallets.ReFungible], async ({helper}) => { + const collectionNFT = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true, restricted: []}}}); + const collectionRFT = await helper.rft.mintCollection(alice); + const targetToken = await collectionNFT.mintToken(alice); - await usingApi(async api => { - const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}}); + // Try to create an immediately nested token + await expect(collectionRFT.mintToken(alice, 5n, targetToken.nestingAccount())) + .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - // Create a token to attempt to be nested into - const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)}; - - const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - - // Try to create a nested token in the wrong collection - await expect(executeTransaction(api, alice, api.tx.unique.createItem( - collectionRFT, - targetAddress, - {ReFungible: {pieces: 100}}, - )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - - // Try to create and nest a token in the wrong collection - const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - }); + // Try to create a token to be nested and nest + const token = await collectionRFT.mintToken(alice, 5n); + await expect(token.transfer(alice, targetToken.nestingAccount(), 2n)) + .to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(5n); }); }); --- a/tests/src/nesting/properties.test.ts +++ b/tests/src/nesting/properties.test.ts @@ -1,1004 +1,787 @@ -import {expect} from 'chai'; -import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import { - addCollectionAdminExpectSuccess, - CollectionMode, - createCollectionExpectSuccess, - setCollectionPermissionsExpectSuccess, - createItemExpectSuccess, - getCreateCollectionResult, - transferExpectSuccess, - requirePallets, - Pallets, -} from '../util/helpers'; -import {IKeyringPair} from '@polkadot/types/types'; -import {tokenIdToAddress} from '../eth/util/helpers'; +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. -let alice: IKeyringPair; -let bob: IKeyringPair; -let charlie: IKeyringPair; +// 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. -describe('Composite Properties Test', () => { - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - }); - }); +// 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. - async function testMakeSureSuppliesRequired(mode: CollectionMode) { - await usingApi(async api => { - const collectionId = await createCollectionExpectSuccess({mode: mode}); +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . - const collectionOption = await api.rpc.unique.collectionById(collectionId); - expect(collectionOption.isSome).to.be.true; - let collection = collectionOption.unwrap(); - expect(collection.tokenPropertyPermissions.toHuman()).to.be.empty; - expect(collection.properties.toHuman()).to.be.empty; +import {IKeyringPair} from '@polkadot/types/types'; +import {itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds, expect} from '../util/playgrounds'; +import {UniqueHelper, UniqueBaseCollection, UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection, UniqueRFToken} from '../util/playgrounds/unique'; - const propertyPermissions = [ - {key: 'mindgame', permission: {collectionAdmin: true, mutable: false, tokenOwner: true}}, - {key: 'skullduggery', permission: {collectionAdmin: false, mutable: true, tokenOwner: false}}, - ]; - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collectionId, propertyPermissions), - )).to.not.be.rejected; +// ---------- COLLECTION PROPERTIES - const collectionProperties = [ - {key: 'black_hole', value: 'LIGO'}, - {key: 'electron', value: 'come bond'}, - ]; - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collectionId, collectionProperties), - )).to.not.be.rejected; +describe('Integration Test: Collection Properties', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; - collection = (await api.rpc.unique.collectionById(collectionId)).unwrap(); - expect(collection.tokenPropertyPermissions.toHuman()).to.be.deep.equal(propertyPermissions); - expect(collection.properties.toHuman()).to.be.deep.equal(collectionProperties); + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor); }); - } + }); - it('Makes sure collectionById supplies required fields for NFT', async () => { - await testMakeSureSuppliesRequired({type: 'NFT'}); + itSub('Properties are initially empty', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + expect(await collection.getProperties()).to.be.empty; }); - it('Makes sure collectionById supplies required fields for ReFungible', async function() { - await requirePallets(this, [Pallets.ReFungible]); + async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) { + // As owner + await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled; + + await collection.addAdmin(alice, {Substrate: bob.address}); - await testMakeSureSuppliesRequired({type: 'ReFungible'}); - }); -}); + // As administrator + await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled; -// ---------- COLLECTION PROPERTIES + const properties = await collection.getProperties(); + expect(properties).to.include.deep.members([ + {key: 'electron', value: 'come bond'}, + {key: 'black_hole', value: ''}, + ]); + } -describe('Integration Test: Collection Properties', () => { - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - }); + itSub('Sets properties for a NFT collection', async ({helper}) => { + await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice)); }); - it('Reads properties from a collection', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess(); - const properties = (await api.query.common.collectionProperties(collection)).toJSON(); - expect(properties.map).to.be.empty; - expect(properties.consumedSpace).to.equal(0); - }); + itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => { + await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice)); }); + async function testCheckValidNames(collection: UniqueBaseCollection) { + // alpha symbols + await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled; - async function testSetsPropertiesForCollection(mode: string) { - await usingApi(async api => { - const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode})); - const {collectionId} = getCreateCollectionResult(events); + // numeric symbols + await expect(collection.setProperties(alice, [{key: '451'}])).to.be.fulfilled; - // As owner - await expect(executeTransaction( - api, - bob, - api.tx.unique.setCollectionProperties(collectionId, [{key: 'electron', value: 'come bond'}]), - )).to.not.be.rejected; + // underscore symbol + await expect(collection.setProperties(alice, [{key: 'black_hole'}])).to.be.fulfilled; - await addCollectionAdminExpectSuccess(bob, collectionId, alice.address); + // dash symbol + await expect(collection.setProperties(alice, [{key: '-'}])).to.be.fulfilled; - // As administrator - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), - )).to.not.be.rejected; + // 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 api.rpc.unique.collectionProperties(collectionId, ['electron', 'black_hole'])).toHuman(); - expect(properties).to.be.deep.equal([ - {key: 'electron', value: 'come bond'}, - {key: 'black_hole', value: ''}, - ]); - }); + const properties = await collection.getProperties(); + expect(properties).to.include.deep.members([ + {key: 'answer', value: ''}, + {key: '451', value: ''}, + {key: 'black_hole', value: ''}, + {key: '-', value: ''}, + {key: 'once.in.a.long.long.while...', value: 'you get a little lost'}, + ]); } - it('Sets properties for a NFT collection', async () => { - await testSetsPropertiesForCollection('NFT'); + + itSub('Check valid names for NFT collection properties keys', async ({helper}) => { + await testCheckValidNames(await helper.nft.mintCollection(alice)); }); - it('Sets properties for a ReFungible collection', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testSetsPropertiesForCollection('ReFungible'); + itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => { + await testCheckValidNames(await helper.rft.mintCollection(alice)); }); - async function testCheckValidNames(mode: string) { - await usingApi(async api => { - const events = await executeTransaction(api, bob, api.tx.unique.createCollectionEx({mode: mode})); - const {collectionId} = getCreateCollectionResult(events); - - // alpha symbols - await expect(executeTransaction( - api, - bob, - api.tx.unique.setCollectionProperties(collectionId, [{key: 'alpha'}]), - )).to.not.be.rejected; - - // numeric symbols - await expect(executeTransaction( - api, - bob, - api.tx.unique.setCollectionProperties(collectionId, [{key: '123'}]), - )).to.not.be.rejected; - - // underscore symbol - await expect(executeTransaction( - api, - bob, - api.tx.unique.setCollectionProperties(collectionId, [{key: 'black_hole'}]), - )).to.not.be.rejected; - - // dash symbol - await expect(executeTransaction( - api, - bob, - api.tx.unique.setCollectionProperties(collectionId, [{key: 'semi-automatic'}]), - )).to.not.be.rejected; - - // underscore symbol - await expect(executeTransaction( - api, - bob, - api.tx.unique.setCollectionProperties(collectionId, [{key: 'build.rs'}]), - )).to.not.be.rejected; - - const propertyKeys = ['alpha', '123', 'black_hole', 'semi-automatic', 'build.rs']; - const properties = (await api.rpc.unique.collectionProperties(collectionId, propertyKeys)).toHuman(); - expect(properties).to.be.deep.equal([ - {key: 'alpha', value: ''}, - {key: '123', value: ''}, - {key: 'black_hole', value: ''}, - {key: 'semi-automatic', value: ''}, - {key: 'build.rs', value: ''}, - ]); - }); + async function testChangesProperties(collection: UniqueBaseCollection) { + 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'}, + ]); } - it('Check valid names for NFT collection properties keys', async () => { - await testCheckValidNames('NFT'); + + itSub('Changes properties of a NFT collection', async ({helper}) => { + await testChangesProperties(await helper.nft.mintCollection(alice)); }); - it('Check valid names for ReFungible collection properties keys', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testCheckValidNames('ReFungible'); + itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => { + await testChangesProperties(await helper.rft.mintCollection(alice)); }); - async function testChangesProperties(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole'}]), - )).to.not.be.rejected; - - // Mutate the properties - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'bonded'}, {key: 'black_hole', value: 'LIGO'}]), - )).to.not.be.rejected; - - const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman(); - expect(properties).to.be.deep.equal([ - {key: 'electron', value: 'bonded'}, - {key: 'black_hole', value: 'LIGO'}, - ]); - }); - } - it('Changes properties of a NFT collection', async () => { - await testChangesProperties({type: 'NFT'}); - }); - it('Changes properties of a ReFungible collection', async function() { - await requirePallets(this, [Pallets.ReFungible]); + async function testDeleteProperties(collection: UniqueBaseCollection) { + await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled; - await testChangesProperties({type: 'ReFungible'}); - }); + await expect(collection.deleteProperties(alice, ['electron'])).to.be.fulfilled; - async function testDeleteProperties(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), - )).to.not.be.rejected; - - await expect(executeTransaction( - api, - alice, - api.tx.unique.deleteCollectionProperties(collection, ['electron']), - )).to.not.be.rejected; - - const properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black_hole'])).toHuman(); - expect(properties).to.be.deep.equal([ - {key: 'black_hole', value: 'LIGO'}, - ]); - }); + const properties = await collection.getProperties(['black_hole', 'electron']); + expect(properties).to.be.deep.equal([ + {key: 'black_hole', value: 'LIGO'}, + ]); } - it('Deletes properties of a NFT collection', async () => { - await testDeleteProperties({type: 'NFT'}); + + itSub('Deletes properties of a NFT collection', async ({helper}) => { + await testDeleteProperties(await helper.nft.mintCollection(alice)); }); - it('Deletes properties of a ReFungible collection', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testDeleteProperties({type: 'ReFungible'}); + itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => { + await testDeleteProperties(await helper.rft.mintCollection(alice)); }); }); describe('Negative Integration Test: Collection Properties', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor); }); }); - - async function testFailsSetPropertiesIfNotOwnerOrAdmin(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - await expect(executeTransaction( - api, - bob, - api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]), - )).to.be.rejectedWith(/common\.NoPermission/); - - const properties = (await api.query.common.collectionProperties(collection)).toJSON(); - expect(properties.map).to.be.empty; - expect(properties.consumedSpace).to.equal(0); - }); + async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueBaseCollection) { + 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; } - it('Fails to set properties in a NFT collection if not its onwer/administrator', async () => { - await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'NFT'}); + + itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => { + await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice)); }); - it('Fails to set properties in a ReFungible collection if not its onwer/administrator', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testFailsSetPropertiesIfNotOwnerOrAdmin({type: 'ReFungible'}); + itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => { + await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice)); }); - async function testFailsSetPropertiesThatExeedLimits(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - const spaceLimit = (await api.query.common.collectionProperties(collection)).toJSON().spaceLimit as number; + async function testFailsSetPropertiesThatExeedLimits(collection: UniqueBaseCollection) { + 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 = () => {}; - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [{key: 'electron', value: 'low high '.repeat(Math.ceil(spaceLimit! / 9))}]), - )).to.be.rejected; - } - - let properties = (await api.rpc.unique.collectionProperties(collection, ['electron'])).toJSON(); - expect(properties).to.be.empty; - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [ - {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/); - - properties = (await api.rpc.unique.collectionProperties(collection, ['electron', 'black hole'])).toJSON(); - expect(properties).to.be.empty; - }); + // Mute the general tx parsing error, too many bytes to process + { + console.error = () => {}; + await expect(collection.setProperties(alice, [ + {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))}, + ])).to.be.rejectedWith(/common\.NoSpaceForProperty/); + + expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty; } - it('Fails to set properties that exceed the limits (NFT)', async () => { - await testFailsSetPropertiesThatExeedLimits({type: 'NFT'}); + + itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) => { + await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice)); }); - it('Fails to set properties that exceed the limits (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testFailsSetPropertiesThatExeedLimits({type: 'ReFungible'}); + itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice)); }); - - async function testFailsSetMorePropertiesThanAllowed(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - const propertiesToBeSet = []; - for (let i = 0; i < 65; i++) { - propertiesToBeSet.push({ - key: 'electron_' + i, - value: Math.random() > 0.5 ? 'high' : 'low', - }); - } - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, propertiesToBeSet), - )).to.be.rejectedWith(/common\.PropertyLimitReached/); - - const properties = (await api.query.common.collectionProperties(collection)).toJSON(); - expect(properties.map).to.be.empty; - expect(properties.consumedSpace).to.equal(0); - }); + async function testFailsSetMorePropertiesThanAllowed(collection: UniqueBaseCollection) { + const propertiesToBeSet = []; + for (let i = 0; i < 65; i++) { + propertiesToBeSet.push({ + key: 'electron_' + i, + 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; } - it('Fails to set more properties than it is allowed (NFT)', async () => { - await testFailsSetMorePropertiesThanAllowed({type: 'NFT'}); + + itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => { + await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice)); }); - it('Fails to set more properties than it is allowed (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testFailsSetMorePropertiesThanAllowed({type: 'ReFungible'}); + itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice)); }); - async function testFailsSetPropertiesWithInvalidNames(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - const invalidProperties = [ - [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}], - [{key: 'Mr/Sandman', value: 'Bring me a gene'}], - [{key: 'déjà vu', value: 'hmm...'}], - ]; - - for (let i = 0; i < invalidProperties.length; i++) { - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, invalidProperties[i]), - ), `on rejecting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); - } - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [{key: '', value: 'nothing must not exist'}]), - ), 'on rejecting an unnamed property').to.be.rejectedWith(/common\.EmptyPropertyKey/); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [ - {key: 'CRISPR-Cas9', value: 'rewriting nature!'}, - ]), - ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected; - - const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat('CRISPR-Cas9').concat(''); - - const properties = (await api.rpc.unique.collectionProperties(collection, keys)).toHuman(); - expect(properties).to.be.deep.equal([ - {key: 'CRISPR-Cas9', value: 'rewriting nature!'}, - ]); - - for (let i = 0; i < invalidProperties.length; i++) { - await expect(executeTransaction( - api, - alice, - api.tx.unique.deleteCollectionProperties(collection, invalidProperties[i].map(propertySet => propertySet.key)), - ), `on trying to delete the non-existent badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); - } - }); + async function testFailsSetPropertiesWithInvalidNames(collection: UniqueBaseCollection) { + const invalidProperties = [ + [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}], + [{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]), + `on rejecting the new badly-named property #${i}`, + ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); + } + + await expect( + 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!'}]), + '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)), + `on trying to delete the non-existent badly-named property #${i}`, + ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); + } } - it('Fails to set properties with invalid names (NFT)', async () => { - await testFailsSetPropertiesWithInvalidNames({type: 'NFT'}); + + itSub('Fails to set properties with invalid names (NFT)', async ({helper}) => { + await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice)); }); - it('Fails to set properties with invalid names (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testFailsSetPropertiesWithInvalidNames({type: 'ReFungible'}); + itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice)); }); }); // ---------- ACCESS RIGHTS describe('Integration Test: Access Rights to Token Properties', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor); }); }); - it('Reads access rights to properties of a collection', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess(); - const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON(); - expect(propertyRights).to.be.empty; - }); + itSub('Reads access rights to properties of a collection', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON(); + expect(propertyRights).to.be.empty; }); - async function testSetsAccessRightsToProperties(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true}}]), - )).to.not.be.rejected; - - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'mindgame', permission: {collectionAdmin: true, tokenOwner: false}}]), - )).to.not.be.rejected; - - const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery', 'mindgame'])).toHuman(); - expect(propertyRights).to.be.deep.equal([ - {key: 'skullduggery', permission: {'mutable': true, 'collectionAdmin': false, 'tokenOwner': false}}, - {key: 'mindgame', permission: {'mutable': false, 'collectionAdmin': true, 'tokenOwner': false}}, - ]); - }); + 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}}, + ]); } - it('Sets access rights to properties of a collection (NFT)', async () => { - await testSetsAccessRightsToProperties({type: 'NFT'}); + + itSub('Sets access rights to properties of a collection (NFT)', async ({helper}) => { + await testSetsAccessRightsToProperties(await helper.nft.mintCollection(alice)); }); - it('Sets access rights to properties of a collection (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testSetsAccessRightsToProperties({type: 'ReFungible'}); + 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(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, collectionAdmin: true}}]), - )).to.not.be.rejected; - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), - )).to.not.be.rejected; - - const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman(); - expect(propertyRights).to.be.deep.equal([ - {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}}, - ]); - }); + 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}}, + ]); } - it('Changes access rights to properties of a NFT collection', async () => { - await testChangesAccessRightsToProperty({type: 'NFT'}); + + itSub('Changes access rights to properties of a NFT collection', async ({helper}) => { + await testChangesAccessRightsToProperty(await helper.nft.mintCollection(alice)); }); - it('Changes access rights to properties of a ReFungible collection', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testChangesAccessRightsToProperty({type: 'ReFungible'}); + 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 usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor); }); }); - async function testPreventsFromSettingAccessRightsNotAdminOrOwner(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - await expect(executeTransaction( - api, - bob, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: true, tokenOwner: true}}]), - )).to.be.rejectedWith(/common\.NoPermission/); - - const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toJSON(); - expect(propertyRights).to.be.empty; - }); + 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; } - it('Prevents from setting access rights to properties of a NFT collection if not an onwer/admin', async () => { - await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'NFT'}); + + 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)); }); - it('Prevents from setting access rights to properties of a ReFungible collection if not an onwer/admin', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testPreventsFromSettingAccessRightsNotAdminOrOwner({type: 'ReFungible'}); + 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(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - const constitution = []; - for (let i = 0; i < 65; i++) { - constitution.push({ - key: 'property_' + i, - permission: Math.random() > 0.5 ? {mutable: true, collectionAdmin: true, tokenOwner: true} : {}, - }); - } - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, constitution), - )).to.be.rejectedWith(/common\.PropertyLimitReached/); - - const propertyRights = (await api.query.common.collectionPropertyPermissions(collection)).toJSON(); - expect(propertyRights).to.be.empty; - }); + async function testPreventFromAddingTooManyPossibleProperties(collection: UniqueNFTCollection | UniqueRFTCollection) { + const constitution = []; + for (let i = 0; i < 65; i++) { + constitution.push({ + key: 'property_' + i, + 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; } - it('Prevents from adding too many possible properties (NFT)', async () => { - await testPreventFromAddingTooManyPossibleProperties({type: 'NFT'}); + + itSub('Prevents from adding too many possible properties (NFT)', async ({helper}) => { + await testPreventFromAddingTooManyPossibleProperties(await helper.nft.mintCollection(alice)); }); - it('Prevents from adding too many possible properties (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testPreventFromAddingTooManyPossibleProperties({type: 'ReFungible'}); + itSub.ifWithPallets('Prevents from adding too many possible properties (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + await testPreventFromAddingTooManyPossibleProperties(await helper.rft.mintCollection(alice)); }); - async function testPreventAccessRightsModifiedIfConstant(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {mutable: false, tokenOwner: true}}]), - )).to.not.be.rejected; - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'skullduggery', permission: {}}]), - )).to.be.rejectedWith(/common\.NoPermission/); - - const propertyRights = (await api.rpc.unique.propertyPermissions(collection, ['skullduggery'])).toHuman(); - expect(propertyRights).to.deep.equal([ - {key: 'skullduggery', permission: {'mutable': false, 'collectionAdmin': false, 'tokenOwner': true}}, - ]); - }); + 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}}, + ]); } - it('Prevents access rights to be modified if constant (NFT)', async () => { - await testPreventAccessRightsModifiedIfConstant({type: 'NFT'}); + + itSub('Prevents access rights to be modified if constant (NFT)', async ({helper}) => { + await testPreventAccessRightsModifiedIfConstant(await helper.nft.mintCollection(alice)); }); - it('Prevents access rights to be modified if constant (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testPreventAccessRightsModifiedIfConstant({type: 'ReFungible'}); + 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(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - - 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(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, invalidProperties[i]), - ), `on setting the new badly-named property #${i}`).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); - } - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{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(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [ - {key: correctKey, permission: {collectionAdmin: true}}, - ]), - ), 'on setting the correctly-but-still-badly-named property').to.not.be.rejected; - - const keys = invalidProperties.flatMap(propertySet => propertySet.map(property => property.key)).concat(correctKey).concat(''); - - const propertyRights = (await api.rpc.unique.propertyPermissions(collection, keys)).toHuman(); - expect(propertyRights).to.be.deep.equal([ - {key: correctKey, permission: {mutable: false, collectionAdmin: true, tokenOwner: false}}, - ]); - }); + 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]), + `on setting the new badly-named property #${i}`, + ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/); + } + + await expect( + 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}}, + ]); } - it('Prevents adding properties with invalid names (NFT)', async () => { - await testPreventsAddingPropertiesWithInvalidNames({type: 'NFT'}); + + itSub('Prevents adding properties with invalid names (NFT)', async ({helper}) => { + await testPreventsAddingPropertiesWithInvalidNames(await helper.nft.mintCollection(alice)); }); - it('Prevents adding properties with invalid names (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testPreventsAddingPropertiesWithInvalidNames({type: 'ReFungible'}); + itSub.ifWithPallets('Prevents adding properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + await testPreventsAddingPropertiesWithInvalidNames(await helper.rft.mintCollection(alice)); }); }); // ---------- TOKEN PROPERTIES describe('Integration Test: Token Properties', () => { + let alice: IKeyringPair; // collection owner + let bob: IKeyringPair; // collection admin + let charlie: IKeyringPair; // token owner + let permissions: {permission: any, signers: IKeyringPair[]}[]; before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); // collection owner - bob = privateKeyWrapper('//Bob'); // collection admin - charlie = privateKeyWrapper('//Charlie'); // token owner - - permissions = [ - {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]}, - {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]}, - {permission: {mutable: true, tokenOwner: true}, signers: [charlie]}, - {permission: {mutable: false, tokenOwner: true}, signers: [charlie]}, - {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]}, - {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]}, - ]; + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor); }); + + // todo:playgrounds probably separate these tests later + permissions = [ + {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob]}, + {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob]}, + {permission: {mutable: true, tokenOwner: true}, signers: [charlie]}, + {permission: {mutable: false, tokenOwner: true}, signers: [charlie]}, + {permission: {mutable: true, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]}, + {permission: {mutable: false, collectionAdmin: true, tokenOwner: true}, signers: [alice, bob, charlie]}, + ]; }); - async function testReadsYetEmptyProperties(mode: CollectionMode) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - const token = await createItemExpectSuccess(alice, collection, mode.type); - - const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON(); - expect(properties.map).to.be.empty; - expect(properties.consumedSpace).to.be.equal(0); - - const tokenData = (await api.rpc.unique.tokenData(collection, token, ['anything'])).toJSON().properties; - expect(tokenData).to.be.empty; - }); + async function testReadsYetEmptyProperties(token: UniqueNFToken | UniqueRFToken) { + const properties = await token.getProperties(); + expect(properties).to.be.empty; + + const tokenData = await token.getData(); + expect(tokenData!.properties).to.be.empty; } - it('Reads yet empty properties of a token (NFT)', async () => { - await testReadsYetEmptyProperties({type: 'NFT'}); + + itSub('Reads yet empty properties of a token (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testReadsYetEmptyProperties(token); }); - it('Reads yet empty properties of a token (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testReadsYetEmptyProperties({type: 'ReFungible'}); + itSub.ifWithPallets('Reads yet empty properties of a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testReadsYetEmptyProperties(token); }); - async function testAssignPropertiesAccordingToPermissions(mode: CollectionMode, pieces: number) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - const token = await createItemExpectSuccess(alice, collection, mode.type); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type); + async function testAssignPropertiesAccordingToPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) { + await token.collection.addAdmin(alice, {Substrate: bob.address}); + await token.transfer(alice, {Substrate: charlie.address}, pieces); - const propertyKeys: string[] = []; - let i = 0; - for (const permission of permissions) { - for (const signer of permission.signers) { - const key = i + '_' + signer.address; - propertyKeys.push(key); + const propertyKeys: string[] = []; + let i = 0; + for (const permission of permissions) { + i++; + let j = 0; + for (const signer of permission.signers) { + j++; + const key = i + '_' + signer.address; + propertyKeys.push(key); - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), - ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected; - - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), - ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected; - } + await expect( + token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), + `on setting permission #${i} by alice`, + ).to.be.fulfilled; - i++; + await expect( + token.setProperties(signer, [{key: key, value: 'Serotonin increase'}]), + `on adding property #${i} by signer #${j}`, + ).to.be.fulfilled; } + } - const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[]; - const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[]; - for (let i = 0; i < properties.length; i++) { - expect(properties[i].value).to.be.equal('Serotonin increase'); - expect(tokensData[i].value).to.be.equal('Serotonin increase'); - } - }); + const properties = await token.getProperties(propertyKeys); + const tokenData = await token.getData(); + for (let i = 0; i < properties.length; i++) { + expect(properties[i].value).to.be.equal('Serotonin increase'); + expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase'); + } } - it('Assigns properties to a token according to permissions (NFT)', async () => { - await testAssignPropertiesAccordingToPermissions({type: 'NFT'}, 1); + + itSub('Assigns properties to a token according to permissions (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testAssignPropertiesAccordingToPermissions(token, 1n); }); - it('Assigns properties to a token according to permissions (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testAssignPropertiesAccordingToPermissions({type: 'ReFungible'}, 100); + itSub.ifWithPallets('Assigns properties to a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 100n); + await testAssignPropertiesAccordingToPermissions(token, 100n); }); - async function testChangesPropertiesAccordingPermission(mode: CollectionMode, pieces: number) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - const token = await createItemExpectSuccess(alice, collection, mode.type); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type); + async function testChangesPropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) { + await token.collection.addAdmin(alice, {Substrate: bob.address}); + await token.transfer(alice, {Substrate: charlie.address}, pieces); + + const propertyKeys: string[] = []; + let i = 0; + for (const permission of permissions) { + i++; + if (!permission.permission.mutable) continue; + + let j = 0; + for (const signer of permission.signers) { + j++; + const key = i + '_' + signer.address; + propertyKeys.push(key); + + await expect( + token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), + `on setting permission #${i} by alice`, + ).to.be.fulfilled; + + await expect( + token.setProperties(signer, [{key, value: 'Serotonin increase'}]), + `on adding property #${i} by signer #${j}`, + ).to.be.fulfilled; - const propertyKeys: string[] = []; - let i = 0; - for (const permission of permissions) { - if (!permission.permission.mutable) continue; - - for (const signer of permission.signers) { - const key = i + '_' + signer.address; - propertyKeys.push(key); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), - ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected; - - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), - ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected; - - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin stable'}]), - ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected; - } - - i++; + await expect( + token.setProperties(signer, [{key, value: 'Serotonin stable'}]), + `on changing property #${i} by signer #${j}`, + ).to.be.fulfilled; } - - const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toHuman() as any[]; - const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toHuman().properties as any[]; - for (let i = 0; i < properties.length; i++) { - expect(properties[i].value).to.be.equal('Serotonin stable'); - expect(tokensData[i].value).to.be.equal('Serotonin stable'); - } - }); + } + + const properties = await token.getProperties(propertyKeys); + const tokenData = await token.getData(); + for (let i = 0; i < properties.length; i++) { + expect(properties[i].value).to.be.equal('Serotonin stable'); + expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable'); + } } - it('Changes properties of a token according to permissions (NFT)', async () => { - await testChangesPropertiesAccordingPermission({type: 'NFT'}, 1); + + itSub('Changes properties of a token according to permissions (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testChangesPropertiesAccordingPermission(token, 1n); }); - it('Changes properties of a token according to permissions (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testChangesPropertiesAccordingPermission({type: 'ReFungible'}, 100); + itSub.ifWithPallets('Changes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 100n); + await testChangesPropertiesAccordingPermission(token, 100n); }); - async function testDeletePropertiesAccordingPermission(mode: CollectionMode, pieces: number) { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: mode}); - const token = await createItemExpectSuccess(alice, collection, mode.type); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type); + async function testDeletePropertiesAccordingPermission(token: UniqueNFToken | UniqueRFToken, pieces: bigint) { + await token.collection.addAdmin(alice, {Substrate: bob.address}); + await token.transfer(alice, {Substrate: charlie.address}, pieces); + + const propertyKeys: string[] = []; + let i = 0; + + for (const permission of permissions) { + i++; + if (!permission.permission.mutable) continue; + + let j = 0; + for (const signer of permission.signers) { + j++; + const key = i + '_' + signer.address; + propertyKeys.push(key); + + await expect( + token.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), + `on setting permission #${i} by alice`, + ).to.be.fulfilled; + + await expect( + token.setProperties(signer, [{key, value: 'Serotonin increase'}]), + `on adding property #${i} by signer #${j}`, + ).to.be.fulfilled; - const propertyKeys: string[] = []; - let i = 0; - - for (const permission of permissions) { - if (!permission.permission.mutable) continue; - - for (const signer of permission.signers) { - const key = i + '_' + signer.address; - propertyKeys.push(key); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), - ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected; - - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, token, [{key: key, value: 'Serotonin increase'}]), - ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected; - - await expect(executeTransaction( - api, - signer, - api.tx.unique.deleteTokenProperties(collection, token, [key]), - ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected; - } - - i++; + await expect( + token.deleteProperties(signer, [key]), + `on deleting property #${i} by signer #${j}`, + ).to.be.fulfilled; } - - const properties = (await api.rpc.unique.tokenProperties(collection, token, propertyKeys)).toJSON() as any[]; - expect(properties).to.be.empty; - const tokensData = (await api.rpc.unique.tokenData(collection, token, propertyKeys)).toJSON().properties as any[]; - expect(tokensData).to.be.empty; - expect((await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace).to.be.equal(0); - }); + } + + expect(await token.getProperties(propertyKeys)).to.be.empty; + expect((await token.getData())!.properties).to.be.empty; } - it('Deletes properties of a token according to permissions (NFT)', async () => { - await testDeletePropertiesAccordingPermission({type: 'NFT'}, 1); + + itSub('Deletes properties of a token according to permissions (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testDeletePropertiesAccordingPermission(token, 1n); }); - it('Deletes properties of a token according to permissions (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testDeletePropertiesAccordingPermission({type: 'ReFungible'}, 100); + itSub.ifWithPallets('Deletes properties of a token according to permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 100n); + await testDeletePropertiesAccordingPermission(token, 100n); }); - it('Assigns properties to a nested token according to permissions', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const token = await createItemExpectSuccess(alice, collection, 'NFT'); - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)}); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - await transferExpectSuccess(collection, token, alice, charlie); + 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); + const targetToken = await collectionA.mintToken(alice); + const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount()); - const propertyKeys: string[] = []; - let i = 0; - for (const permission of permissions) { - for (const signer of permission.signers) { - const key = i + '_' + signer.address; - propertyKeys.push(key); + await collectionB.addAdmin(alice, {Substrate: bob.address}); + await targetToken.transfer(alice, {Substrate: charlie.address}); - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), - ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected; + const propertyKeys: string[] = []; + let i = 0; + for (const permission of permissions) { + i++; + let j = 0; + for (const signer of permission.signers) { + j++; + const key = i + '_' + signer.address; + propertyKeys.push(key); + + await expect( + nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), + `on setting permission #${i} by alice`, + ).to.be.fulfilled; - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), - ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected; - } - - i++; + await expect( + nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), + `on adding property #${i} by signer #${j}`, + ).to.be.fulfilled; } + } - const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[]; - const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[]; - for (let i = 0; i < properties.length; i++) { - expect(properties[i].value).to.be.equal('Serotonin increase'); - expect(tokensData[i].value).to.be.equal('Serotonin increase'); - } - }); + const properties = await nestedToken.getProperties(propertyKeys); + const tokenData = await nestedToken.getData(); + for (let i = 0; i < properties.length; i++) { + expect(properties[i].value).to.be.equal('Serotonin increase'); + expect(tokenData!.properties[i].value).to.be.equal('Serotonin increase'); + } + expect(await targetToken.getProperties()).to.be.empty; }); - it('Changes properties of a nested token according to permissions', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const token = await createItemExpectSuccess(alice, collection, 'NFT'); - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)}); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - await transferExpectSuccess(collection, token, alice, charlie); + 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); + const targetToken = await collectionA.mintToken(alice); + const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount()); - const propertyKeys: string[] = []; - let i = 0; - for (const permission of permissions) { - if (!permission.permission.mutable) continue; - - for (const signer of permission.signers) { - const key = i + '_' + signer.address; - propertyKeys.push(key); + await collectionB.addAdmin(alice, {Substrate: bob.address}); + await targetToken.transfer(alice, {Substrate: charlie.address}); - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), - ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected; + const propertyKeys: string[] = []; + let i = 0; + for (const permission of permissions) { + i++; + if (!permission.permission.mutable) continue; + + let j = 0; + for (const signer of permission.signers) { + j++; + const key = i + '_' + signer.address; + propertyKeys.push(key); - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), - ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected; + await expect( + nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), + `on setting permission #${i} by alice`, + ).to.be.fulfilled; - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin stable'}]), - ), `on changing property ${i} by ${signer.address}`).to.not.be.rejected; - } + await expect( + nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), + `on adding property #${i} by signer #${j}`, + ).to.be.fulfilled; - i++; + await expect( + nestedToken.setProperties(signer, [{key, value: 'Serotonin stable'}]), + `on changing property #${i} by signer #${j}`, + ).to.be.fulfilled; } + } - const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toHuman() as any[]; - const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toHuman().properties as any[]; - for (let i = 0; i < properties.length; i++) { - expect(properties[i].value).to.be.equal('Serotonin stable'); - expect(tokensData[i].value).to.be.equal('Serotonin stable'); - } - }); + const properties = await nestedToken.getProperties(propertyKeys); + const tokenData = await nestedToken.getData(); + for (let i = 0; i < properties.length; i++) { + expect(properties[i].value).to.be.equal('Serotonin stable'); + expect(tokenData!.properties[i].value).to.be.equal('Serotonin stable'); + } + expect(await targetToken.getProperties()).to.be.empty; }); - it('Deletes properties of a nested token according to permissions', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const token = await createItemExpectSuccess(alice, collection, 'NFT'); - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, token)}); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - await transferExpectSuccess(collection, token, alice, charlie); + 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); + const targetToken = await collectionA.mintToken(alice); + const nestedToken = await collectionB.mintToken(alice, targetToken.nestingAccount()); - const propertyKeys: string[] = []; - let i = 0; + await collectionB.addAdmin(alice, {Substrate: bob.address}); + await targetToken.transfer(alice, {Substrate: charlie.address}); - for (const permission of permissions) { - if (!permission.permission.mutable) continue; - - for (const signer of permission.signers) { - const key = i + '_' + signer.address; - propertyKeys.push(key); + const propertyKeys: string[] = []; + let i = 0; + for (const permission of permissions) { + i++; + if (!permission.permission.mutable) continue; + + let j = 0; + for (const signer of permission.signers) { + j++; + const key = i + '_' + signer.address; + propertyKeys.push(key); - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: key, permission: permission.permission}]), - ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected; + await expect( + nestedToken.collection.setTokenPropertyPermissions(alice, [{key: key, permission: permission.permission}]), + `on setting permission #${i} by alice`, + ).to.be.fulfilled; - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, nestedToken, [{key: key, value: 'Serotonin increase'}]), - ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected; + await expect( + nestedToken.setProperties(signer, [{key, value: 'Serotonin increase'}]), + `on adding property #${i} by signer #${j}`, + ).to.be.fulfilled; - await expect(executeTransaction( - api, - signer, - api.tx.unique.deleteTokenProperties(collection, nestedToken, [key]), - ), `on deleting property ${i} by ${signer.address}`).to.not.be.rejected; - } - - i++; + await expect( + nestedToken.deleteProperties(signer, [key]), + `on deleting property #${i} by signer #${j}`, + ).to.be.fulfilled; } + } - const properties = (await api.rpc.unique.tokenProperties(collection, nestedToken, propertyKeys)).toJSON() as any[]; - expect(properties).to.be.empty; - const tokensData = (await api.rpc.unique.tokenData(collection, nestedToken, propertyKeys)).toJSON().properties as any[]; - expect(tokensData).to.be.empty; - expect((await api.query.nonfungible.tokenProperties(collection, nestedToken)).toJSON().consumedSpace).to.be.equal(0); - }); + expect(await nestedToken.getProperties(propertyKeys)).to.be.empty; + expect((await nestedToken.getData())!.properties).to.be.empty; + expect(await targetToken.getProperties()).to.be.empty; }); }); describe('Negative Integration Test: Token Properties', () => { - let collection: number; - let token: number; - let originalSpace: number; + let alice: IKeyringPair; // collection owner + let bob: IKeyringPair; // collection admin + let charlie: IKeyringPair; // token owner + let constitution: {permission: any, signers: IKeyringPair[], sinner: IKeyringPair}[]; before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - charlie = privateKeyWrapper('//Charlie'); - const dave = privateKeyWrapper('//Dave'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + let dave: IKeyringPair; + [alice, bob, charlie, dave] = await helper.arrange.createAccounts([100n, 100n, 100n, 100n], donor); + // todo:playgrounds probably separate these tests later constitution = [ {permission: {mutable: true, collectionAdmin: true}, signers: [alice, bob], sinner: charlie}, {permission: {mutable: false, collectionAdmin: true}, signers: [alice, bob], sinner: charlie}, @@ -1010,278 +793,255 @@ }); }); - async function prepare(mode: CollectionMode, pieces: number) { - collection = await createCollectionExpectSuccess({mode: mode}); - token = await createItemExpectSuccess(alice, collection, mode.type); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); - await transferExpectSuccess(collection, token, alice, charlie, pieces, mode.type); - - await usingApi(async api => { - let i = 0; - for (const passage of constitution) { - const signer = passage.signers[0]; - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: `${i}`, permission: passage.permission}]), - ), `on setting permission ${i} by ${signer.address}`).to.not.be.rejected; - - await expect(executeTransaction( - api, - signer, - api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin increase'}]), - ), `on adding property ${i} by ${signer.address}`).to.not.be.rejected; - - i++; - } - - originalSpace = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON().consumedSpace as number; - }); + async function getConsumedSpace(api: any, collectionId: number, tokenId: number, mode: 'NFT' | 'RFT'): Promise { + return (await (mode == 'NFT' ? api.query.nonfungible : api.query.refungible).tokenProperties(collectionId, tokenId)).toJSON().consumedSpace; + } + + async function prepare(token: UniqueNFToken | UniqueRFToken, pieces: bigint): Promise { + await token.collection.addAdmin(alice, {Substrate: bob.address}); + await token.transfer(alice, {Substrate: charlie.address}, pieces); + + let i = 0; + for (const passage of constitution) { + i++; + const signer = passage.signers[0]; + + await expect( + token.collection.setTokenPropertyPermissions(alice, [{key: `${i}`, permission: passage.permission}]), + `on setting permission ${i} by alice`, + ).to.be.fulfilled; + + await expect( + 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.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + return originalSpace; } - async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(mode: CollectionMode, pieces: number) { - await prepare(mode, pieces); - - await usingApi(async api => { - let i = -1; - for (const forbiddance of constitution) { - i++; - if (!forbiddance.permission.mutable) continue; - - await expect(executeTransaction( - api, - forbiddance.sinner, - api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), - ), `on failing to change property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/); - - await expect(executeTransaction( - api, - forbiddance.sinner, - api.tx.unique.deleteTokenProperties(collection, token, [`${i}`]), - ), `on failing to delete property ${i} by ${forbiddance.sinner.address}`).to.be.rejectedWith(/common\.NoPermission/); - } - - const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON(); - expect(properties.consumedSpace).to.be.equal(originalSpace); - }); + async function testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token: UniqueNFToken | UniqueRFToken, pieces: bigint) { + const originalSpace = await prepare(token, pieces); + + let i = 0; + for (const forbiddance of constitution) { + i++; + if (!forbiddance.permission.mutable) continue; + + await expect( + 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}`]), + `on failing to delete property ${i} by the malefactor`, + ).to.be.rejectedWith(/common\.NoPermission/); + } + + const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + expect(consumedSpace).to.be.equal(originalSpace); } - it('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async () => { - await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'NFT'}, 1); + + itSub('Forbids changing/deleting properties of a token if the user is outside of permissions (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 1n); }); - it('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions({type: 'ReFungible'}, 100); + itSub.ifWithPallets('Forbids changing/deleting properties of a token if the user is outside of permissions (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 100n); + await testForbidsChangingDeletingPropertiesUserOutsideOfPermissions(token, 100n); }); - async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(mode: CollectionMode, pieces: number) { - await prepare(mode, pieces); - - await usingApi(async api => { - let i = -1; - for (const permission of constitution) { - i++; - if (permission.permission.mutable) continue; + async function testForbidsChangingDeletingPropertiesIfPropertyImmutable(token: UniqueNFToken | UniqueRFToken, pieces: bigint) { + const originalSpace = await prepare(token, pieces); + + let i = 0; + for (const permission of constitution) { + i++; + if (permission.permission.mutable) continue; + + await expect( + 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()]), + `on failing to delete property ${i} by signer #0`, + ).to.be.rejectedWith(/common\.NoPermission/); + } - await expect(executeTransaction( - api, - permission.signers[0], - api.tx.unique.setTokenProperties(collection, token, [{key: `${i}`, value: 'Serotonin down'}]), - ), `on failing to change property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/); - - await expect(executeTransaction( - api, - permission.signers[0], - api.tx.unique.deleteTokenProperties(collection, token, [i.toString()]), - ), `on failing to delete property ${i} by ${permission.signers[0].address}`).to.be.rejectedWith(/common\.NoPermission/); - } - - const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON(); - expect(properties.consumedSpace).to.be.equal(originalSpace); - }); + const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + expect(consumedSpace).to.be.equal(originalSpace); } - it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async () => { - await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'NFT'}, 1); + + itSub('Forbids changing/deleting properties of a token if the property is permanent (immutable) (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 1n); }); - it('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testForbidsChangingDeletingPropertiesIfPropertyImmutable({type: 'ReFungible'}, 100); + itSub.ifWithPallets('Forbids changing/deleting properties of a token if the property is permanent (immutable) (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 100n); + await testForbidsChangingDeletingPropertiesIfPropertyImmutable(token, 100n); }); - async function testForbidsAddingPropertiesIfPropertyNotDeclared(mode: CollectionMode, pieces: number) { - await prepare(mode, pieces); + async function testForbidsAddingPropertiesIfPropertyNotDeclared(token: UniqueNFToken | UniqueRFToken, pieces: bigint) { + const originalSpace = await prepare(token, pieces); + + await expect( + 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: {}}]), + 'on setting a new non-permitted property', + ).to.be.fulfilled; + + await expect( + 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/); - await usingApi(async api => { - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenProperties(collection, token, [{key: 'non-existent', value: 'I exist!'}]), - ), 'on failing to add a previously non-existent property').to.be.rejectedWith(/common\.NoPermission/); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'now-existent', permission: {}}]), - ), 'on setting a new non-permitted property').to.not.be.rejected; - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenProperties(collection, token, [{key: 'now-existent', value: 'I exist!'}]), - ), 'on failing to add a property forbidden by the \'None\' permission').to.be.rejectedWith(/common\.NoPermission/); - - expect((await api.rpc.unique.tokenProperties(collection, token, ['non-existent', 'now-existent'])).toJSON()).to.be.empty; - const properties = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON(); - expect(properties.consumedSpace).to.be.equal(originalSpace); - }); + expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty; + + const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + expect(consumedSpace).to.be.equal(originalSpace); } - it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async () => { - await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'NFT'}, 1); + + itSub('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 1n); }); - it('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testForbidsAddingPropertiesIfPropertyNotDeclared({type: 'ReFungible'}, 100); + itSub.ifWithPallets('Forbids adding properties to a token if the property is not declared / forbidden with the \'None\' permission (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 100n); + await testForbidsAddingPropertiesIfPropertyNotDeclared(token, 100n); }); - async function testForbidsAddingTooManyProperties(mode: CollectionMode, pieces: number) { - await prepare(mode, pieces); + async function testForbidsAddingTooManyProperties(token: UniqueNFToken | UniqueRFToken, pieces: bigint) { + const originalSpace = await prepare(token, pieces); + + await expect( + token.collection.setTokenPropertyPermissions(alice, [ + {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; + + // Mute the general tx parsing error + { + console.error = () => {}; + await expect(token.setProperties(alice, [{key: 'a_holy_book', value: 'word '.repeat(6554)}])) + .to.be.rejected; + } - await usingApi(async api => { - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [ - {key: 'a_holy_book', permission: {collectionAdmin: true, tokenOwner: true}}, - {key: 'young_years', permission: {collectionAdmin: true, tokenOwner: true}}, - ]), - ), 'on setting a new non-permitted property').to.not.be.rejected; + await expect(token.setProperties(alice, [ + {key: 'a_holy_book', value: 'word '.repeat(3277)}, + {key: 'young_years', value: 'neverending'.repeat(1490)}, + ])).to.be.rejectedWith(/common\.NoSpaceForProperty/); - // Mute the general tx parsing error - { - console.error = () => {}; - await expect(executeTransaction( - api, - alice, - api.tx.unique.setCollectionProperties(collection, [{key: 'a_holy_book', value: 'word '.repeat(6554)}]), - )).to.be.rejected; - } - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenProperties(collection, token, [ - {key: 'a_holy_book', value: 'word '.repeat(3277)}, - {key: 'young_years', value: 'neverending'.repeat(1490)}, - ]), - )).to.be.rejectedWith(/common\.NoSpaceForProperty/); - - expect((await api.rpc.unique.tokenProperties(collection, token, ['a_holy_book', 'young_years'])).toJSON()).to.be.empty; - const propertiesMap = (await api.query.nonfungible.tokenProperties(collection, token)).toJSON(); - expect(propertiesMap.consumedSpace).to.be.equal(originalSpace); - }); + expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty; + const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + expect(consumedSpace).to.be.equal(originalSpace); } - it('Forbids adding too many properties to a token (NFT)', async () => { - await testForbidsAddingTooManyProperties({type: 'NFT'}, 1); + + itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice); + const token = await collection.mintToken(alice); + await testForbidsAddingTooManyProperties(token, 1n); }); - it('Forbids adding too many properties to a token (ReFungible)', async function() { - await requirePallets(this, [Pallets.ReFungible]); - await testForbidsAddingTooManyProperties({type: 'ReFungible'}, 100); + itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.rft.mintCollection(alice); + const token = await collection.mintToken(alice, 100n); + await testForbidsAddingTooManyProperties(token, 100n); }); }); describe('ReFungible token properties permissions tests', () => { - let collection: number; - let token: number; + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; before(async function() { - await requirePallets(this, [Pallets.ReFungible]); + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - charlie = privateKeyWrapper('//Charlie'); + const donor = privateKey('//Alice'); + [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor); }); }); - beforeEach(async () => { - await usingApi(async api => { - collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - token = await createItemExpectSuccess(alice, collection, 'ReFungible'); - await addCollectionAdminExpectSuccess(alice, collection, bob.address); + 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; + } + + itSub('Forbids adding token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => { + const token = await prepare(helper); + + await token.transfer(alice, {Substrate: charlie.address}, 33n); - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), - )).to.not.be.rejected; - }); + await expect(token.setProperties(alice, [ + {key: 'fractals', value: 'multiverse'}, + ])).to.be.rejectedWith(/common\.NoPermission/); }); - it('Forbids add token property with tokenOwher==true but signer have\'t all pieces', async () => { - await usingApi(async api => { - await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible'); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenProperties(collection, token, [ - {key: 'key', value: 'word'}, - ]), - )).to.be.rejectedWith(/common\.NoPermission/); - }); + itSub('Forbids mutating token property with tokenOwher==true when signer doesn\'t have all pieces', async ({helper}) => { + const token = await prepare(helper); + + await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, tokenOwner: true}}])) + .to.be.fulfilled; + + await expect(token.setProperties(alice, [ + {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'}, + ])).to.be.rejectedWith(/common\.NoPermission/); }); - it('Forbids mutate token property with tokenOwher==true but signer have\'t all pieces', async () => { - await usingApi(async api => { - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenPropertyPermissions(collection, [{key: 'key', permission: {mutable:true, tokenOwner: true}}]), - )).to.not.be.rejected; - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenProperties(collection, token, [ - {key: 'key', value: 'word'}, - ]), - )).to.be.not.rejected; + itSub('Forbids deleting token property with tokenOwner==true when signer doesn\'t have all pieces', async ({helper}) => { + const token = await prepare(helper); + + await expect(token.setProperties(alice, [ + {key: 'fractals', value: 'one headline - why believe it'}, + ])).to.be.fulfilled; + + await token.transfer(alice, {Substrate: charlie.address}, 33n); - await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible'); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenProperties(collection, token, [ - {key: 'key', value: 'bad word'}, - ]), - )).to.be.rejectedWith(/common\.NoPermission/); - }); + await expect(token.deleteProperties(alice, ['fractals'])). + to.be.rejectedWith(/common\.NoPermission/); }); - it('Forbids delete token property with tokenOwher==true but signer have\'t all pieces', async () => { - await usingApi(async api => { - await expect(executeTransaction( - api, - alice, - api.tx.unique.setTokenProperties(collection, token, [ - {key: 'key', value: 'word'}, - ]), - )).to.be.not.rejected; + itSub('Allows token property mutation with collectionOwner==true when admin doesn\'t have all pieces', async ({helper}) => { + const token = await prepare(helper); - await transferExpectSuccess(collection, token, alice, charlie, 33, 'ReFungible'); - - await expect(executeTransaction( - api, - alice, - api.tx.unique.deleteTokenProperties(collection, token, [ - 'key', - ]), - )).to.be.rejectedWith(/common\.NoPermission/); - }); + await token.transfer(alice, {Substrate: charlie.address}, 33n); + + await expect(token.collection.setTokenPropertyPermissions(alice, [{key: 'fractals', permission: {mutable:true, collectionAdmin: true}}])) + .to.be.fulfilled; + + await expect(token.setProperties(alice, [ + {key: 'fractals', value: 'multiverse'}, + ])).to.be.fulfilled; }); }); --- a/tests/src/nesting/rules-smoke.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {expect} from 'chai'; -import {tokenIdToAddress} from '../eth/util/helpers'; -import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import {createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, CrossAccountId, getCreateCollectionResult, requirePallets, Pallets} from '../util/helpers'; -import {IKeyringPair} from '@polkadot/types/types'; - -describe('nesting check', () => { - let alice!: IKeyringPair; - let nestTarget!: CrossAccountId; - before(async() => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - const events = await executeTransaction(api, alice, api.tx.unique.createCollectionEx({ - mode: 'NFT', - permissions: { - nesting: {tokenOwner: true, restricted: []}, - }, - })); - const collection = getCreateCollectionResult(events).collectionId; - const token = await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: bob.address}); - nestTarget = {Ethereum: tokenIdToAddress(collection, token)}; - }); - }); - - it('called for fungible', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'Fungible',decimalPoints:0}}); - await expect(executeTransaction(api, alice, api.tx.unique.createItem(collection, nestTarget, {Fungible: {Value: 1}}))) - .to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); - - await createFungibleItemExpectSuccess(alice, collection, {Value:1n}, {Substrate: alice.address}); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(nestTarget, collection, 0, 1n))) - .to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); - }); - }); - - it('called for nonfungible', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await expect(executeTransaction(api, alice, api.tx.unique.createItem(collection, nestTarget, {NFT: {properties: []}}))) - .to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); - - const token = await createItemExpectSuccess(alice, collection, 'NFT', {Substrate: alice.address}); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(nestTarget, collection, token, 1n))) - .to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); - }); - }); - - it('called for refungible', async function() { - await requirePallets(this, [Pallets.ReFungible]); - - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - await expect(executeTransaction(api, alice, api.tx.unique.createItem(collection, nestTarget, {ReFungible: {}}))) - .to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); - - const token = await createItemExpectSuccess(alice, collection, 'ReFungible', {Substrate: alice.address}); - await expect(executeTransaction(api, alice, api.tx.unique.transfer(nestTarget, collection, token, 1n))) - .to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/); - }); - }); -}); --- a/tests/src/nesting/unnest.test.ts +++ b/tests/src/nesting/unnest.test.ts @@ -1,162 +1,126 @@ -import {expect} from 'chai'; -import {tokenIdToAddress} from '../eth/util/helpers'; -import usingApi, {executeTransaction} from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - createItemExpectSuccess, - getBalance, - getTokenOwner, - normalizeAccountId, - setCollectionPermissionsExpectSuccess, - transferExpectSuccess, - transferFromExpectSuccess, - requirePallets, - Pallets, -} from '../util/helpers'; +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Unique Network is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Unique Network. If not, see . + import {IKeyringPair} from '@polkadot/types/types'; +import {expect, itSub, Pallets, usingPlaygrounds} from '../util/playgrounds'; -let alice: IKeyringPair; -let bob: IKeyringPair; +describe('Integration Test: Unnesting', () => { + let alice: IKeyringPair; -describe('Integration Test: Unnesting', () => { before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice] = await helper.arrange.createAccounts([50n], donor); }); }); - it('NFT: allows the owner to successfully unnest a token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)}; - - // Create a nested token - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress); + 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()); - // Unnest - await expect(executeTransaction( - api, - alice, - api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1), - ), 'while unnesting').to.not.be.rejected; - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address}); + // Unnest + await expect(nestedToken.transferFrom(alice, targetToken.nestingAccount(), {Substrate: alice.address}), 'while unnesting').to.be.fulfilled; + expect(await nestedToken.getOwner()).to.be.deep.equal({Substrate: alice.address}); - // Nest and burn - await transferExpectSuccess(collection, nestedToken, alice, targetAddress); - await expect(executeTransaction( - api, - alice, - api.tx.unique.burnFrom(collection, normalizeAccountId(targetAddress), nestedToken, 1), - ), 'while burning').to.not.be.rejected; - await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected; - }); + // Nest and burn + await nestedToken.nest(alice, targetToken); + await expect(nestedToken.burnFrom(alice, targetToken.nestingAccount()), 'while burning').to.be.fulfilled; + await expect(nestedToken.getOwner()).to.be.rejected; }); - it('Fungible: allows the owner to successfully unnest a token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)}; + itSub('Fungible: 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); - const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const nestedToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible'); + 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; + expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(9n); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(1n); - // Nest and unnest - await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible'); - await transferFromExpectSuccess(collectionFT, nestedToken, alice, targetAddress, alice, 1, 'Fungible'); - - // Nest and burn - await transferExpectSuccess(collectionFT, nestedToken, alice, targetAddress, 1, 'Fungible'); - const balanceBefore = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken); - await expect(executeTransaction( - api, - alice, - api.tx.unique.burnFrom(collectionFT, normalizeAccountId(targetAddress), nestedToken, 1), - ), 'while burning').to.not.be.rejected; - const balanceAfter = await getBalance(api, collectionFT, normalizeAccountId(targetAddress), nestedToken); - expect(balanceAfter + BigInt(1)).to.be.equal(balanceBefore); - }); + // Nest and burn + await collectionFT.transfer(alice, targetToken.nestingAccount(), 5n); + await expect(collectionFT.burnTokensFrom(alice, targetToken.nestingAccount(), 6n), 'while burning').to.be.fulfilled; + expect(await collectionFT.getBalance({Substrate: alice.address})).to.be.equal(4n); + expect(await collectionFT.getBalance(targetToken.nestingAccount())).to.be.equal(0n); + expect(await targetToken.getChildren()).to.be.length(0); }); - it('ReFungible: allows the owner to successfully unnest a token', async function() { - await requirePallets(this, [Pallets.ReFungible]); + itSub.ifWithPallets('ReFungible: allows the owner to successfully unnest a token', [Pallets.ReFungible], async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); + const targetToken = await collection.mintToken(alice); - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)}; - - const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const nestedToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible'); - - // Nest and unnest - await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible'); - await transferFromExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, alice, 1, 'ReFungible'); + 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; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(9n); + expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(1n); - // Nest and burn - await transferExpectSuccess(collectionRFT, nestedToken, alice, targetAddress, 1, 'ReFungible'); - await expect(executeTransaction( - api, - alice, - api.tx.unique.burnFrom(collectionRFT, normalizeAccountId(targetAddress), nestedToken, 1), - ), 'while burning').to.not.be.rejected; - const balance = await getBalance(api, collectionRFT, normalizeAccountId(targetAddress), nestedToken); - expect(balance).to.be.equal(0n); - }); + // Nest and burn + await token.transfer(alice, targetToken.nestingAccount(), 5n); + await expect(token.burnFrom(alice, targetToken.nestingAccount(), 6n), 'while burning').to.be.fulfilled; + expect(await token.getBalance({Substrate: alice.address})).to.be.equal(4n); + expect(await token.getBalance(targetToken.nestingAccount())).to.be.equal(0n); + expect(await targetToken.getChildren()).to.be.length(0); }); }); describe('Negative Test: Unnesting', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); + await usingPlaygrounds(async (helper, privateKey) => { + const donor = privateKey('//Alice'); + [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor); }); }); - it('Disallows a non-owner to unnest/burn a token', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); - const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)}; + itSub('Disallows a non-owner to unnest/burn 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 createItemExpectSuccess(alice, collection, 'NFT', targetAddress); + // Create a nested token + const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); - // Try to unnest - await expect(executeTransaction( - api, - bob, - api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1), - ), 'while unnesting').to.be.rejectedWith(/^common\.ApprovedValueTooLow$/); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Try to unnest + await expect(nestedToken.unnest(bob, targetToken, {Substrate: alice.address})).to.be.rejectedWith(/common\.ApprovedValueTooLow/); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); - // Try to burn - await expect(executeTransaction( - api, - bob, - api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1), - ), 'while burning').to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/); - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); - }); + // Try to burn + await expect(nestedToken.burnFrom(bob, targetToken.nestingAccount())).to.be.rejectedWith(/common\.ApprovedValueTooLow/); + expect(await nestedToken.getOwner()).to.be.deep.equal(targetToken.nestingAccount().toLowerCase()); }); // todo another test for creating excessive depth matryoshka with Ethereum? // Recursive nesting - it('Prevents Ouroboros creation', async () => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + itSub('Prevents Ouroboros creation', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); + const targetToken = await collection.mintToken(alice); - // Create a nested token ouroboros - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); - await expect(transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)})).to.be.rejectedWith(/^structure\.OuroborosDetected$/); + // Fail to create a nested token ouroboros + const nestedToken = await collection.mintToken(alice, targetToken.nestingAccount()); + await expect(targetToken.nest(alice, nestedToken)).to.be.rejectedWith(/^structure\.OuroborosDetected$/); }); }); --- a/tests/src/refungible.test.ts +++ b/tests/src/refungible.test.ts @@ -124,7 +124,7 @@ const token = await collection.mintToken(alice, 100n); expect(await collection.isTokenExists(token.tokenId)).to.be.true; expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); - expect((await token.burn(alice, 99n)).success).to.be.true; + expect(await token.burn(alice, 99n)).to.be.true; expect(await collection.isTokenExists(token.tokenId)).to.be.true; expect(await token.getBalance({Substrate: alice.address})).to.be.equal(1n); }); @@ -136,7 +136,7 @@ expect(await collection.isTokenExists(token.tokenId)).to.be.true; expect(await token.getBalance({Substrate: alice.address})).to.be.equal(100n); - expect((await token.burn(alice, 100n)).success).to.be.true; + expect(await token.burn(alice, 100n)).to.be.true; expect(await collection.isTokenExists(token.tokenId)).to.be.false; }); @@ -152,17 +152,17 @@ expect(await token.getBalance({Substrate: alice.address})).to.be.equal(40n); expect(await token.getBalance({Substrate: bob.address})).to.be.equal(60n); - expect((await token.burn(alice, 40n)).success).to.be.true; + expect(await token.burn(alice, 40n)).to.be.true; expect(await collection.isTokenExists(token.tokenId)).to.be.true; expect(await token.getBalance({Substrate: alice.address})).to.be.equal(0n); - expect((await token.burn(bob, 59n)).success).to.be.true; + expect(await token.burn(bob, 59n)).to.be.true; expect(await token.getBalance({Substrate: bob.address})).to.be.equal(1n); expect(await collection.isTokenExists(token.tokenId)).to.be.true; - expect((await token.burn(bob, 1n)).success).to.be.true; + expect(await token.burn(bob, 1n)).to.be.true; expect(await collection.isTokenExists(token.tokenId)).to.be.false; }); --- a/tests/src/rpc.test.ts +++ b/tests/src/rpc.test.ts @@ -16,7 +16,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {usingPlaygrounds, itSub, expect} from './util/playgrounds'; -import {crossAccountIdFromLower} from './util/playgrounds/unique'; +import {CrossAccountId} from './util/playgrounds/unique'; describe('integration test: RPC methods', () => { let donor: IKeyringPair; @@ -55,7 +55,7 @@ // Set-up over const owners = await helper.callRpc('api.rpc.unique.tokenOwners', [collection.collectionId, 0]); - const ids = (owners.toJSON() as any[]).map(crossAccountIdFromLower); + const ids = (owners.toJSON() as any[]).map(CrossAccountId.fromLowerCaseKeys); expect(ids).to.deep.include.members([{Substrate: alice.address}, ethAcc, {Substrate: bob.address}, ...facelessCrowd]); expect(owners.length == 10).to.be.true; --- a/tests/src/util/playgrounds/index.ts +++ b/tests/src/util/playgrounds/index.ts @@ -9,7 +9,6 @@ import '../../interfaces/augment-api-events'; import {DevUniqueHelper, SilentLogger, SilentConsole} from './unique.dev'; - chai.use(chaiAsPromised); export const expect = chai.expect; @@ -63,6 +62,12 @@ }); }); } +export async function itSubIfWithPallet(name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean, requiredPallets?: string[] } = {}) { + return itSub(name, cb, {requiredPallets: required, ...opts}); +} itSub.only = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {only: true}); itSub.skip = (name: string, cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {skip: true}); -itSub.ifWithPallets = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSub(name, cb, {requiredPallets: required}); + +itSubIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSubIfWithPallet(name, required, cb, {only: true}); +itSubIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itSubIfWithPallet(name, required, cb, {skip: true}); +itSub.ifWithPallets = itSubIfWithPallet; --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -94,15 +94,15 @@ export interface IProperty { key: string; - value: string; + value?: string; } export interface ITokenPropertyPermission { key: string; permission: { - mutable: boolean; - tokenOwner: boolean; - collectionAdmin: boolean; + mutable?: boolean; + tokenOwner?: boolean; + collectionAdmin?: boolean; } } --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -6,6 +6,7 @@ import {ApiPromise, WsProvider} from '@polkadot/api'; import * as defs from '../../interfaces/definitions'; import {IKeyringPair} from '@polkadot/types/types'; +import {ICrossAccountId} from './types'; export class SilentLogger { @@ -230,6 +231,17 @@ 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); + + await promise(); + + balance -= await this.helper.balance.getSubstrate(address); + + return balance; + } } class WaitGroup { --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -11,12 +11,42 @@ import {IKeyringPair} from '@polkadot/types/types'; import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types'; -export const crossAccountIdFromLower = (lowerAddress: ICrossAccountIdLower): ICrossAccountId => { - const address = {} as ICrossAccountId; - if(lowerAddress.substrate) address.Substrate = lowerAddress.substrate; - if(lowerAddress.ethereum) address.Ethereum = lowerAddress.ethereum; - return address; -}; +export class CrossAccountId implements ICrossAccountId { + Substrate?: TSubstrateAccount; + Ethereum?: TEthereumAccount; + + constructor(account: ICrossAccountId) { + if (account.Substrate) this.Substrate = account.Substrate; + if (account.Ethereum) this.Ethereum = account.Ethereum; + } + + static fromKeyring(account: IKeyringPair) { + return new CrossAccountId({Substrate: account.address}); + } + + static fromLowerCaseKeys(address: ICrossAccountIdLower): CrossAccountId { + return new CrossAccountId({Substrate: address.substrate, Ethereum: address.ethereum}); + } + + static normalizeSubstrateAddress(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount { + return encodeAddress(decodeAddress(address), ss58Format); + } + + static withNormalizedSubstrate(address: TSubstrateAccount, ss58Format = 42): CrossAccountId { + return new CrossAccountId({Substrate: CrossAccountId.normalizeSubstrateAddress(address, ss58Format)}); + } + + withNormalizedSubstrate(ss58Format = 42): CrossAccountId { + if (this.Substrate) return CrossAccountId.withNormalizedSubstrate(this.Substrate, ss58Format); + return this; + } + + toLowerCase(): CrossAccountId { + if (this.Substrate) this.Substrate = this.Substrate.toLowerCase(); + if (this.Ethereum) this.Ethereum = this.Ethereum.toLowerCase(); + return this; + } +} const nesting = { toChecksumAddress(address: string): string { @@ -55,8 +85,12 @@ RPC: 'rpc', }; - static getNestingTokenAddress(collectionId: number, tokenId: number) { - return nesting.tokenIdToAddress(collectionId, tokenId); + static getTokenAccount(token: IToken): CrossAccountId { + return new CrossAccountId({Ethereum: this.getTokenAddress(token)}); + } + + static getTokenAddress(token: IToken): string { + return nesting.tokenIdToAddress(token.collectionId, token.tokenId); } static getDefaultLogger(): ILogger { @@ -86,11 +120,7 @@ return keyring.addFromUri(seed); } - static normalizeSubstrateAddress(address: string, ss58Format = 42) { - return encodeAddress(decodeAddress(address), ss58Format); - } - - static extractCollectionIdFromCreationResult(creationResult: ITransactionResult) { + static extractCollectionIdFromCreationResult(creationResult: ITransactionResult): number { if (creationResult.status !== this.transactionStatus.SUCCESS) { throw Error('Unable to create collection!'); } @@ -109,12 +139,15 @@ return collectionId; } - static extractTokensFromCreationResult(creationResult: ITransactionResult) { + static extractTokensFromCreationResult(creationResult: ITransactionResult): { + success: boolean, + tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[], + } { if (creationResult.status !== this.transactionStatus.SUCCESS) { throw Error('Unable to create tokens!'); } let success = false; - const tokens = [] as any; + const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[]; creationResult.result.events.forEach(({event: {data, method, section}}) => { if (method === 'ExtrinsicSuccess') { success = true; @@ -122,19 +155,23 @@ tokens.push({ collectionId: parseInt(data[0].toString(), 10), tokenId: parseInt(data[1].toString(), 10), - owner: data[2].toJSON(), + owner: data[2].toHuman(), + amount: data[3].toBigInt(), }); } }); return {success, tokens}; } - static extractTokensFromBurnResult(burnResult: ITransactionResult) { + static extractTokensFromBurnResult(burnResult: ITransactionResult): { + success: boolean, + tokens: {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[], + } { if (burnResult.status !== this.transactionStatus.SUCCESS) { throw Error('Unable to burn tokens!'); } let success = false; - const tokens = [] as any; + const tokens = [] as {collectionId: number, tokenId: number, owner: CrossAccountId, amount: bigint}[]; burnResult.result.events.forEach(({event: {data, method, section}}) => { if (method === 'ExtrinsicSuccess') { success = true; @@ -142,14 +179,15 @@ tokens.push({ collectionId: parseInt(data[0].toString(), 10), tokenId: parseInt(data[1].toString(), 10), - owner: data[2].toJSON(), + owner: data[2].toHuman(), + amount: data[3].toBigInt(), }); } }); return {success, tokens}; } - static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string) { + static findCollectionInEvents(events: {event: IEvent}[], collectionId: number, expectedSection: string, expectedMethod: string): boolean { let eventId = null; events.forEach(({event: {data, method, section}}) => { if ((section === expectedSection) && (method === expectedMethod)) { @@ -170,8 +208,8 @@ Object.keys(address).forEach(k => { obj[k.toLocaleLowerCase()] = address[k as 'Substrate' | 'Ethereum']; }); - if(obj.substrate) return {Substrate: this.normalizeSubstrateAddress(obj.substrate)}; - if(obj.ethereum) return {Ethereum: obj.ethereum.toLocaleLowerCase()}; + if(obj.substrate) return CrossAccountId.withNormalizedSubstrate(obj.substrate); + if(obj.ethereum) return CrossAccountId.fromLowerCaseKeys(obj).toLowerCase(); return address; }; let transfer = {collectionId: null, tokenId: null, from: null, to: null, amount: 1} as any; @@ -555,7 +593,7 @@ name: string; description: string; tokensCount: number; - admins: ICrossAccountId[]; + admins: CrossAccountId[]; normalizedOwner: TSubstrateAccount; raw: any } | null> { @@ -588,15 +626,11 @@ * @example await getAdmins(1) * @returns array of administrators */ - async getAdmins(collectionId: number, normalize = false): Promise { + async getAdmins(collectionId: number, normalize = false): Promise { const admins = (await this.helper.callRpc('api.rpc.unique.adminlist', [collectionId])).toHuman(); return normalize - ? admins.map((address: any) => { - return address.Substrate - ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)} - : address; - }) + ? admins.map((address: CrossAccountId) => address.withNormalizedSubstrate()) : admins; } @@ -607,14 +641,10 @@ * @example await getAllowList(1) * @returns array of allow-listed addresses */ - async getAllowList(collectionId: number, normalize = false): Promise { + async getAllowList(collectionId: number, normalize = false): Promise { const allowListed = (await this.helper.callRpc('api.rpc.unique.allowlist', [collectionId])).toHuman(); return normalize - ? allowListed.map((address: any) => { - return address.Substrate - ? {Substrate: this.helper.address.normalizeSubstrate(address.Substrate)} - : address; - }) + ? allowListed.map((address: CrossAccountId) => address.withNormalizedSubstrate()) : allowListed; } @@ -897,6 +927,18 @@ } /** + * Get collection properties. + * + * @param collectionId ID of collection + * @param propertyKeys optionally filter the returned properties to only these keys + * @example getProperties(1219, ['location', 'date', 'time', 'isParadise']); + * @returns array of key-value pairs + */ + async getProperties(collectionId: number, propertyKeys: string[] | null = null): Promise { + return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman(); + } + + /** * Deletes onchain properties from the collection. * * @param signer keyring of signer @@ -967,12 +1009,9 @@ * @param tokenId ID of token * @param amount amount of tokens to be burned. For NFT must be set to 1n * @example burnToken(aliceKeyring, 10, 5); - * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null``` + * @returns ```true``` if the extrinsic is successful, otherwise ```false``` */ - async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise<{ - success: boolean, - token: number | null - }> { + async burnToken(signer: TSigner, collectionId: number, tokenId: number, amount=1n): Promise { const burnResult = await this.helper.executeExtrinsic( signer, 'api.tx.unique.burnItem', [collectionId, tokenId, amount], @@ -980,7 +1019,7 @@ ); const burnedTokens = this.helper.util.extractTokensFromBurnResult(burnResult); if (burnedTokens.tokens.length > 1) throw Error('Burned multiple tokens'); - return {success: burnedTokens.success, token: burnedTokens.tokens.length > 0 ? burnedTokens.tokens[0] : null}; + return burnedTokens.success; } /** @@ -988,13 +1027,13 @@ * * @param signer keyring of signer * @param collectionId ID of collection + * @param tokenId ID of token * @param fromAddressObj address on behalf of which the token will be burnt - * @param tokenId ID of token * @param amount amount of tokens to be burned. For NFT must be set to 1n * @example burnTokenFrom(aliceKeyring, 10, {Substrate: "5DyN4Y92vZCjv38fg..."}, 5, {Ethereum: "0x9F0583DbB85..."}) * @returns ```true``` if extrinsic success, otherwise ```false``` */ - async burnTokenFrom(signer: TSigner, collectionId: number, fromAddressObj: ICrossAccountId, tokenId: number, amount=1n): Promise { + async burnTokenFrom(signer: TSigner, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise { const burnResult = await this.helper.executeExtrinsic( signer, 'api.tx.unique.burnFrom', [collectionId, fromAddressObj, tokenId, amount], @@ -1087,8 +1126,8 @@ */ async getToken(collectionId: number, tokenId: number, propertyKeys: string[] = [], blockHashAt?: string): Promise<{ properties: IProperty[]; - owner: ICrossAccountId; - normalizedOwner: ICrossAccountId; + owner: CrossAccountId; + normalizedOwner: CrossAccountId; }| null> { let tokenData; if(typeof blockHashAt === 'undefined') { @@ -1106,9 +1145,11 @@ if (tokenData === null || tokenData.owner === null) return null; const owner = {} as any; for (const key of Object.keys(tokenData.owner)) { - owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() === 'substrate' ? this.helper.address.normalizeSubstrate(tokenData.owner[key]) : tokenData.owner[key]; + owner[key.toLocaleLowerCase()] = key.toLocaleLowerCase() == 'substrate' + ? CrossAccountId.normalizeSubstrateAddress(tokenData.owner[key]) + : tokenData.owner[key]; } - tokenData.normalizedOwner = crossAccountIdFromLower(owner); + tokenData.normalizedOwner = CrossAccountId.fromLowerCaseKeys(owner); return tokenData; } @@ -1117,7 +1158,7 @@ * * @param signer keyring of signer * @param collectionId ID of collection - * @param permissions permissions to change a property by the collection owner or admin + * @param permissions permissions to change a property by the collection admin or token owner * @example setTokenPropertyPermissions( * aliceKeyring, 10, [{key: "gender", permission: {tokenOwner: true, mutable: true, collectionAdmin: true}}] * ) @@ -1134,6 +1175,18 @@ } /** + * Get token property permissions. + * + * @param collectionId ID of collection + * @param propertyKeys optionally filter the returned property permissions to only these keys + * @example getPropertyPermissions(1219, ['location', 'date', 'time', 'isParadise']); + * @returns array of key-permission pairs + */ + async getPropertyPermissions(collectionId: number, propertyKeys: string[] | null = null): Promise { + return (await this.helper.callRpc('api.rpc.unique.propertyPermissions', [collectionId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman(); + } + + /** * Set token properties * * @param signer keyring of signer @@ -1154,6 +1207,19 @@ } /** + * Get properties, metadata assigned to a token. + * + * @param collectionId ID of collection + * @param tokenId ID of token + * @param propertyKeys optionally filter the returned properties to only these keys + * @example getTokenProperties(1219, ['location', 'date', 'time', 'isParadise']); + * @returns array of key-value pairs + */ + async getTokenProperties(collectionId: number, tokenId: number, propertyKeys: string[] | null = null): Promise { + return (await this.helper.callRpc('api.rpc.unique.tokenProperties', [collectionId, tokenId, ...(propertyKeys === null ? [] : [propertyKeys])])).toHuman(); + } + + /** * Delete the provided properties of a token * @param signer keyring of signer * @param collectionId ID of collection @@ -1181,7 +1247,7 @@ * @example mintCollection(aliceKeyring, {name: 'New', description: "New collection", tokenPrefix: "NEW"}, "NFT") * @returns object of the created collection */ - async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise { + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, mode: 'NFT' | 'RFT'): Promise { collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object collectionOptions.mode = (mode === 'NFT') ? {nft: null} : {refungible: null}; for (const key of ['name', 'description', 'tokenPrefix']) { @@ -1223,8 +1289,8 @@ * @example getTokenObject(10, 5); * @returns instance of UniqueNFTToken */ - getTokenObject(collectionId: number, tokenId: number): UniqueNFTToken { - return new UniqueNFTToken(tokenId, this.getCollectionObject(collectionId)); + getTokenObject(collectionId: number, tokenId: number): UniqueNFToken { + return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId)); } /** @@ -1235,14 +1301,14 @@ * @example getTokenOwner(10, 5); * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."} */ - async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise { + async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise { let owner; if (typeof blockHashAt === 'undefined') { owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]); } else { owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]); } - return crossAccountIdFromLower(owner.toJSON()); + return CrossAccountId.fromLowerCaseKeys(owner.toJSON()); } /** @@ -1294,7 +1360,7 @@ * @example getTokenTopmostOwner(10, 5); * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."} */ - async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise { + async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise { let owner; if (typeof blockHashAt === 'undefined') { owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]); @@ -1304,9 +1370,7 @@ if (owner === null) return null; - owner = owner.toHuman(); - - return owner.Substrate ? {Substrate: this.helper.address.normalizeSubstrate(owner.Substrate)} : owner; + return owner.toHuman(); } /** @@ -1339,7 +1403,7 @@ * @returns ```true``` if extrinsic success, otherwise ```false``` */ async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise { - const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)}; + const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj); const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress); if(!result) { throw Error('Unable to nest token!'); @@ -1357,7 +1421,7 @@ * @returns ```true``` if extrinsic success, otherwise ```false``` */ async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise { - const rootTokenAddress = {Ethereum: this.helper.util.getNestingTokenAddress(rootTokenObj.collectionId, rootTokenObj.tokenId)}; + const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj); const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj); if(!result) { throw Error('Unable to unnest token!'); @@ -1377,7 +1441,7 @@ * }) * @returns object of the created collection */ - async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise { + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise { return await super.mintCollection(signer, collectionOptions, 'NFT') as UniqueNFTCollection; } @@ -1387,7 +1451,7 @@ * @param data token data * @returns created token object */ - async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise { + async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; properties?: IProperty[]; }): Promise { const creationResult = await this.helper.executeExtrinsic( signer, 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, { @@ -1418,7 +1482,7 @@ * }]); * @returns ```true``` if extrinsic success, otherwise ```false``` */ - async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise { + async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, properties?: IProperty[]}[]): Promise { const creationResult = await this.helper.executeExtrinsic( signer, 'api.tx.unique.createMultipleItemsEx', [collectionId, {NFT: tokens}], @@ -1446,7 +1510,7 @@ * }]); * @returns array of newly created tokens */ - async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise { + async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {properties?: IProperty[]}[]): Promise { const rawTokens = []; for (const token of tokens) { const raw = {NFT: {properties: token.properties}}; @@ -1495,8 +1559,8 @@ * @example getTokenObject(10, 5); * @returns instance of UniqueNFTToken */ - getTokenObject(collectionId: number, tokenId: number): UniqueRFTToken { - return new UniqueRFTToken(tokenId, this.getCollectionObject(collectionId)); + getTokenObject(collectionId: number, tokenId: number): UniqueRFToken { + return new UniqueRFToken(tokenId, this.getCollectionObject(collectionId)); } /** @@ -1506,8 +1570,8 @@ * @example getTokenTop10Owners(10, 5); * @returns array of top 10 owners */ - async getTokenTop10Owners(collectionId: number, tokenId: number): Promise { - return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(crossAccountIdFromLower); + async getTokenTop10Owners(collectionId: number, tokenId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, tokenId])).toJSON().map(CrossAccountId.fromLowerCaseKeys); } /** @@ -1563,7 +1627,7 @@ * }) * @returns object of the created collection */ - async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions): Promise { + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}): Promise { return await super.mintCollection(signer, collectionOptions, 'RFT') as UniqueRFTCollection; } @@ -1574,7 +1638,7 @@ * @example mintToken(aliceKeyring, {collectionId: 10, owner: {Substrate: '5GHoZe9c73RYbVzq...'}, pieces: 10000n}); * @returns created token object */ - async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise { + async mintToken(signer: TSigner, data: { collectionId: number; owner: ICrossAccountId | string; pieces: bigint; properties?: IProperty[]; }): Promise { const creationResult = await this.helper.executeExtrinsic( signer, 'api.tx.unique.createItem', [data.collectionId, (typeof data.owner === 'string') ? {Substrate: data.owner} : data.owner, { @@ -1591,7 +1655,7 @@ return this.getTokenObject(data.collectionId, createdTokens.tokens[0].tokenId); } - async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise { + async mintMultipleTokens(signer: TSigner, collectionId: number, tokens: {owner: ICrossAccountId, pieces: bigint, properties?: IProperty[]}[]): Promise { throw Error('Not implemented'); const creationResult = await this.helper.executeExtrinsic( signer, @@ -1611,7 +1675,7 @@ * @example mintMultipleTokensWithOneOwner(aliceKeyring, 10, {Substrate: "5GHoZe9c73RYbVzq..."}, [{pieces: 100000n, properties: [{key: "gender", value: "male"}]}]); * @returns array of newly created RFT tokens */ - async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise { + async mintMultipleTokensWithOneOwner(signer: TSigner, collectionId: number, owner: ICrossAccountId, tokens: {pieces: bigint, properties?: IProperty[]}[]): Promise { const rawTokens = []; for (const token of tokens) { const raw = {ReFungible: {pieces: token.pieces, properties: token.properties}}; @@ -1633,13 +1697,27 @@ * @param tokenId ID of token * @param amount number of pieces to be burnt * @example burnToken(aliceKeyring, 10, 5); - * @returns ```true``` and burnt token number is extrinsic success. Otherwise ```false``` and ```null``` + * @returns ```true``` if the extrinsic is successful, otherwise ```false``` */ - async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise<{ success: boolean; token: number | null; }> { + async burnToken(signer: IKeyringPair, collectionId: number, tokenId: number, amount=1n): Promise { return await super.burnToken(signer, collectionId, tokenId, amount); } /** + * Destroys a concrete instance of RFT on behalf of the owner. + * @param signer keyring of signer + * @param collectionId ID of collection + * @param tokenId ID of token + * @param fromAddressObj address on behalf of which the token will be burnt + * @param amount number of pieces to be burnt + * @example burnTokenFrom(aliceKeyring, 10, 5, {Substrate: "5DyN4Y92vZCjv38fg..."}, 2n) + * @returns ```true``` if extrinsic success, otherwise ```false``` + */ + async burnTokenFrom(signer: IKeyringPair, collectionId: number, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise { + return await super.burnTokenFrom(signer, collectionId, tokenId, fromAddressObj, amount); + } + + /** * Set, change, or remove approved address to transfer the ownership of the RFT. * * @param signer keyring of signer @@ -1711,7 +1789,7 @@ * }, 18) * @returns newly created fungible collection */ - async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions, decimalPoints = 0): Promise { + async mintCollection(signer: TSigner, collectionOptions: ICollectionCreationOptions = {}, decimalPoints = 0): Promise { collectionOptions = JSON.parse(JSON.stringify(collectionOptions)) as ICollectionCreationOptions; // Clone object if(collectionOptions.tokenPropertyPermissions) throw Error('Fungible collections has no tokenPropertyPermissions'); collectionOptions.mode = {fungible: decimalPoints}; @@ -1776,8 +1854,8 @@ * @example getTop10Owners(10); * @returns array of ```ICrossAccountId``` */ - async getTop10Owners(collectionId: number): Promise { - return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(crossAccountIdFromLower); + async getTop10Owners(collectionId: number): Promise { + return (await this.helper.callRpc('api.rpc.unique.tokenOwners', [collectionId, 0])).toJSON().map(CrossAccountId.fromLowerCaseKeys); } /** @@ -1827,7 +1905,7 @@ * @returns ```true``` if extrinsic success, otherwise ```false``` */ async burnTokens(signer: IKeyringPair, collectionId: number, amount=1n): Promise { - return (await super.burnToken(signer, collectionId, 0, amount)).success; + return await super.burnToken(signer, collectionId, 0, amount); } /** @@ -1840,7 +1918,7 @@ * @returns ```true``` if extrinsic success, otherwise ```false``` */ async burnTokensFrom(signer: IKeyringPair, collectionId: number, fromAddressObj: ICrossAccountId, amount=1n): Promise { - return await super.burnTokenFrom(signer, collectionId, fromAddressObj, 0, amount); + return await super.burnTokenFrom(signer, collectionId, 0, fromAddressObj, amount); } /** @@ -1936,7 +2014,7 @@ class BalanceGroup extends HelperGroup { /** - * Representation of the native token in the smallest unit + * Representation of the native token in the smallest unit - one OPAL (OPL), QUARTZ (QTZ), or UNIQUE (UNQ). * @example getOneTokenNominal() * @returns ```BigInt``` representation of the native token in the smallest unit, e.g. ```1_000_000_000_000_000_000n``` for QTZ. */ @@ -1996,9 +2074,9 @@ }; } }); - let isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from; - isSuccess = isSuccess && this.helper.address.normalizeSubstrate(address) === transfer.to; - isSuccess = isSuccess && BigInt(amount) === transfer.amount; + const isSuccess = this.helper.address.normalizeSubstrate(typeof signer === 'string' ? signer : signer.address) === transfer.from + && this.helper.address.normalizeSubstrate(address) === transfer.to + && BigInt(amount) === transfer.amount; return isSuccess; } } @@ -2013,7 +2091,7 @@ * @returns substrate address converted to normalized (i.e., starting with 5) or specified explicitly representation */ normalizeSubstrate(address: TSubstrateAccount, ss58Format = 42): TSubstrateAccount { - return this.helper.util.normalizeSubstrateAddress(address, ss58Format); + return CrossAccountId.normalizeSubstrateAddress(address, ss58Format); } /** @@ -2161,7 +2239,7 @@ } -class UniqueCollectionBase { +export class UniqueBaseCollection { helper: UniqueHelper; collectionId: number; @@ -2194,6 +2272,14 @@ return await this.helper.collection.getEffectiveLimits(this.collectionId); } + async getProperties(propertyKeys: string[] | null = null) { + return await this.helper.collection.getProperties(this.collectionId, propertyKeys); + } + + async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) { + return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj); + } + async setSponsor(signer: TSigner, sponsorAddress: TSubstrateAccount) { return await this.helper.collection.setSponsor(signer, this.collectionId, sponsorAddress); } @@ -2236,10 +2322,6 @@ async deleteProperties(signer: TSigner, propertyKeys: string[]) { return await this.helper.collection.deleteProperties(signer, this.collectionId, propertyKeys); - } - - async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) { - return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj); } async setPermissions(signer: TSigner, permissions: ICollectionPermissions) { @@ -2260,9 +2342,9 @@ } -class UniqueNFTCollection extends UniqueCollectionBase { +export class UniqueNFTCollection extends UniqueBaseCollection { getTokenObject(tokenId: number) { - return new UniqueNFTToken(tokenId, this); + return new UniqueNFToken(tokenId, this); } async getTokensByAddress(addressObj: ICrossAccountId) { @@ -2285,6 +2367,14 @@ return await this.helper.nft.getTokenChildren(this.collectionId, tokenId, blockHashAt); } + async getPropertyPermissions(propertyKeys: string[] | null = null) { + return await this.helper.nft.getPropertyPermissions(this.collectionId, propertyKeys); + } + + async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) { + return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys); + } + async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) { return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj); } @@ -2313,6 +2403,10 @@ return await this.helper.nft.burnToken(signer, this.collectionId, tokenId); } + async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId) { + return await this.helper.nft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj); + } + async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) { return await this.helper.nft.setTokenProperties(signer, this.collectionId, tokenId, properties); } @@ -2335,9 +2429,13 @@ } -class UniqueRFTCollection extends UniqueCollectionBase { +export class UniqueRFTCollection extends UniqueBaseCollection { getTokenObject(tokenId: number) { - return new UniqueRFTToken(tokenId, this); + return new UniqueRFToken(tokenId, this); + } + + async getToken(tokenId: number, blockHashAt?: string) { + return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt); } async getTokensByAddress(addressObj: ICrossAccountId) { @@ -2356,6 +2454,18 @@ return await this.helper.rft.getTokenTotalPieces(this.collectionId, tokenId); } + async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj); + } + + async getPropertyPermissions(propertyKeys: string[] | null = null) { + return await this.helper.rft.getPropertyPermissions(this.collectionId, propertyKeys); + } + + async getTokenProperties(tokenId: number, propertyKeys: string[] | null = null) { + return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys); + } + async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) { return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount); } @@ -2366,10 +2476,6 @@ async approveToken(signer: TSigner, tokenId: number, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.rft.approveToken(signer, this.collectionId, tokenId, toAddressObj, amount); - } - - async getTokenApprovedPieces(tokenId: number, fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { - return await this.helper.rft.getTokenApprovedPieces(this.collectionId, tokenId, toAddressObj, fromAddressObj); } async repartitionToken(signer: TSigner, tokenId: number, amount: bigint) { @@ -2388,6 +2494,10 @@ return await this.helper.rft.burnToken(signer, this.collectionId, tokenId, amount); } + async burnTokenFrom(signer: TSigner, tokenId: number, fromAddressObj: ICrossAccountId, amount=1n) { + return await this.helper.rft.burnTokenFrom(signer, this.collectionId, tokenId, fromAddressObj, amount); + } + async setTokenProperties(signer: TSigner, tokenId: number, properties: IProperty[]) { return await this.helper.rft.setTokenProperties(signer, this.collectionId, tokenId, properties); } @@ -2402,23 +2512,31 @@ } -class UniqueFTCollection extends UniqueCollectionBase { - async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) { - return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner); +export class UniqueFTCollection extends UniqueBaseCollection { + async getBalance(addressObj: ICrossAccountId) { + return await this.helper.ft.getBalance(this.collectionId, addressObj); } - async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) { - return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner); + async getTotalPieces() { + return await this.helper.ft.getTotalPieces(this.collectionId); } - async getBalance(addressObj: ICrossAccountId) { - return await this.helper.ft.getBalance(this.collectionId, addressObj); + async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { + return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj); } async getTop10Owners() { return await this.helper.ft.getTop10Owners(this.collectionId); } + async mint(signer: TSigner, amount=1n, owner: ICrossAccountId = {Substrate: signer.address}) { + return await this.helper.ft.mintTokens(signer, this.collectionId, amount, owner); + } + + async mintWithOneOwner(signer: TSigner, tokens: {value: bigint}[], owner: ICrossAccountId = {Substrate: signer.address}) { + return await this.helper.ft.mintMultipleTokensWithOneOwner(signer, this.collectionId, tokens, owner); + } + async transfer(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.transfer(signer, this.collectionId, toAddressObj, amount); } @@ -2433,23 +2551,15 @@ async burnTokensFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.burnTokensFrom(signer, this.collectionId, fromAddressObj, amount); - } - - async getTotalPieces() { - return await this.helper.ft.getTotalPieces(this.collectionId); } async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount); - } - - async getApprovedTokens(fromAddressObj: ICrossAccountId, toAddressObj: ICrossAccountId) { - return await this.helper.ft.getApprovedTokens(this.collectionId, fromAddressObj, toAddressObj); } } -class UniqueTokenBase implements IToken { +export class UniqueBaseToken { collection: UniqueNFTCollection | UniqueRFTCollection; collectionId: number; tokenId: number; @@ -2464,6 +2574,10 @@ return await this.collection.getTokenNextSponsored(this.tokenId, addressObj); } + async getProperties(propertyKeys: string[] | null = null) { + return await this.collection.getTokenProperties(this.tokenId, propertyKeys); + } + async setProperties(signer: TSigner, properties: IProperty[]) { return await this.collection.setTokenProperties(signer, this.tokenId, properties); } @@ -2471,10 +2585,14 @@ async deleteProperties(signer: TSigner, propertyKeys: string[]) { return await this.collection.deleteTokenProperties(signer, this.tokenId, propertyKeys); } + + nestingAccount() { + return this.collection.helper.util.getTokenAccount(this); + } } -class UniqueNFTToken extends UniqueTokenBase { +export class UniqueNFToken extends UniqueBaseToken { collection: UniqueNFTCollection; constructor(tokenId: number, collection: UniqueNFTCollection) { @@ -2525,9 +2643,13 @@ async burn(signer: TSigner) { return await this.collection.burnToken(signer, this.tokenId); } + + async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) { + return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj); + } } -class UniqueRFTToken extends UniqueTokenBase { +export class UniqueRFToken extends UniqueBaseToken { collection: UniqueRFTCollection; constructor(tokenId: number, collection: UniqueRFTCollection) { @@ -2535,6 +2657,10 @@ this.collection = collection; } + async getData(blockHashAt?: string) { + return await this.collection.getToken(this.tokenId, blockHashAt); + } + async getTop10Owners() { return await this.collection.getTop10TokenOwners(this.tokenId); } @@ -2547,6 +2673,10 @@ return await this.collection.getTokenTotalPieces(this.tokenId); } + async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) { + return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj); + } + async transfer(signer: TSigner, addressObj: ICrossAccountId, amount=1n) { return await this.collection.transferToken(signer, this.tokenId, addressObj, amount); } @@ -2559,10 +2689,6 @@ return await this.collection.approveToken(signer, this.tokenId, toAddressObj, amount); } - async getApprovedPieces(fromAddressObj: ICrossAccountId, toAccountObj: ICrossAccountId) { - return await this.collection.getTokenApprovedPieces(this.tokenId, fromAddressObj, toAccountObj); - } - async repartition(signer: TSigner, amount: bigint) { return await this.collection.repartitionToken(signer, this.tokenId, amount); } @@ -2570,4 +2696,8 @@ async burn(signer: TSigner, amount=1n) { return await this.collection.burnToken(signer, this.tokenId, amount); } + + async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) { + return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount); + } } -- gitstuff