git.delta.rocks / unique-network / refs/commits / 9272309560ec

difftreelog

CORE-386 Implement eth methods

Trubnikov Sergey2022-06-01parent: #f11d024.patch.diff
in: master

7 files changed

modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -455,7 +455,7 @@
 	}
 }
 
-#[impl_for_tuples(0, 12)]
+#[impl_for_tuples(0, 24)]
 impl SolidityFunctions for Tuple {
 	for_tuples!( where #( Tuple: SolidityFunctions ),* );
 
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -23,7 +23,7 @@
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_core::{H160, U256, H256};
 use sp_std::vec::Vec;
-use up_data_structs::{Property, SponsoringRateLimit, NestingRule, OwnerRestrictedSet};
+use up_data_structs::{Property, SponsoringRateLimit, NestingRule, OwnerRestrictedSet, AccessMode};
 use alloc::format;
 
 use crate::{Pallet, CollectionHandle, Config, CollectionProperties};
@@ -252,6 +252,42 @@
 		save(self);
 		Ok(())
 	}
+
+	fn set_access(&mut self, caller: caller, mode: string) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		self.check_is_owner_or_admin(&caller)
+			.map_err(dispatch_to_evm::<T>)?;
+		self.collection.permissions.access = Some(match mode.as_str() {
+			"Normal" => AccessMode::Normal,
+			"AllowList" => AccessMode::AllowList,
+			_ => return Err("Not supported access mode".into()),
+		});
+		save(self);
+		Ok(())
+	}
+
+	fn add_to_allow_list(&self, caller: caller, user: address) -> Result<void> {
+		let caller = check_is_owner_or_admin(caller, self)?;
+		let user = T::CrossAccountId::from_eth(user);
+		<Pallet<T>>::toggle_allowlist(self, &caller, &user, true)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn remove_from_allow_list(&self, caller: caller, user: address) -> Result<void> {
+		let caller = check_is_owner_or_admin(caller, self)?;
+		let user = T::CrossAccountId::from_eth(user);
+		<Pallet<T>>::toggle_allowlist(self, &caller, &user, false)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn set_mint_mode(&mut self, caller: caller, mode: bool) -> Result<void> {
+		check_is_owner_or_admin(caller, self)?;
+		self.collection.permissions.mint_mode = Some(mode);
+		save(self);
+		Ok(())
+	}
 }
 
 fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<()> {
@@ -262,6 +298,14 @@
 	Ok(())
 }
 
+fn check_is_owner_or_admin<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<T::CrossAccountId> {
+	let caller = T::CrossAccountId::from_eth(caller);
+	collection
+		.check_is_owner_or_admin(&caller)
+		.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+	Ok(caller)
+}
+
 fn save<T: Config>(collection: &CollectionHandle<T>) {
 	<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());
 }
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -51,105 +51,6 @@
 	event MintingFinished();
 }
 
-// Selector: 3a54513b
-contract Collection is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		public
-	{
-		require(false, stub_error);
-		key;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) public {
-		require(false, stub_error);
-		key;
-		dummy = 0;
-	}
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		public
-		view
-		returns (bytes memory)
-	{
-		require(false, stub_error);
-		key;
-		dummy;
-		return hex"";
-	}
-
-	// Selector: ethSetSponsor(address) 8f9af356
-	function ethSetSponsor(address sponsor) public {
-		require(false, stub_error);
-		sponsor;
-		dummy = 0;
-	}
-
-	// Selector: ethConfirmSponsorship() a8580d1a
-	function ethConfirmSponsorship() public {
-		require(false, stub_error);
-		dummy = 0;
-	}
-
-	// Selector: setLimit(string,uint32) 68db30ca
-	function setLimit(string memory limit, uint32 value) public {
-		require(false, stub_error);
-		limit;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: setLimit(string,bool) ea67e4c2
-	function setLimit(string memory limit, bool value) public {
-		require(false, stub_error);
-		limit;
-		value;
-		dummy = 0;
-	}
-
-	// Selector: contractAddress() f6b4dfb4
-	function contractAddress() public view returns (address) {
-		require(false, stub_error);
-		dummy;
-		return 0x0000000000000000000000000000000000000000;
-	}
-
-	// Selector: addAdmin(address) 70480275
-	function addAdmin(address newAdmin) public view {
-		require(false, stub_error);
-		newAdmin;
-		dummy;
-	}
-
-	// Selector: removeAdmin(address) 1785f53c
-	function removeAdmin(address admin) public view {
-		require(false, stub_error);
-		admin;
-		dummy;
-	}
-
-	// Selector: setNesting(bool) e8fc50dd
-	function setNesting(bool enable) public {
-		require(false, stub_error);
-		enable;
-		dummy = 0;
-	}
-
-	// Selector: setNesting(bool,address[]) 7df12a9a
-	function setNesting(bool enable, address[] memory collections) public {
-		require(false, stub_error);
-		enable;
-		collections;
-		dummy = 0;
-	}
-}
-
 // Selector: 41369377
 contract TokenProperties is Dummy, ERC165 {
 	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
@@ -479,6 +380,133 @@
 	}
 }
 
