difftreelog
feature/mint-for-fungible-token
in: master
5 files changed
pallets/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>),
)
pallets/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
{}
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/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
interface Dummy {
}
@@ -12,8 +18,32 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
-/// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0xe54be640
+// Inline
+interface ERC20Events {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(
+ address indexed owner,
+ address indexed spender,
+ uint256 value
+ );
+}
+
+// Selector: 40c10f19
+interface ERC20Mintable is Dummy, ERC165 {
+ // Selector: mint(address,uint256) 40c10f19
+ function mint(address to, uint256 amount) external returns (bool);
+}
+
+// Selector: 63034ac5
+interface ERC20UniqueExtensions is Dummy, ERC165 {
+ // Selector: burnFrom(address,uint256) 79cc6790
+ function burnFrom(address from, uint256 amount) external returns (bool);
+
+ // Selector: mintBulk((address,uint256)[]) 1acf2d55
+ function mintBulk(Tuple0[] memory amounts) external returns (bool);
+}
+
+// Selector: 6cf113cd
interface Collection is Dummy, ERC165 {
/// Set collection property.
///
@@ -207,30 +237,8 @@
/// @dev EVM selector for this function is: 0xd34b55b8,
/// or in textual repr: uniqueCollectionType()
function uniqueCollectionType() external returns (string memory);
-
- /// Changes collection owner to another account
- ///
- /// @dev Owner can be changed only by current owner
- /// @param newOwner new owner account
- /// @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
-struct Tuple6 {
- address field_0;
- uint256 field_1;
-}
-
/// @dev the ERC-165 identifier for this interface is 0x79cc6790
interface ERC20UniqueExtensions is Dummy, ERC165 {
/// @dev EVM selector for this function is: 0x79cc6790,
@@ -298,6 +306,7 @@
Dummy,
ERC165,
ERC20,
+ ERC20Mintable,
ERC20UniqueExtensions,
Collection
{}
tests/src/eth/fungible.test.tsdiffbeforeafterboth1// 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 {approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';19import fungibleAbi from './fungibleAbi.json';20import {expect} from 'chai';2122describe('Fungible: Information getting', () => {23 itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {24 const collection = await createCollectionExpectSuccess({25 name: 'token name',26 mode: {type: 'Fungible', decimalPoints: 0},27 });28 const alice = privateKeyWrapper('//Alice');2930 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);3132 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Substrate: alice.address});3334 const address = collectionIdToAddress(collection);35 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS});36 const totalSupply = await contract.methods.totalSupply().call();3738 expect(totalSupply).to.equal('200');39 });4041 itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => {42 const collection = await createCollectionExpectSuccess({43 name: 'token name',44 mode: {type: 'Fungible', decimalPoints: 0},45 });46 const alice = privateKeyWrapper('//Alice');4748 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);4950 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: caller});5152 const address = collectionIdToAddress(collection);53 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS});54 const balance = await contract.methods.balanceOf(caller).call();5556 expect(balance).to.equal('200');57 });58});5960describe('Fungible: Plain calls', () => {61 itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {62 const collection = await createCollectionExpectSuccess({63 name: 'token name',64 mode: {type: 'Fungible', decimalPoints: 0},65 });66 const alice = privateKeyWrapper('//Alice');6768 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);6970 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});7172 const spender = createEthAccount(web3);7374 const address = collectionIdToAddress(collection);75 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});7677 {78 const result = await contract.methods.approve(spender, 100).send({from: owner});79 const events = normalizeEvents(result.events);8081 expect(events).to.be.deep.equal([82 {83 address,84 event: 'Approval',85 args: {86 owner,87 spender,88 value: '100',89 },90 },91 ]);92 }9394 {95 const allowance = await contract.methods.allowance(owner, spender).call();96 expect(+allowance).to.equal(100);97 }98 });99100 itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => {101 const collection = await createCollectionExpectSuccess({102 name: 'token name',103 mode: {type: 'Fungible', decimalPoints: 0},104 });105 const alice = privateKeyWrapper('//Alice');106107 const owner = createEthAccount(web3);108 await transferBalanceToEth(api, alice, owner);109110 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});111112 const spender = createEthAccount(web3);113 await transferBalanceToEth(api, alice, spender);114115 const receiver = createEthAccount(web3);116117 const address = collectionIdToAddress(collection);118 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});119120 await contract.methods.approve(spender, 100).send();121122 {123 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});124 const events = normalizeEvents(result.events);125 expect(events).to.be.deep.equal([126 {127 address,128 event: 'Transfer',129 args: {130 from: owner,131 to: receiver,132 value: '49',133 },134 },135 {136 address,137 event: 'Approval',138 args: {139 owner,140 spender,141 value: '51',142 },143 },144 ]);145 }146147 {148 const balance = await contract.methods.balanceOf(receiver).call();149 expect(+balance).to.equal(49);150 }151152 {153 const balance = await contract.methods.balanceOf(owner).call();154 expect(+balance).to.equal(151);155 }156 });157158 itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => {159 const collection = await createCollectionExpectSuccess({160 name: 'token name',161 mode: {type: 'Fungible', decimalPoints: 0},162 });163 const alice = privateKeyWrapper('//Alice');164165 const owner = createEthAccount(web3);166 await transferBalanceToEth(api, alice, owner);167168 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});169170 const receiver = createEthAccount(web3);171 await transferBalanceToEth(api, alice, receiver);172173 const address = collectionIdToAddress(collection);174 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});175176 {177 const result = await contract.methods.transfer(receiver, 50).send({from: owner});178 const events = normalizeEvents(result.events);179 expect(events).to.be.deep.equal([180 {181 address,182 event: 'Transfer',183 args: {184 from: owner,185 to: receiver,186 value: '50',187 },188 },189 ]);190 }191192 {193 const balance = await contract.methods.balanceOf(owner).call();194 expect(+balance).to.equal(150);195 }196197 {198 const balance = await contract.methods.balanceOf(receiver).call();199 expect(+balance).to.equal(50);200 }201 });202});203204describe('Fungible: Fees', () => {205 itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {206 const collection = await createCollectionExpectSuccess({207 mode: {type: 'Fungible', decimalPoints: 0},208 });209 const alice = privateKeyWrapper('//Alice');210211 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);212 const spender = createEthAccount(web3);213214 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});215216 const address = collectionIdToAddress(collection);217 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});218219 const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, 100).send({from: owner}));220 expect(cost < BigInt(0.2 * Number(UNIQUE)));221 });222223 itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {224 const collection = await createCollectionExpectSuccess({225 mode: {type: 'Fungible', decimalPoints: 0},226 });227 const alice = privateKeyWrapper('//Alice');228229 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);230 const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper);231232 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});233234 const address = collectionIdToAddress(collection);235 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});236237 await contract.methods.approve(spender, 100).send({from: owner});238239 const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));240 expect(cost < BigInt(0.2 * Number(UNIQUE)));241 });242243 itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {244 const collection = await createCollectionExpectSuccess({245 mode: {type: 'Fungible', decimalPoints: 0},246 });247 const alice = privateKeyWrapper('//Alice');248249 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);250 const receiver = createEthAccount(web3);251252 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});253254 const address = collectionIdToAddress(collection);255 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});256257 const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));258 expect(cost < BigInt(0.2 * Number(UNIQUE)));259 });260});261262describe('Fungible: Substrate calls', () => {263 itWeb3('Events emitted for approve()', async ({web3, privateKeyWrapper}) => {264 const collection = await createCollectionExpectSuccess({265 mode: {type: 'Fungible', decimalPoints: 0},266 });267 const alice = privateKeyWrapper('//Alice');268269 const receiver = createEthAccount(web3);270271 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n});272273 const address = collectionIdToAddress(collection);274 const contract = new web3.eth.Contract(fungibleAbi as any, address);275276 const events = await recordEvents(contract, async () => {277 await approveExpectSuccess(collection, 0, alice, {Ethereum: receiver}, 100);278 });279280 expect(events).to.be.deep.equal([281 {282 address,283 event: 'Approval',284 args: {285 owner: subToEth(alice.address),286 spender: receiver,287 value: '100',288 },289 },290 ]);291 });292293 itWeb3('Events emitted for transferFrom()', async ({web3, privateKeyWrapper}) => {294 const collection = await createCollectionExpectSuccess({295 mode: {type: 'Fungible', decimalPoints: 0},296 });297 const alice = privateKeyWrapper('//Alice');298 const bob = privateKeyWrapper('//Bob');299300 const receiver = createEthAccount(web3);301302 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n});303 await approveExpectSuccess(collection, 0, alice, bob.address, 100);304305 const address = collectionIdToAddress(collection);306 const contract = new web3.eth.Contract(fungibleAbi as any, address);307308 const events = await recordEvents(contract, async () => {309 await transferFromExpectSuccess(collection, 0, bob, alice, {Ethereum: receiver}, 51, 'Fungible');310 });311312 expect(events).to.be.deep.equal([313 {314 address,315 event: 'Transfer',316 args: {317 from: subToEth(alice.address),318 to: receiver,319 value: '51',320 },321 },322 {323 address,324 event: 'Approval',325 args: {326 owner: subToEth(alice.address),327 spender: subToEth(bob.address),328 value: '49',329 },330 },331 ]);332 });333334 itWeb3('Events emitted for transfer()', async ({web3, privateKeyWrapper}) => {335 const collection = await createCollectionExpectSuccess({336 mode: {type: 'Fungible', decimalPoints: 0},337 });338 const alice = privateKeyWrapper('//Alice');339340 const receiver = createEthAccount(web3);341342 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n});343344 const address = collectionIdToAddress(collection);345 const contract = new web3.eth.Contract(fungibleAbi as any, address);346347 const events = await recordEvents(contract, async () => {348 await transferExpectSuccess(collection, 0, alice, {Ethereum:receiver}, 51, 'Fungible');349 });350351 expect(events).to.be.deep.equal([352 {353 address,354 event: 'Transfer',355 args: {356 from: subToEth(alice.address),357 to: receiver,358 value: '51',359 },360 },361 ]);362 });363});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 {approveExpectSuccess, createCollection, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE} from '../util/helpers';18import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, evmCollection, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth} from './util/helpers';19import fungibleAbi from './fungibleAbi.json';20import {expect} from 'chai';21import {submitTransactionAsync} from '../substrate/substrate-api';2223describe('Fungible: Information getting', () => {24 itWeb3('totalSupply', async ({api, web3, privateKeyWrapper}) => {25 const collection = await createCollectionExpectSuccess({26 name: 'token name',27 mode: {type: 'Fungible', decimalPoints: 0},28 });29 const alice = privateKeyWrapper('//Alice');3031 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);3233 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Substrate: alice.address});3435 const address = collectionIdToAddress(collection);36 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS});37 const totalSupply = await contract.methods.totalSupply().call();3839 expect(totalSupply).to.equal('200');40 });4142 itWeb3('balanceOf', async ({api, web3, privateKeyWrapper}) => {43 const collection = await createCollectionExpectSuccess({44 name: 'token name',45 mode: {type: 'Fungible', decimalPoints: 0},46 });47 const alice = privateKeyWrapper('//Alice');4849 const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper);5051 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: caller});5253 const address = collectionIdToAddress(collection);54 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: caller, ...GAS_ARGS});55 const balance = await contract.methods.balanceOf(caller).call();5657 expect(balance).to.equal('200');58 });59});6061describe('Fungible: Plain calls', () => {62 itWeb3('Can perform mint()', async ({web3, api, privateKeyWrapper}) => {63 const alice = privateKeyWrapper('//Alice');64 const collection = await createCollection(api, alice, {65 name: 'token name',66 mode: {type: 'Fungible', decimalPoints: 0},67 });6869 const receiver = createEthAccount(web3);7071 const collectionIdAddress = collectionIdToAddress(collection.collectionId);72 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);73 const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});74 await submitTransactionAsync(alice, changeAdminTx);7576 const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});77 const result = await collectionContract.methods.mint(receiver, 100).send();78 const events = normalizeEvents(result.events);79 80 expect(events).to.be.deep.equal([81 {82 address: collectionIdAddress,83 event: 'Transfer',84 args: {85 from: '0x0000000000000000000000000000000000000000',86 to: receiver,87 value: '100',88 },89 },90 ]);91 });9293 itWeb3('Can perform mintBulk()', async ({web3, api, privateKeyWrapper}) => {94 const alice = privateKeyWrapper('//Alice');95 const collection = await createCollection(api, alice, {96 name: 'token name',97 mode: {type: 'Fungible', decimalPoints: 0},98 });99100 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);101 const receiver1 = createEthAccount(web3);102 const receiver2 = createEthAccount(web3);103 const receiver3 = createEthAccount(web3);104105 const collectionIdAddress = collectionIdToAddress(collection.collectionId);106 const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});107 await submitTransactionAsync(alice, changeAdminTx);108109 const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});110 const result = await collectionContract.methods.mintBulk([111 [receiver1, 10],112 [receiver2, 20],113 [receiver3, 30],114 ]).call();115 console.log(result);116 const events = normalizeEvents(result.events);117118 expect(events).to.be.deep.equal([119 {120 address:collectionIdAddress,121 event: 'Transfer',122 args: {123 from: '0x0000000000000000000000000000000000000000',124 to: receiver1,125 value: '10',126 },127 },128 {129 address:collectionIdAddress,130 event: 'Transfer',131 args: {132 from: '0x0000000000000000000000000000000000000000',133 to: receiver2,134 value: '20',135 },136 },137 {138 address:collectionIdAddress,139 event: 'Transfer',140 args: {141 from: '0x0000000000000000000000000000000000000000',142 to: receiver3,143 value: '30',144 },145 },146 ]);147 });148149 itWeb3('Can perform burn()', async ({web3, api, privateKeyWrapper}) => {150 const alice = privateKeyWrapper('//Alice');151 const collection = await createCollection(api, alice, {152 name: 'token name',153 mode: {type: 'Fungible', decimalPoints: 0},154 });155156 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);157 const changeAdminTx = api.tx.unique.addCollectionAdmin(collection.collectionId, {Ethereum: owner});158 await submitTransactionAsync(alice, changeAdminTx);159 const receiver = await createEthAccountWithBalance(api, web3, privateKeyWrapper);160161 const collectionIdAddress = collectionIdToAddress(collection.collectionId);162 const collectionContract = evmCollection(web3, owner, collectionIdAddress, {type: 'Fungible', decimalPoints: 0});163 await collectionContract.methods.mint(receiver, 100).send();164165 const result = await collectionContract.methods.burnFrom(receiver, 49).send({from: receiver});166 167 const events = normalizeEvents(result.events);168169 expect(events).to.be.deep.equal([170 {171 address: collectionIdAddress,172 event: 'Transfer',173 args: {174 from: receiver,175 to: '0x0000000000000000000000000000000000000000',176 value: '49',177 },178 },179 ]);180181 const balance = await collectionContract.methods.balanceOf(receiver).call();182 expect(balance).to.equal('51');183 });184185 itWeb3('Can perform approve()', async ({web3, api, privateKeyWrapper}) => {186 const collection = await createCollectionExpectSuccess({187 name: 'token name',188 mode: {type: 'Fungible', decimalPoints: 0},189 });190 const alice = privateKeyWrapper('//Alice');191192 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);193194 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});195196 const spender = createEthAccount(web3);197198 const address = collectionIdToAddress(collection);199 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});200201 {202 const result = await contract.methods.approve(spender, 100).send({from: owner});203 const events = normalizeEvents(result.events);204205 expect(events).to.be.deep.equal([206 {207 address,208 event: 'Approval',209 args: {210 owner,211 spender,212 value: '100',213 },214 },215 ]);216 }217218 {219 const allowance = await contract.methods.allowance(owner, spender).call();220 expect(+allowance).to.equal(100);221 }222 });223224 itWeb3('Can perform transferFrom()', async ({web3, api, privateKeyWrapper}) => {225 const collection = await createCollectionExpectSuccess({226 name: 'token name',227 mode: {type: 'Fungible', decimalPoints: 0},228 });229 const alice = privateKeyWrapper('//Alice');230231 const owner = createEthAccount(web3);232 await transferBalanceToEth(api, alice, owner);233234 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});235236 const spender = createEthAccount(web3);237 await transferBalanceToEth(api, alice, spender);238239 const receiver = createEthAccount(web3);240241 const address = collectionIdToAddress(collection);242 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});243244 await contract.methods.approve(spender, 100).send();245246 {247 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});248 const events = normalizeEvents(result.events);249 expect(events).to.be.deep.equal([250 {251 address,252 event: 'Transfer',253 args: {254 from: owner,255 to: receiver,256 value: '49',257 },258 },259 {260 address,261 event: 'Approval',262 args: {263 owner,264 spender,265 value: '51',266 },267 },268 ]);269 }270271 {272 const balance = await contract.methods.balanceOf(receiver).call();273 expect(+balance).to.equal(49);274 }275276 {277 const balance = await contract.methods.balanceOf(owner).call();278 expect(+balance).to.equal(151);279 }280 });281282 itWeb3('Can perform transfer()', async ({web3, api, privateKeyWrapper}) => {283 const collection = await createCollectionExpectSuccess({284 name: 'token name',285 mode: {type: 'Fungible', decimalPoints: 0},286 });287 const alice = privateKeyWrapper('//Alice');288289 const owner = createEthAccount(web3);290 await transferBalanceToEth(api, alice, owner);291292 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});293294 const receiver = createEthAccount(web3);295 await transferBalanceToEth(api, alice, receiver);296297 const address = collectionIdToAddress(collection);298 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});299300 {301 const result = await contract.methods.transfer(receiver, 50).send({from: owner});302 const events = normalizeEvents(result.events);303 expect(events).to.be.deep.equal([304 {305 address,306 event: 'Transfer',307 args: {308 from: owner,309 to: receiver,310 value: '50',311 },312 },313 ]);314 }315316 {317 const balance = await contract.methods.balanceOf(owner).call();318 expect(+balance).to.equal(150);319 }320321 {322 const balance = await contract.methods.balanceOf(receiver).call();323 expect(+balance).to.equal(50);324 }325 });326});327328describe('Fungible: Fees', () => {329 itWeb3('approve() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {330 const collection = await createCollectionExpectSuccess({331 mode: {type: 'Fungible', decimalPoints: 0},332 });333 const alice = privateKeyWrapper('//Alice');334335 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);336 const spender = createEthAccount(web3);337338 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});339340 const address = collectionIdToAddress(collection);341 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});342343 const cost = await recordEthFee(api, owner, () => contract.methods.approve(spender, 100).send({from: owner}));344 expect(cost < BigInt(0.2 * Number(UNIQUE)));345 });346347 itWeb3('transferFrom() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {348 const collection = await createCollectionExpectSuccess({349 mode: {type: 'Fungible', decimalPoints: 0},350 });351 const alice = privateKeyWrapper('//Alice');352353 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);354 const spender = await createEthAccountWithBalance(api, web3, privateKeyWrapper);355356 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});357358 const address = collectionIdToAddress(collection);359 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});360361 await contract.methods.approve(spender, 100).send({from: owner});362363 const cost = await recordEthFee(api, spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));364 expect(cost < BigInt(0.2 * Number(UNIQUE)));365 });366367 itWeb3('transfer() call fee is less than 0.2UNQ', async ({web3, api, privateKeyWrapper}) => {368 const collection = await createCollectionExpectSuccess({369 mode: {type: 'Fungible', decimalPoints: 0},370 });371 const alice = privateKeyWrapper('//Alice');372373 const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper);374 const receiver = createEthAccount(web3);375376 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n}, {Ethereum: owner});377378 const address = collectionIdToAddress(collection);379 const contract = new web3.eth.Contract(fungibleAbi as any, address, {from: owner, ...GAS_ARGS});380381 const cost = await recordEthFee(api, owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));382 expect(cost < BigInt(0.2 * Number(UNIQUE)));383 });384});385386describe('Fungible: Substrate calls', () => {387 itWeb3('Events emitted for approve()', async ({web3, privateKeyWrapper}) => {388 const collection = await createCollectionExpectSuccess({389 mode: {type: 'Fungible', decimalPoints: 0},390 });391 const alice = privateKeyWrapper('//Alice');392393 const receiver = createEthAccount(web3);394395 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n});396397 const address = collectionIdToAddress(collection);398 const contract = new web3.eth.Contract(fungibleAbi as any, address);399400 const events = await recordEvents(contract, async () => {401 await approveExpectSuccess(collection, 0, alice, {Ethereum: receiver}, 100);402 });403404 expect(events).to.be.deep.equal([405 {406 address,407 event: 'Approval',408 args: {409 owner: subToEth(alice.address),410 spender: receiver,411 value: '100',412 },413 },414 ]);415 });416417 itWeb3('Events emitted for transferFrom()', async ({web3, privateKeyWrapper}) => {418 const collection = await createCollectionExpectSuccess({419 mode: {type: 'Fungible', decimalPoints: 0},420 });421 const alice = privateKeyWrapper('//Alice');422 const bob = privateKeyWrapper('//Bob');423424 const receiver = createEthAccount(web3);425426 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n});427 await approveExpectSuccess(collection, 0, alice, bob.address, 100);428429 const address = collectionIdToAddress(collection);430 const contract = new web3.eth.Contract(fungibleAbi as any, address);431432 const events = await recordEvents(contract, async () => {433 await transferFromExpectSuccess(collection, 0, bob, alice, {Ethereum: receiver}, 51, 'Fungible');434 });435436 expect(events).to.be.deep.equal([437 {438 address,439 event: 'Transfer',440 args: {441 from: subToEth(alice.address),442 to: receiver,443 value: '51',444 },445 },446 {447 address,448 event: 'Approval',449 args: {450 owner: subToEth(alice.address),451 spender: subToEth(bob.address),452 value: '49',453 },454 },455 ]);456 });457458 itWeb3('Events emitted for transfer()', async ({web3, privateKeyWrapper}) => {459 const collection = await createCollectionExpectSuccess({460 mode: {type: 'Fungible', decimalPoints: 0},461 });462 const alice = privateKeyWrapper('//Alice');463464 const receiver = createEthAccount(web3);465466 await createFungibleItemExpectSuccess(alice, collection, {Value: 200n});467468 const address = collectionIdToAddress(collection);469 const contract = new web3.eth.Contract(fungibleAbi as any, address);470471 const events = await recordEvents(contract, async () => {472 await transferExpectSuccess(collection, 0, alice, {Ethereum:receiver}, 51, 'Fungible');473 });474475 expect(events).to.be.deep.equal([476 {477 address,478 event: 'Transfer',479 args: {480 from: subToEth(alice.address),481 to: receiver,482 value: '51',483 },484 },485 ]);486 });487});tests/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": [