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

difftreelog

feature/mint-for-fungible-token

Grigoriy Simonov2022-08-18parent: #eadc594.patch.diff
in: master

5 files changed

modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -129,7 +129,23 @@
 	}
 }
 
-#[solidity_interface(name = ERC20UniqueExtensions)]
+#[solidity_interface(name = "ERC20Mintable")]
+impl<T: Config> FungibleHandle<T> {
+	#[weight(<SelfWeightOf<T>>::create_item())]
+	fn mint(&mut self, caller: caller, to: address, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+		<Pallet<T>>::create_item(&self, &caller, (to, amount), &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+}
+
+#[solidity_interface(name = "ERC20UniqueExtensions")]
 impl<T: Config> FungibleHandle<T> {
 	#[weight(<SelfWeightOf<T>>::burn_from())]
 	fn burn_from(&mut self, caller: caller, from: address, amount: uint256) -> Result<bool> {
@@ -144,12 +160,29 @@
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
+
+	#[weight(<SelfWeightOf<T>>::create_multiple_items_ex(amounts.len() as u32))]
+	fn mint_bulk(&mut self, caller: caller, amounts: Vec<(address, uint256)>) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+		let amounts = amounts
+			.into_iter()
+			.map(|(to, amount)| Ok((T::CrossAccountId::from_eth(to), amount.try_into().map_err(|_| "amount overflow")?)))
+			.collect::<Result<_>>()?;
+
+		<Pallet<T>>::create_multiple_items(&self, &caller, amounts, &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
 }
 
 #[solidity_interface(
 	name = UniqueFungible,
 	is(
 		ERC20,
+		ERC20Mintable,
 		ERC20UniqueExtensions,
 		Collection(common_mut, CollectionHandle<T>),
 	)
modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -3,7 +3,13 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
-/// @dev common stubs holder
+// Anonymous struct
+struct Tuple0 {
+	address field_0;
+	uint256 field_1;
+}
+
+// Common stubs holder
 contract Dummy {
 	uint8 dummy;
 	string stub_error = "this contract is implemented in native";
@@ -21,8 +27,49 @@
 	}
 }
 
-/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0xe54be640
+// Inline
+contract ERC20Events {
+	event Transfer(address indexed from, address indexed to, uint256 value);
+	event Approval(
+		address indexed owner,
+		address indexed spender,
+		uint256 value
+	);
+}
+
+// Selector: 40c10f19
+contract ERC20Mintable is Dummy, ERC165 {
+	// Selector: mint(address,uint256) 40c10f19
+	function mint(address to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+}
+
+// Selector: 63034ac5
+contract ERC20UniqueExtensions is Dummy, ERC165 {
+	// Selector: burnFrom(address,uint256) 79cc6790
+	function burnFrom(address from, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		from;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
+	// Selector: mintBulk((address,uint256)[]) 1acf2d55
+	function mintBulk(Tuple0[] memory amounts) public returns (bool) {
+		require(false, stub_error);
+		amounts;
+		dummy = 0;
+		return false;
+	}
+}
+
+// Selector: 6cf113cd
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -476,6 +523,7 @@
 	Dummy,
 	ERC165,
 	ERC20,
+	ERC20Mintable,
 	ERC20UniqueExtensions,
 	Collection
 {}
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
33
4pragma solidity >=0.8.0 <0.9.0;4pragma solidity >=0.8.0 <0.9.0;
5
6// Anonymous struct
7struct Tuple0 {
8 address field_0;
9 uint256 field_1;
10}
511
6/// @dev common stubs holder12// Common stubs holder
7interface Dummy {13interface Dummy {
814
9}15}
12 function supportsInterface(bytes4 interfaceID) external view returns (bool);18 function supportsInterface(bytes4 interfaceID) external view returns (bool);
13}19}
1420
15/// @title A contract that allows you to work with collections.21// Inline
16/// @dev the ERC-165 identifier for this interface is 0xe54be64022interface ERC20Events {
23 event Transfer(address indexed from, address indexed to, uint256 value);
24 event Approval(
25 address indexed owner,
26 address indexed spender,
27 uint256 value
28 );
29}
30
31// Selector: 40c10f19
32interface ERC20Mintable is Dummy, ERC165 {
33 // Selector: mint(address,uint256) 40c10f19
34 function mint(address to, uint256 amount) external returns (bool);
35}
36
37// Selector: 63034ac5
38interface ERC20UniqueExtensions is Dummy, ERC165 {
39 // Selector: burnFrom(address,uint256) 79cc6790
40 function burnFrom(address from, uint256 amount) external returns (bool);
41
42 // Selector: mintBulk((address,uint256)[]) 1acf2d55
43 function mintBulk(Tuple0[] memory amounts) external returns (bool);
44}
45
46// Selector: 6cf113cd
17interface Collection is Dummy, ERC165 {47interface Collection is Dummy, ERC165 {
18 /// Set collection property.48 /// Set collection property.
19 ///49 ///
208 /// or in textual repr: uniqueCollectionType()238 /// or in textual repr: uniqueCollectionType()
209 function uniqueCollectionType() external returns (string memory);239 function uniqueCollectionType() external returns (string memory);
210
211 /// Changes collection owner to another account
212 ///
213 /// @dev Owner can be changed only by current owner
214 /// @param newOwner new owner account
215 /// @dev EVM selector for this function is: 0x13af4035,
216 /// or in textual repr: setOwner(address)
217 function setOwner(address newOwner) external;
218
219 /// Changes collection owner to another substrate account
220 ///
221 /// @dev Owner can be changed only by current owner
222 /// @param newOwner new owner substrate account
223 /// @dev EVM selector for this function is: 0xb212138f,
224 /// or in textual repr: setOwnerSubstrate(uint256)
225 function setOwnerSubstrate(uint256 newOwner) external;
226}240}
227
228/// @dev anonymous struct
229struct Tuple6 {
230 address field_0;
231 uint256 field_1;
232}
233241
234/// @dev the ERC-165 identifier for this interface is 0x79cc6790242/// @dev the ERC-165 identifier for this interface is 0x79cc6790
235interface ERC20UniqueExtensions is Dummy, ERC165 {243interface ERC20UniqueExtensions is Dummy, ERC165 {
298 Dummy,306 Dummy,
299 ERC165,307 ERC165,
300 ERC20,308 ERC20,
309 ERC20Mintable,
301 ERC20UniqueExtensions,310 ERC20UniqueExtensions,
302 Collection311 Collection
303{}312{}
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -14,10 +14,11 @@
 // You should have received a copy of the GNU General Public License
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
-import {approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';
-import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';
+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';
 
 describe('Fungible: Information getting', () => {
   itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {
@@ -58,6 +59,129 @@
 });
 
 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},
+    });
+
+    const receiver = createEthAccount(web3);
+
+    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 collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});
+    const result = await collectionContract.methods.mint(receiver, 100).send();
+    const events = normalizeEvents(result.events);
+    
+    expect(events).to.be.deep.equal([
+      {
+        address: collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver,
+          value: '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);
+
+    const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});
+    const result = await collectionContract.methods.mintBulk([
+      [receiver1, 10],
+      [receiver2, 20],
+      [receiver3, 30],
+    ]).call();
+    console.log(result);
+    const events = normalizeEvents(result.events);
+
+    expect(events).to.be.deep.equal([
+      {
+        address:collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver1,
+          value: '10',
+        },
+      },
+      {
+        address:collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver2,
+          value: '20',
+        },
+      },
+      {
+        address:collectionIdAddress,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: receiver3,
+          value: '30',
+        },
+      },
+    ]);
+  });
+
+  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);
+
+    const collectionIdAddress = collectionIdToAddress(collection.collectionId);
+    const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});
+    await collectionContract.methods.mint(receiver, 100).send();
+
+    const result = await collectionContract.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 balance = await collectionContract.methods.balanceOf(receiver).call();
+    expect(balance).to.equal('51');
+  });
+
   itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {
     const collection = await createCollectionExpectSuccess({
       name: 'token name',
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -151,6 +151,33 @@
     "type": "function"
   },
   {
+    "inputs": [
+      { "internalType": "address", "name": "to", "type": "address" },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "mint",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple0[]",
+        "name": "amounts",
+        "type": "tuple[]"
+      }
+    ],
+    "name": "mintBulk",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "getCollectionSponsor",
     "outputs": [