+// Selector: f56cd7fa
+contract Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		public
+	{
+		require(false, stub_error);
+		key;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) public {
+		require(false, stub_error);
+		key;
+		dummy = 0;
+	}
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		public
+		view
+		returns (bytes memory)
+	{
+		require(false, stub_error);
+		key;
+		dummy;
+		return hex"";
+	}
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) public {
+		require(false, stub_error);
+		sponsor;
+		dummy = 0;
+	}
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() public {
+		require(false, stub_error);
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,uint32) 68db30ca
+	function setLimit(string memory limit, uint32 value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: setLimit(string,bool) ea67e4c2
+	function setLimit(string memory limit, bool value) public {
+		require(false, stub_error);
+		limit;
+		value;
+		dummy = 0;
+	}
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	// Selector: addAdmin(address) 70480275
+	function addAdmin(address newAdmin) public view {
+		require(false, stub_error);
+		newAdmin;
+		dummy;
+	}
+
+	// Selector: removeAdmin(address) 1785f53c
+	function removeAdmin(address admin) public view {
+		require(false, stub_error);
+		admin;
+		dummy;
+	}
+
+	// Selector: setNesting(bool) e8fc50dd
+	function setNesting(bool enable) public {
+		require(false, stub_error);
+		enable;
+		dummy = 0;
+	}
+
+	// Selector: setNesting(bool,address[]) 7df12a9a
+	function setNesting(bool enable, address[] memory collections) public {
+		require(false, stub_error);
+		enable;
+		collections;
+		dummy = 0;
+	}
+
+	// Selector: setAccess(string) 488f56aa
+	function setAccess(string memory mode) public {
+		require(false, stub_error);
+		mode;
+		dummy = 0;
+	}
+
+	// Selector: addToAllowList(address) 31f59102
+	function addToAllowList(address user) public view {
+		require(false, stub_error);
+		user;
+		dummy;
+	}
+
+	// Selector: removeFromAllowList(address) eba8dabc
+	function removeFromAllowList(address user) public view {
+		require(false, stub_error);
+		user;
+		dummy;
+	}
+
+	// Selector: setMintMode(bool) 5dea9bd5
+	function setMintMode(bool mode) public {
+		require(false, stub_error);
+		mode;
+		dummy = 0;
+	}
+}
+
 contract UniqueNFT is
 	Dummy,
 	ERC165,
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -42,51 +42,6 @@
 	event MintingFinished();
 }
 
