git.delta.rocks / unique-network / refs/commits / 930910430200

difftreelog

chore add `collectionCreationFee` method to CollectionHelpers

Grigoriy Simonov2022-09-27parent: #413ee13.patch.diff
in: master

6 files changed

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -23,7 +23,7 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x88ee8ef1
+/// @dev the ERC-165 identifier for this interface is 0x5ad4f440
 contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create an NFT collection
 	/// @param name Name of the collection
@@ -105,4 +105,12 @@
 		dummy;
 		return false;
 	}
+
+	/// @dev EVM selector for this function is: 0xd23a7ab1,
+	///  or in textual repr: collectionCreationFee()
+	function collectionCreationFee() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
 }
modifiedtests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -18,7 +18,7 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x88ee8ef1
+/// @dev the ERC-165 identifier for this interface is 0x5ad4f440
 interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create an NFT collection
 	/// @param name Name of the collection
@@ -65,4 +65,8 @@
 	/// @dev EVM selector for this function is: 0xc3de1494,
 	///  or in textual repr: isCollectionExist(address)
 	function isCollectionExist(address collectionAddress) external view returns (bool);
+
+	/// @dev EVM selector for this function is: 0xd23a7ab1,
+	///  or in textual repr: collectionCreationFee()
+	function collectionCreationFee() external view returns (uint256);
 }