-// Selector: 3a54513b
-interface Collection is Dummy, ERC165 {
-	// Selector: setCollectionProperty(string,bytes) 2f073f66
-	function setCollectionProperty(string memory key, bytes memory value)
-		external;
-
-	// Selector: deleteCollectionProperty(string) 7b7debce
-	function deleteCollectionProperty(string memory key) external;
-
-	// Throws error if key not found
-	//
-	// Selector: collectionProperty(string) cf24fd6d
-	function collectionProperty(string memory key)
-		external
-		view
-		returns (bytes memory);
-
-	// Selector: ethSetSponsor(address) 8f9af356
-	function ethSetSponsor(address sponsor) external;
-
-	// Selector: ethConfirmSponsorship() a8580d1a
-	function ethConfirmSponsorship() external;
-
-	// Selector: setLimit(string,uint32) 68db30ca
-	function setLimit(string memory limit, uint32 value) external;
-
-	// Selector: setLimit(string,bool) ea67e4c2
-	function setLimit(string memory limit, bool value) external;
-
-	// Selector: contractAddress() f6b4dfb4
-	function contractAddress() external view returns (address);
-
-	// Selector: addAdmin(address) 70480275
-	function addAdmin(address newAdmin) external view;
-
-	// Selector: removeAdmin(address) 1785f53c
-	function removeAdmin(address admin) external view;
-
-	// Selector: setNesting(bool) e8fc50dd
-	function setNesting(bool enable) external;
-
-	// Selector: setNesting(bool,address[]) 7df12a9a
-	function setNesting(bool enable, address[] memory collections) external;
-}
-
 // Selector: 41369377
 interface TokenProperties is Dummy, ERC165 {
 	// Selector: setTokenPropertyPermission(string,bool,bool,bool) 222d97fa
@@ -258,6 +213,63 @@
 		returns (bool);
 }
 
+// Selector: f56cd7fa
+interface Collection is Dummy, ERC165 {
+	// Selector: setCollectionProperty(string,bytes) 2f073f66
+	function setCollectionProperty(string memory key, bytes memory value)
+		external;
+
+	// Selector: deleteCollectionProperty(string) 7b7debce
+	function deleteCollectionProperty(string memory key) external;
+
+	// Throws error if key not found
+	//
+	// Selector: collectionProperty(string) cf24fd6d
+	function collectionProperty(string memory key)
+		external
+		view
+		returns (bytes memory);
+
+	// Selector: ethSetSponsor(address) 8f9af356
+	function ethSetSponsor(address sponsor) external;
+
+	// Selector: ethConfirmSponsorship() a8580d1a
+	function ethConfirmSponsorship() external;
+
+	// Selector: setLimit(string,uint32) 68db30ca
+	function setLimit(string memory limit, uint32 value) external;
+
+	// Selector: setLimit(string,bool) ea67e4c2
+	function setLimit(string memory limit, bool value) external;
+
+	// Selector: contractAddress() f6b4dfb4
+	function contractAddress() external view returns (address);
+
+	// Selector: addAdmin(address) 70480275
+	function addAdmin(address newAdmin) external view;
+
+	// Selector: removeAdmin(address) 1785f53c
+	function removeAdmin(address admin) external view;
+
+	// Selector: setNesting(bool) e8fc50dd
+	function setNesting(bool enable) external;
+
+	// Selector: setNesting(bool,address[]) 7df12a9a
+	function setNesting(bool enable, address[] memory collections) external;
+
+	// Selector: setAccess(string) 488f56aa
+	function setAccess(string memory mode) external;
+
+	// Selector: addToAllowList(address) 31f59102
+	function addToAllowList(address user) external view;
+
+	// Selector: removeFromAllowList(address) eba8dabc
+	function removeFromAllowList(address user) external view;
+
+	// Selector: setMintMode(bool) 5dea9bd5
+	function setMintMode(bool mode) external;
+}
+
 interface UniqueNFT is
 	Dummy,
 	ERC165,