modifiedtests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/collectionHelpersAbi.json
+++ b/tests/src/eth/collectionHelpersAbi.json
@@ -19,6 +19,13 @@
     "type": "event"
   },
   {
+    "inputs": [],
+    "name": "collectionCreationFee",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
     "inputs": [
       { "internalType": "string", "name": "name", "type": "string" },
       { "internalType": "string", "name": "description", "type": "string" },
modifiedtests/src/eth/payable.test.tsdiffbeforeafterboth
before · tests/src/eth/payable.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';1819import {itEth, expect, usingEthPlaygrounds, EthUniqueHelper} from './util/playgrounds';2021describe('EVM payable contracts', () => {22  let donor: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (_, privateKey) => {26      donor = privateKey('//Alice');27    });28  });2930  itEth('Evm contract can receive wei from eth account', async ({helper}) => {31    const deployer = await helper.eth.createAccountWithBalance(donor);32    const contract = await helper.eth.deployCollectorContract(deployer);3334    const web3 = helper.getWeb3();3536    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', gas: helper.eth.DEFAULT_GAS});3738    expect(await contract.methods.getCollected().call()).to.be.equal('10000');39  });4041  itEth('Evm contract can receive wei from substrate account', async ({helper}) => {42    const deployer = await helper.eth.createAccountWithBalance(donor);43    const contract = await helper.eth.deployCollectorContract(deployer);44    const [alice] = await helper.arrange.createAccounts([10n], donor);4546    const weiCount = '10000';4748    // Transaction fee/value will be payed from subToEth(sender) evm balance,49    // which is backed by evmToAddress(subToEth(sender)) substrate balance50    await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n);515253    await helper.eth.sendEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount);5455    expect(await contract.methods.getCollected().call()).to.be.equal(weiCount);56  });5758  // We can't handle sending balance to backing storage of evm balance, because evmToAddress operation is irreversible59  itEth('Wei sent directly to backing storage of evm contract balance is unaccounted', async({helper}) => {60    const deployer = await helper.eth.createAccountWithBalance(donor);61    const contract = await helper.eth.deployCollectorContract(deployer);62    const [alice] = await helper.arrange.createAccounts([10n], donor);6364    const weiCount = 10_000n;6566    await helper.eth.transferBalanceFromSubstrate(alice, contract.options.address, weiCount, false);6768    expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString());69  });7071  itEth('Balance can be retrieved from evm contract', async({helper, privateKey}) => {72    const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal();73    const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal();7475    const deployer = await helper.eth.createAccountWithBalance(donor);76    const contract = await helper.eth.deployCollectorContract(deployer);77    const [alice] = await helper.arrange.createAccounts([20n], donor);7879    const web3 = helper.getWeb3();8081    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS});8283    const receiver = privateKey(`//Receiver${Date.now()}`);8485    // First receive balance on eth balance of bob86    {87      const ethReceiver = helper.address.substrateToEth(receiver.address);88      expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0');89      await contract.methods.withdraw(ethReceiver).send({from: deployer});90      expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString());91    }9293    // Some balance is required to pay fee for evm.withdraw call94    await helper.balance.transferToSubstrate(alice, receiver.address, FEE_BALANCE);95    // await transferBalanceExpectSuccess(api, alice, receiver.address, FEE_BALANCE.toString());9697    // Withdraw balance from eth to substrate98    {99      const initialReceiverBalance = await helper.balance.getSubstrate(receiver.address);100      await helper.executeExtrinsic(receiver, 'api.tx.evm.withdraw', [helper.address.substrateToEth(receiver.address), CONTRACT_BALANCE.toString()], true);101      const finalReceiverBalance = await helper.balance.getSubstrate(receiver.address);102103      expect(finalReceiverBalance > initialReceiverBalance).to.be.true;104    }105  });106});107108describe('EVM transaction fees', () => {109  let donor: IKeyringPair;110111  before(async function() {112    await usingEthPlaygrounds(async (_, privateKey) => {113      donor = privateKey('//Alice');114    });115  });116117  itEth('Fee is withdrawn from the user', async({helper}) => {118    const deployer = await helper.eth.createAccountWithBalance(donor);119    const caller = await helper.eth.createAccountWithBalance(donor);120    const contract = await helper.eth.deployFlipper(deployer);121    122    const initialCallerBalance = await helper.balance.getEthereum(caller);123    await contract.methods.flip().send({from: caller});124    const finalCallerBalance = await helper.balance.getEthereum(caller);125    expect(finalCallerBalance < initialCallerBalance).to.be.true;126  });127128  itEth('Fee for nested calls is withdrawn from the user', async({helper}) => {129    const deployer = await helper.eth.createAccountWithBalance(donor);130    const caller = await helper.eth.createAccountWithBalance(donor);131    const contract = await deployProxyContract(helper, deployer);132    133    const initialCallerBalance = await helper.balance.getEthereum(caller);134    const initialContractBalance = await helper.balance.getEthereum(contract.options.address);135    await contract.methods.flip().send({from: caller});136    const finalCallerBalance = await helper.balance.getEthereum(caller);137    const finalContractBalance = await helper.balance.getEthereum(contract.options.address);138    expect(finalCallerBalance < initialCallerBalance).to.be.true;139    expect(finalContractBalance == initialContractBalance).to.be.true;140  });141  142  itEth('Fee for nested calls to native methods is withdrawn from the user', async({helper}) => {143    const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();144145    const deployer = await helper.eth.createAccountWithBalance(donor);146    const caller = await helper.eth.createAccountWithBalance(donor);147    const contract = await deployProxyContract(helper, deployer);148149    const collectionAddress = (await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;150    const initialCallerBalance = await helper.balance.getEthereum(caller);151    const initialContractBalance = await helper.balance.getEthereum(contract.options.address);152    await contract.methods.mintNftToken(collectionAddress).send({from: caller});153    const finalCallerBalance = await helper.balance.getEthereum(caller);154    const finalContractBalance = await helper.balance.getEthereum(contract.options.address);155    expect(finalCallerBalance < initialCallerBalance).to.be.true;156    expect(finalContractBalance == initialContractBalance).to.be.true;157  });158  159  itEth('Fee for nested calls to create*Collection methods is withdrawn from the user and from the contract', async({helper}) => {160    const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();161    const deployer = await helper.eth.createAccountWithBalance(donor);162    const caller = await helper.eth.createAccountWithBalance(donor);163    const contract = await deployProxyContract(helper, deployer);164165    const initialCallerBalance = await helper.balance.getEthereum(caller);166    const initialContractBalance = await helper.balance.getEthereum(contract.options.address);167    await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});168    const finalCallerBalance = await helper.balance.getEthereum(caller);169    const finalContractBalance = await helper.balance.getEthereum(contract.options.address);170    expect(finalCallerBalance < initialCallerBalance).to.be.true;171    expect(finalContractBalance == initialContractBalance).to.be.true;172  });173174  async function deployProxyContract(helper: EthUniqueHelper, deployer: string) {175    return await helper.ethContract.deployByCode(176      deployer,177      'ProxyContract',178      `179      // SPDX-License-Identifier: UNLICENSED180      pragma solidity ^0.8.6;181182      import {CollectionHelpers} from "../api/CollectionHelpers.sol";183      import {UniqueNFT} from "../api/UniqueNFT.sol";184185      error Value(uint256 value);186187      contract ProxyContract {188        bool value = false;189        address flipper;190191        event CollectionCreated(address collection);192        event TokenMinted(uint256 tokenId);193194        receive() external payable {}195196        constructor() {197          flipper = address(new Flipper());198        }199200        function flip() public {201          value = !value;202          Flipper(flipper).flip();203        }204205        function createNonfungibleCollection() external payable {206          address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;207		      address nftCollection = CollectionHelpers(collectionHelpers).createNonfungibleCollection{value: msg.value}("A", "B", "C");208          emit CollectionCreated(nftCollection);209        }210211        function mintNftToken(address collectionAddress) external  {212          UniqueNFT collection = UniqueNFT(collectionAddress);213          uint256 tokenId = collection.nextTokenId();214          collection.mint(msg.sender, tokenId);215          emit TokenMinted(tokenId);216        }217218        function getValue() external view returns (bool) {219          return Flipper(flipper).getValue();220        }221      }222223      contract Flipper {224        bool value = false;225        function flip() external {226          value = !value;227        }228        function getValue() external view returns (bool) {229          return value;230        }231      }232      `,233      [234        {235          solPath: 'api/CollectionHelpers.sol',236          fsPath: `${__dirname}/api/CollectionHelpers.sol`,237        },238        {239          solPath: 'api/UniqueNFT.sol',240          fsPath: `${__dirname}/api/UniqueNFT.sol`,241        },242      ],243    );244  }245});
after · tests/src/eth/payable.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';1819import {itEth, expect, usingEthPlaygrounds, EthUniqueHelper} from './util/playgrounds';2021describe('EVM payable contracts', () => {22  let donor: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (_, privateKey) => {26      donor = privateKey('//Alice');27    });28  });2930  itEth('Evm contract can receive wei from eth account', async ({helper}) => {31    const deployer = await helper.eth.createAccountWithBalance(donor);32    const contract = await helper.eth.deployCollectorContract(deployer);3334    const web3 = helper.getWeb3();3536    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', gas: helper.eth.DEFAULT_GAS});3738    expect(await contract.methods.getCollected().call()).to.be.equal('10000');39  });4041  itEth('Evm contract can receive wei from substrate account', async ({helper}) => {42    const deployer = await helper.eth.createAccountWithBalance(donor);43    const contract = await helper.eth.deployCollectorContract(deployer);44    const [alice] = await helper.arrange.createAccounts([10n], donor);4546    const weiCount = '10000';4748    // Transaction fee/value will be payed from subToEth(sender) evm balance,49    // which is backed by evmToAddress(subToEth(sender)) substrate balance50    await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n);515253    await helper.eth.sendEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount);5455    expect(await contract.methods.getCollected().call()).to.be.equal(weiCount);56  });5758  // We can't handle sending balance to backing storage of evm balance, because evmToAddress operation is irreversible59  itEth('Wei sent directly to backing storage of evm contract balance is unaccounted', async({helper}) => {60    const deployer = await helper.eth.createAccountWithBalance(donor);61    const contract = await helper.eth.deployCollectorContract(deployer);62    const [alice] = await helper.arrange.createAccounts([10n], donor);6364    const weiCount = 10_000n;6566    await helper.eth.transferBalanceFromSubstrate(alice, contract.options.address, weiCount, false);6768    expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString());69  });7071  itEth('Balance can be retrieved from evm contract', async({helper, privateKey}) => {72    const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal();73    const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal();7475    const deployer = await helper.eth.createAccountWithBalance(donor);76    const contract = await helper.eth.deployCollectorContract(deployer);77    const [alice] = await helper.arrange.createAccounts([20n], donor);7879    const web3 = helper.getWeb3();8081    await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS});8283    const receiver = privateKey(`//Receiver${Date.now()}`);8485    // First receive balance on eth balance of bob86    {87      const ethReceiver = helper.address.substrateToEth(receiver.address);88      expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0');89      await contract.methods.withdraw(ethReceiver).send({from: deployer});90      expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString());91    }9293    // Some balance is required to pay fee for evm.withdraw call94    await helper.balance.transferToSubstrate(alice, receiver.address, FEE_BALANCE);95    // await transferBalanceExpectSuccess(api, alice, receiver.address, FEE_BALANCE.toString());9697    // Withdraw balance from eth to substrate98    {99      const initialReceiverBalance = await helper.balance.getSubstrate(receiver.address);100      await helper.executeExtrinsic(receiver, 'api.tx.evm.withdraw', [helper.address.substrateToEth(receiver.address), CONTRACT_BALANCE.toString()], true);101      const finalReceiverBalance = await helper.balance.getSubstrate(receiver.address);102103      expect(finalReceiverBalance > initialReceiverBalance).to.be.true;104    }105  });106});107108describe('EVM transaction fees', () => {109  let donor: IKeyringPair;110111  before(async function() {112    await usingEthPlaygrounds(async (_, privateKey) => {113      donor = privateKey('//Alice');114    });115  });116117  itEth('Fee is withdrawn from the user', async({helper}) => {118    const deployer = await helper.eth.createAccountWithBalance(donor);119    const caller = await helper.eth.createAccountWithBalance(donor);120    const contract = await helper.eth.deployFlipper(deployer);121    122    const initialCallerBalance = await helper.balance.getEthereum(caller);123    await contract.methods.flip().send({from: caller});124    const finalCallerBalance = await helper.balance.getEthereum(caller);125    expect(finalCallerBalance < initialCallerBalance).to.be.true;126  });127128  itEth('Fee for nested calls is withdrawn from the user', async({helper}) => {129    const deployer = await helper.eth.createAccountWithBalance(donor);130    const caller = await helper.eth.createAccountWithBalance(donor);131    const contract = await deployProxyContract(helper, deployer);132    133    const initialCallerBalance = await helper.balance.getEthereum(caller);134    const initialContractBalance = await helper.balance.getEthereum(contract.options.address);135    await contract.methods.flip().send({from: caller});136    const finalCallerBalance = await helper.balance.getEthereum(caller);137    const finalContractBalance = await helper.balance.getEthereum(contract.options.address);138    expect(finalCallerBalance < initialCallerBalance).to.be.true;139    expect(finalContractBalance == initialContractBalance).to.be.true;140  });141  142  itEth('Fee for nested calls to native methods is withdrawn from the user', async({helper}) => {143    const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();144145    const deployer = await helper.eth.createAccountWithBalance(donor);146    const caller = await helper.eth.createAccountWithBalance(donor);147    const contract = await deployProxyContract(helper, deployer);148149    const collectionAddress = (await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)})).events.CollectionCreated.returnValues.collection;150    const initialCallerBalance = await helper.balance.getEthereum(caller);151    const initialContractBalance = await helper.balance.getEthereum(contract.options.address);152    await contract.methods.mintNftToken(collectionAddress).send({from: caller});153    const finalCallerBalance = await helper.balance.getEthereum(caller);154    const finalContractBalance = await helper.balance.getEthereum(contract.options.address);155    expect(finalCallerBalance < initialCallerBalance).to.be.true;156    expect(finalContractBalance == initialContractBalance).to.be.true;157  });158  159  itEth('Fee for nested calls to create*Collection methods is withdrawn from the user and from the contract', async({helper}) => {160    const CONTRACT_BALANCE = 2n * helper.balance.getOneTokenNominal();161    const deployer = await helper.eth.createAccountWithBalance(donor);162    const caller = await helper.eth.createAccountWithBalance(donor);163    const contract = await deployProxyContract(helper, deployer);164165    const initialCallerBalance = await helper.balance.getEthereum(caller);166    const initialContractBalance = await helper.balance.getEthereum(contract.options.address);167    await contract.methods.createNonfungibleCollection().send({from: caller, value: Number(CONTRACT_BALANCE)});168    const finalCallerBalance = await helper.balance.getEthereum(caller);169    const finalContractBalance = await helper.balance.getEthereum(contract.options.address);170    expect(finalCallerBalance < initialCallerBalance).to.be.true;171    expect(finalContractBalance == initialContractBalance).to.be.true;172  });173174  itEth('Get collection creation fee', async({helper}) => {175    const deployer = await helper.eth.createAccountWithBalance(donor);176    expect(await helper.eth.getCollectionCreationFee(deployer)).to.be.equal(String(2n * helper.balance.getOneTokenNominal()));177  });178179  async function deployProxyContract(helper: EthUniqueHelper, deployer: string) {180    return await helper.ethContract.deployByCode(181      deployer,182      'ProxyContract',183      `184      // SPDX-License-Identifier: UNLICENSED185      pragma solidity ^0.8.6;186187      import {CollectionHelpers} from "../api/CollectionHelpers.sol";188      import {UniqueNFT} from "../api/UniqueNFT.sol";189190      error Value(uint256 value);191192      contract ProxyContract {193        bool value = false;194        address flipper;195196        event CollectionCreated(address collection);197        event TokenMinted(uint256 tokenId);198199        receive() external payable {}200201        constructor() {202          flipper = address(new Flipper());203        }204205        function flip() public {206          value = !value;207          Flipper(flipper).flip();208        }209210        function createNonfungibleCollection() external payable {211          address collectionHelpers = 0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F;212		      address nftCollection = CollectionHelpers(collectionHelpers).createNonfungibleCollection{value: msg.value}("A", "B", "C");213          emit CollectionCreated(nftCollection);214        }215216        function mintNftToken(address collectionAddress) external  {217          UniqueNFT collection = UniqueNFT(collectionAddress);218          uint256 tokenId = collection.nextTokenId();219          collection.mint(msg.sender, tokenId);220          emit TokenMinted(tokenId);221        }222223        function getValue() external view returns (bool) {224          return Flipper(flipper).getValue();225        }226      }227228      contract Flipper {229        bool value = false;230        function flip() external {231          value = !value;232        }233        function getValue() external view returns (bool) {234          return value;235        }236      }237      `,238      [239        {240          solPath: 'api/CollectionHelpers.sol',241          fsPath: `${__dirname}/api/CollectionHelpers.sol`,242        },243        {244          solPath: 'api/UniqueNFT.sol',245          fsPath: `${__dirname}/api/UniqueNFT.sol`,246        },247      ],248    );249  }250});
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -152,6 +152,11 @@
   async transferBalanceFromSubstrate(donor: IKeyringPair, recepient: string, amount=1000n, inTokens=true) {
     return await this.helper.balance.transferToSubstrate(donor, evmToAddress(recepient), amount * (inTokens ? this.helper.balance.getOneTokenNominal() : 1n));
   }
+  
+  async getCollectionCreationFee(signer: string) {
+    const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer);
+    return await collectionHelper.methods.collectionCreationFee().call();
+  }
 
   async sendEVM(signer: IKeyringPair, contractAddress: string, abi: string, value: string, gasLimit?: number) {
     if(!gasLimit) gasLimit = this.DEFAULT_GAS;