modifiedtests/src/eth/contractSponsoring.test.tsdiffbeforeafterboth
before · tests/src/eth/contractSponsoring.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 {expect} from 'chai';18import {19  contractHelpers,20  createEthAccountWithBalance,21  transferBalanceToEth,22  deployFlipper,23  itWeb3,24  SponsoringMode,25  createEthAccount,26  collectionIdToAddress,27  GAS_ARGS,28  normalizeEvents,29  subToEth,30  executeEthTxOnSub,31  evmCollectionHelpers,32  getCollectionAddressFromResult,33  evmCollection,34} from './util/helpers';35import {36  addCollectionAdminExpectSuccess,37  createCollectionExpectSuccess,38  getDetailedCollectionInfo,39  transferBalanceTo,40} from '../util/helpers';41import nonFungibleAbi from './nonFungibleAbi.json';42import getBalance from '../substrate/get-balance';43import {evmToAddress} from '@polkadot/util-crypto';4445describe('Sponsoring EVM contracts', () => {46  itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3}) => {47    const owner = await createEthAccountWithBalance(api, web3);48    const flipper = await deployFlipper(web3, owner);49    const helpers = contractHelpers(web3, owner);50    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;51    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});52    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;53  });5455  itWeb3('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3}) => {56    const owner = await createEthAccountWithBalance(api, web3);57    const notOwner = await createEthAccountWithBalance(api, web3);58    const flipper = await deployFlipper(web3, owner);59    const helpers = contractHelpers(web3, owner);60    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;61    await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).send({from: notOwner})).to.rejected;62    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;63  });6465  itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {66    const alice = privateKeyWrapper('//Alice');6768    const owner = await createEthAccountWithBalance(api, web3);69    const caller = await createEthAccountWithBalance(api, web3);7071    const flipper = await deployFlipper(web3, owner);7273    const helpers = contractHelpers(web3, owner);7475    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;76    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});77    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});78    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;7980    await transferBalanceToEth(api, alice, flipper.options.address);8182    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);83    expect(originalFlipperBalance).to.be.not.equal('0');8485    await flipper.methods.flip().send({from: caller});86    expect(await flipper.methods.getValue().call()).to.be.true;8788    // Balance should be taken from flipper instead of caller89    const balanceAfter = await web3.eth.getBalance(flipper.options.address);90    expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);91  });9293  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {94    const alice = privateKeyWrapper('//Alice');9596    const owner = await createEthAccountWithBalance(api, web3);97    const caller = createEthAccount(web3);9899    const flipper = await deployFlipper(web3, owner);100101    const helpers = contractHelpers(web3, owner);102    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});103    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});104105    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;106    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});107    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});108    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;109110    await transferBalanceToEth(api, alice, flipper.options.address);111112    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);113    expect(originalFlipperBalance).to.be.not.equal('0');114115    await flipper.methods.flip().send({from: caller});116    expect(await flipper.methods.getValue().call()).to.be.true;117118    // Balance should be taken from flipper instead of caller119    const balanceAfter = await web3.eth.getBalance(flipper.options.address);120    expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);121  });122123  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {124    const alice = privateKeyWrapper('//Alice');125126    const owner = await createEthAccountWithBalance(api, web3);127    const caller = createEthAccount(web3);128129    const flipper = await deployFlipper(web3, owner);130131    const helpers = contractHelpers(web3, owner);132133    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;134    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});135    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});136    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;137138    await transferBalanceToEth(api, alice, flipper.options.address);139140    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);141    expect(originalFlipperBalance).to.be.not.equal('0');142143    await expect(flipper.methods.flip().send({from: caller})).to.be.rejectedWith(/InvalidTransaction::Payment/);144    expect(await flipper.methods.getValue().call()).to.be.false;145146    // Balance should be taken from flipper instead of caller147    const balanceAfter = await web3.eth.getBalance(flipper.options.address);148    expect(+balanceAfter).to.be.equals(+originalFlipperBalance);149  });150151  itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {152    const alice = privateKeyWrapper('//Alice');153154    const owner = await createEthAccountWithBalance(api, web3);155    const caller = await createEthAccountWithBalance(api, web3);156    const originalCallerBalance = await web3.eth.getBalance(caller);157158    const flipper = await deployFlipper(web3, owner);159160    const helpers = contractHelpers(web3, owner);161    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});162    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});163164    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;165    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});166    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});167    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;168169    await transferBalanceToEth(api, alice, flipper.options.address);170171    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);172    expect(originalFlipperBalance).to.be.not.equal('0');173174    await flipper.methods.flip().send({from: caller});175    expect(await flipper.methods.getValue().call()).to.be.true;176177    expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);178  });179180  itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {181    const alice = privateKeyWrapper('//Alice');182183    const owner = await createEthAccountWithBalance(api, web3);184    const caller = await createEthAccountWithBalance(api, web3);185    const originalCallerBalance = await web3.eth.getBalance(caller);186187    const flipper = await deployFlipper(web3, owner);188189    const helpers = contractHelpers(web3, owner);190    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});191    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});192193    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;194    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});195    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});196    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;197198    await transferBalanceToEth(api, alice, flipper.options.address);199200    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);201    expect(originalFlipperBalance).to.be.not.equal('0');202203    await flipper.methods.flip().send({from: caller});204    expect(await flipper.methods.getValue().call()).to.be.true;205    expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);206207    const newFlipperBalance = await web3.eth.getBalance(flipper.options.address);208    expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);209210    await flipper.methods.flip().send({from: caller});211    expect(await web3.eth.getBalance(flipper.options.address)).to.be.equal(newFlipperBalance);212    expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);213  });214215  // TODO: Find a way to calculate default rate limit216  itWeb3('Default rate limit equals 7200', async ({api, web3}) => {217    const owner = await createEthAccountWithBalance(api, web3);218    const flipper = await deployFlipper(web3, owner);219    const helpers = contractHelpers(web3, owner);220    expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');221  });222223  //TODO: CORE-302 add eth methods224  itWeb3.skip('Sponsoring evm address from substrate collection', async ({api, web3}) => {225    const owner = await createEthAccountWithBalance(api, web3);226    const collectionHelpers = evmCollectionHelpers(web3, owner);227    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();228    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);229    const sponsor = await createEthAccountWithBalance(api, web3);230    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);231    result = await collectionEvm.methods.ethSetSponsor(sponsor).send();232    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;233    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;234    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));235    await expect(collectionEvm.methods.ethConfirmSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');236237    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);238    await sponsorCollection.methods.ethConfirmSponsorship().send();239    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;240    expect(collectionSub.sponsorship.isConfirmed).to.be.true;241    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));242243    const user = createEthAccount(web3);244    const userContract = evmCollection(web3, user, collectionIdAddress);245    const nextTokenId = await userContract.methods.nextTokenId().call();246247    expect(nextTokenId).to.be.equal('1');248    await expect(userContract.methods.mintWithTokenURI(249      user,250      nextTokenId,251      'Test URI',252    ).call()).to.be.rejectedWith('PublicMintingNotAllowed');253    254    // TODO: add this methods to eth255    // {256    //   const tx = api.tx.unique.setPublicAccessMode(collectionId, 'AllowList');257    //   const events = await submitTransactionAsync(owner, tx);258    //   const result = getCreateCollectionResult(events);259    //   expect(result.success).to.be.true;260    // }261    // {262    //   const tx = api.tx.unique.addToAllowList(collectionId, {Ethereum: userEth});263    //   const events = await submitTransactionAsync(owner, tx);264    //   const result = getCreateCollectionResult(events);265    //   expect(result.success).to.be.true;266    // }267    // {268    //   const tx = api.tx.unique.setMintPermission(collectionId, true);269    //   const events = await submitTransactionAsync(owner, tx);270    //   const result = getCreateCollectionResult(events);271    //   expect(result.success).to.be.true;272    // }273274    // const [alicesBalanceBefore] = await getBalance(api, [alicesPublicKey]);275276    {277      const nextTokenId = await userContract.methods.nextTokenId().call();278      expect(nextTokenId).to.be.equal('1');279      const result = await userContract.methods.mintWithTokenURI(280        user,281        nextTokenId,282        'Test URI',283      ).send();284      const events = normalizeEvents(result.events);285286      expect(events).to.be.deep.equal([287        {288          collectionIdAddress,289          event: 'Transfer',290          args: {291            from: '0x0000000000000000000000000000000000000000',292            to: user,293            tokenId: nextTokenId,294          },295        },296      ]);297298      expect(await userContract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');299    }300  });301302  //TODO: CORE-302 add eth methods303  itWeb3.skip('Check that transaction via EVM spend money from substrate address', async ({api, web3, privateKeyWrapper}) => {304    const owner = privateKeyWrapper('//Alice');305    const user = privateKeyWrapper(`//User/${Date.now()}`);306    const userEth = subToEth(user.address);307    const collectionId = await createCollectionExpectSuccess();308    await addCollectionAdminExpectSuccess(owner, collectionId, {Ethereum: userEth});309    await transferBalanceTo(api, owner, user.address);310311    const address = collectionIdToAddress(collectionId);312    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: userEth, ...GAS_ARGS});313314    const [userBalanceBefore] = await getBalance(api, [user.address]);315316    {317      const nextTokenId = await contract.methods.nextTokenId().call();318      expect(nextTokenId).to.be.equal('1');319      await executeEthTxOnSub(web3, api, user, contract, m => m.mintWithTokenURI(320        userEth,321        nextTokenId,322        'Test URI',323      ));324325      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');326    }327328    const [userBalanceAfter] = await getBalance(api, [user.address]);329    expect(userBalanceAfter < userBalanceBefore).to.be.true;330  });331});
after · tests/src/eth/contractSponsoring.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 {expect} from 'chai';18import {19  contractHelpers,20  createEthAccountWithBalance,21  transferBalanceToEth,22  deployFlipper,23  itWeb3,24  SponsoringMode,25  createEthAccount,26  collectionIdToAddress,27  GAS_ARGS,28  normalizeEvents,29  subToEth,30  executeEthTxOnSub,31  evmCollectionHelpers,32  getCollectionAddressFromResult,33  evmCollection,34  ethBalanceViaSub,35} from './util/helpers';36import {37  addCollectionAdminExpectSuccess,38  createCollectionExpectSuccess,39  getDetailedCollectionInfo,40  transferBalanceTo,41} from '../util/helpers';42import nonFungibleAbi from './nonFungibleAbi.json';43import getBalance from '../substrate/get-balance';44import {evmToAddress} from '@polkadot/util-crypto';4546describe('Sponsoring EVM contracts', () => {47  itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3}) => {48    const owner = await createEthAccountWithBalance(api, web3);49    const flipper = await deployFlipper(web3, owner);50    const helpers = contractHelpers(web3, owner);51    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;52    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});53    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;54  });5556  itWeb3('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3}) => {57    const owner = await createEthAccountWithBalance(api, web3);58    const notOwner = await createEthAccountWithBalance(api, web3);59    const flipper = await deployFlipper(web3, owner);60    const helpers = contractHelpers(web3, owner);61    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;62    await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).send({from: notOwner})).to.rejected;63    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;64  });6566  itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => {67    const alice = privateKeyWrapper('//Alice');6869    const owner = await createEthAccountWithBalance(api, web3);70    const caller = await createEthAccountWithBalance(api, web3);7172    const flipper = await deployFlipper(web3, owner);7374    const helpers = contractHelpers(web3, owner);7576    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;77    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner});78    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});79    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;8081    await transferBalanceToEth(api, alice, flipper.options.address);8283    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);84    expect(originalFlipperBalance).to.be.not.equal('0');8586    await flipper.methods.flip().send({from: caller});87    expect(await flipper.methods.getValue().call()).to.be.true;8889    // Balance should be taken from flipper instead of caller90    const balanceAfter = await web3.eth.getBalance(flipper.options.address);91    expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);92  });9394  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => {95    const alice = privateKeyWrapper('//Alice');9697    const owner = await createEthAccountWithBalance(api, web3);98    const caller = createEthAccount(web3);99100    const flipper = await deployFlipper(web3, owner);101102    const helpers = contractHelpers(web3, owner);103    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});104    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});105106    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;107    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});108    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});109    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;110111    await transferBalanceToEth(api, alice, flipper.options.address);112113    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);114    expect(originalFlipperBalance).to.be.not.equal('0');115116    await flipper.methods.flip().send({from: caller});117    expect(await flipper.methods.getValue().call()).to.be.true;118119    // Balance should be taken from flipper instead of caller120    const balanceAfter = await web3.eth.getBalance(flipper.options.address);121    expect(+balanceAfter).to.be.lessThan(+originalFlipperBalance);122  });123124  itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => {125    const alice = privateKeyWrapper('//Alice');126127    const owner = await createEthAccountWithBalance(api, web3);128    const caller = createEthAccount(web3);129130    const flipper = await deployFlipper(web3, owner);131132    const helpers = contractHelpers(web3, owner);133134    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;135    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});136    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});137    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;138139    await transferBalanceToEth(api, alice, flipper.options.address);140141    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);142    expect(originalFlipperBalance).to.be.not.equal('0');143144    await expect(flipper.methods.flip().send({from: caller})).to.be.rejectedWith(/InvalidTransaction::Payment/);145    expect(await flipper.methods.getValue().call()).to.be.false;146147    // Balance should be taken from flipper instead of caller148    const balanceAfter = await web3.eth.getBalance(flipper.options.address);149    expect(+balanceAfter).to.be.equals(+originalFlipperBalance);150  });151152  itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => {153    const alice = privateKeyWrapper('//Alice');154155    const owner = await createEthAccountWithBalance(api, web3);156    const caller = await createEthAccountWithBalance(api, web3);157    const originalCallerBalance = await web3.eth.getBalance(caller);158159    const flipper = await deployFlipper(web3, owner);160161    const helpers = contractHelpers(web3, owner);162    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});163    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});164165    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;166    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});167    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner});168    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;169170    await transferBalanceToEth(api, alice, flipper.options.address);171172    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);173    expect(originalFlipperBalance).to.be.not.equal('0');174175    await flipper.methods.flip().send({from: caller});176    expect(await flipper.methods.getValue().call()).to.be.true;177178    expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);179  });180181  itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => {182    const alice = privateKeyWrapper('//Alice');183184    const owner = await createEthAccountWithBalance(api, web3);185    const caller = await createEthAccountWithBalance(api, web3);186    const originalCallerBalance = await web3.eth.getBalance(caller);187188    const flipper = await deployFlipper(web3, owner);189190    const helpers = contractHelpers(web3, owner);191    await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner});192    await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner});193194    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false;195    await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner});196    await helpers.methods.setSponsoringRateLimit(flipper.options.address, 10).send({from: owner});197    expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true;198199    await transferBalanceToEth(api, alice, flipper.options.address);200201    const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address);202    expect(originalFlipperBalance).to.be.not.equal('0');203204    await flipper.methods.flip().send({from: caller});205    expect(await flipper.methods.getValue().call()).to.be.true;206    expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance);207208    const newFlipperBalance = await web3.eth.getBalance(flipper.options.address);209    expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance);210211    await flipper.methods.flip().send({from: caller});212    expect(await web3.eth.getBalance(flipper.options.address)).to.be.equal(newFlipperBalance);213    expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance);214  });215216  // TODO: Find a way to calculate default rate limit217  itWeb3('Default rate limit equals 7200', async ({api, web3}) => {218    const owner = await createEthAccountWithBalance(api, web3);219    const flipper = await deployFlipper(web3, owner);220    const helpers = contractHelpers(web3, owner);221    expect(await helpers.methods.getSponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200');222  });223224  //TODO: CORE-302 add eth methods225  itWeb3('Sponsoring collection from evm address via access list', async ({api, web3}) => {226    const owner = await createEthAccountWithBalance(api, web3);227    const collectionHelpers = evmCollectionHelpers(web3, owner);228    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();229    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);230    const sponsor = await createEthAccountWithBalance(api, web3);231    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);232    result = await collectionEvm.methods.ethSetSponsor(sponsor).send({from: owner});233    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;234    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;235    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));236    await expect(collectionEvm.methods.ethConfirmSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');237238    await collectionEvm.methods.ethConfirmSponsorship().send({from: sponsor});239    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;240    expect(collectionSub.sponsorship.isConfirmed).to.be.true;241    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));242243    const user = createEthAccount(web3);244    const nextTokenId = await collectionEvm.methods.nextTokenId().call();245    expect(nextTokenId).to.be.equal('1');246247    const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();248    expect(oldPermissions.mintMode).to.be.false;249    expect(oldPermissions.access).to.be.equal('Normal');250251    await collectionEvm.methods.setAccess('AllowList').send({from: owner});252    await collectionEvm.methods.addToAllowList(user).send({from: owner});253    await collectionEvm.methods.setMintMode(true).send({from: owner});254255    const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman();256    expect(newPermissions.mintMode).to.be.true;257    expect(newPermissions.access).to.be.equal('AllowList');258259    // const [alicesBalanceBefore] = await getBalance(api, [alicesPublicKey]);260261    {262      const nextTokenId = await collectionEvm.methods.nextTokenId().call();263      expect(nextTokenId).to.be.equal('1');264      const result = await collectionEvm.methods.mintWithTokenURI(265        user,266        nextTokenId,267        'Test URI',268      ).call({from: user});269      console.log(result);270      const events = normalizeEvents(result.events);271272      expect(events).to.be.deep.equal([273        {274          collectionIdAddress,275          event: 'Transfer',276          args: {277            from: '0x0000000000000000000000000000000000000000',278            to: user,279            tokenId: nextTokenId,280          },281        },282      ]);283284      expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');285    }286  });287288  itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3}) => {289    const owner = await createEthAccountWithBalance(api, web3);290    const collectionHelpers = evmCollectionHelpers(web3, owner);291    let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send();292    const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result);293    const sponsor = await createEthAccountWithBalance(api, web3);294    const collectionEvm = evmCollection(web3, owner, collectionIdAddress);295    result = await collectionEvm.methods.ethSetSponsor(sponsor).send();296    let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;297    expect(collectionSub.sponsorship.isUnconfirmed).to.be.true;298    expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));299    await expect(collectionEvm.methods.ethConfirmSponsorship().call()).to.be.rejectedWith('Caller is not set as sponsor');300    const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress);301    await sponsorCollection.methods.ethConfirmSponsorship().send();302    collectionSub = (await getDetailedCollectionInfo(api, collectionId))!;303    expect(collectionSub.sponsorship.isConfirmed).to.be.true;304    expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor));305306    const user = createEthAccount(web3);307    await collectionEvm.methods.addAdmin(user).send();308    309    const ownerBalanceBefore = await ethBalanceViaSub(api, owner);310    const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor);311    312  313    const userCollectionEvm = evmCollection(web3, user, collectionIdAddress);314    const nextTokenId = await userCollectionEvm.methods.nextTokenId().call();315    expect(nextTokenId).to.be.equal('1');316    result = await userCollectionEvm.methods.mintWithTokenURI(317      user,318      nextTokenId,319      'Test URI',320    ).send();321322    const events = normalizeEvents(result.events);323    const address = collectionIdToAddress(collectionId);324325    expect(events).to.be.deep.equal([326      {327        address,328        event: 'Transfer',329        args: {330          from: '0x0000000000000000000000000000000000000000',331          to: user,332          tokenId: nextTokenId,333        },334      },335    ]);336    expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');337  338    const ownerBalanceAfter = await ethBalanceViaSub(api, owner);339    expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore);340    const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor);341    expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true;342  });343});
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -91,6 +91,15 @@
   },
   {
     "inputs": [
+      { "internalType": "address", "name": "user", "type": "address" }
+    ],
+    "name": "addToAllowList",
+    "outputs": [],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "approved", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
     ],
@@ -300,6 +309,15 @@
   },
   {
     "inputs": [
+      { "internalType": "address", "name": "user", "type": "address" }
+    ],
+    "name": "removeFromAllowList",
+    "outputs": [],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "from", "type": "address" },
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "uint256", "name": "tokenId", "type": "uint256" }
@@ -322,6 +340,13 @@
     "type": "function"
   },
   {
+    "inputs": [{ "internalType": "string", "name": "mode", "type": "string" }],
+    "name": "setAccess",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [
       { "internalType": "address", "name": "operator", "type": "address" },
       { "internalType": "bool", "name": "approved", "type": "bool" }
@@ -362,6 +387,13 @@
     "type": "function"
   },
   {
+    "inputs": [{ "internalType": "bool", "name": "mode", "type": "bool" }],
+    "name": "setMintMode",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [
       { "internalType": "bool", "name": "enable", "type": "bool" },
       {