difftreelog
feat add allowanceCross method
in: master
22 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6360,7 +6360,7 @@
[[package]]
name = "pallet-fungible"
-version = "0.1.9"
+version = "0.1.10"
dependencies = [
"evm-coder",
"frame-benchmarking",
@@ -6773,7 +6773,7 @@
[[package]]
name = "pallet-refungible"
-version = "0.2.12"
+version = "0.2.13"
dependencies = [
"evm-coder",
"frame-benchmarking",
pallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -4,6 +4,12 @@
<!-- bureaucrate goes here -->
+## [0.1.10] - 2023-02-01
+
+### Added
+
+- The functions `allowanceCross` to `ERC20UniqueExtensions` interface.
+
## [0.1.9] - 2022-12-01
### Added
pallets/fungible/Cargo.tomldiffbeforeafterboth--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -2,7 +2,7 @@
edition = "2021"
license = "GPLv3"
name = "pallet-fungible"
-version = "0.1.9"
+version = "0.1.10"
[dependencies]
# Note: `package = "parity-scale-codec"` must be supplied since the `Encode` macro searches for it.
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -161,6 +161,21 @@
where
T::AccountId: From<[u8; 32]>,
{
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ fn allowance_cross(
+ &self,
+ owner: pallet_common::eth::CrossAddress,
+ spender: pallet_common::eth::CrossAddress,
+ ) -> Result<U256> {
+ let owner = owner.into_sub_cross_account::<T>()?;
+ let spender = spender.into_sub_cross_account::<T>()?;
+
+ Ok(<Allowance<T>>::get((self.id, owner, spender)).into())
+ }
+
/// @notice A description for the collection.
fn description(&self) -> Result<String> {
Ok(decode_utf16(self.description.iter().copied())
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -511,8 +511,22 @@
bytes value;
}
-/// @dev the ERC-165 identifier for this interface is 0x65789571
+/// @dev the ERC-165 identifier for this interface is 0x85d7dea6
contract ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ spender;
+ dummy;
+ return 0;
+ }
+
/// @notice A description for the collection.
/// @dev EVM selector for this function is: 0x7284e416,
/// or in textual repr: description()
@@ -576,7 +590,7 @@
/// @param amounts array of pairs of account address and amount
/// @dev EVM selector for this function is: 0x1acf2d55,
/// or in textual repr: mintBulk((address,uint256)[])
- function mintBulk(Tuple9[] memory amounts) public returns (bool) {
+ function mintBulk(Tuple11[] memory amounts) public returns (bool) {
require(false, stub_error);
amounts;
dummy = 0;
@@ -619,7 +633,7 @@
}
/// @dev anonymous struct
-struct Tuple9 {
+struct Tuple11 {
address field_0;
uint256 field_1;
}
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -4,6 +4,12 @@
<!-- bureaucrate goes here -->
+## [0.2.13] - 2023-02-01
+
+### Added
+
+- The functions `allowanceCross` to `ERC20UniqueExtensions` interface.
+
## [0.2.12] - 2023-01-20
### Fixed
pallets/refungible/Cargo.tomldiffbeforeafterboth--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -2,7 +2,7 @@
edition = "2021"
license = "GPLv3"
name = "pallet-refungible"
-version = "0.2.12"
+version = "0.2.13"
[dependencies]
# Note: `package = "parity-scale-codec"` must be supplied since the `Encode` macro searches for it.
pallets/refungible/src/erc_token.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc_token.rs
+++ b/pallets/refungible/src/erc_token.rs
@@ -203,6 +203,21 @@
where
T::AccountId: From<[u8; 32]>,
{
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ fn allowance_cross(
+ &self,
+ owner: pallet_common::eth::CrossAddress,
+ spender: pallet_common::eth::CrossAddress,
+ ) -> Result<U256> {
+ let owner = owner.into_sub_cross_account::<T>()?;
+ let spender = spender.into_sub_cross_account::<T>()?;
+
+ Ok(<Allowance<T>>::get((self.id, self.1, owner, spender)).into())
+ }
+
/// @dev Function that burns an amount of the token of a given account,
/// deducting from the sender's allowance for said account.
/// @param from The account whose tokens will be burnt.
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungibleToken.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungibleToken.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungibleToken.sol
@@ -36,8 +36,22 @@
}
}
-/// @dev the ERC-165 identifier for this interface is 0xe17a7d2b
+/// @dev the ERC-165 identifier for this interface is 0x01d536fc
contract ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) public view returns (uint256) {
+ require(false, stub_error);
+ owner;
+ spender;
+ dummy;
+ return 0;
+ }
+
// /// @dev Function that burns an amount of the token of a given account,
// /// deducting from the sender's allowance for said account.
// /// @param from The account whose tokens will be burnt.
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
runtime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -341,7 +341,9 @@
ERC165Call(_, _) => None,
// Not sponsored
- BurnFrom { .. } | BurnFromCross { .. } | Repartition { .. } => None,
+ AllowanceCross { .. } | BurnFrom { .. } | BurnFromCross { .. } | Repartition { .. } => {
+ None
+ }
TransferCross { .. } | TransferFromCross { .. } => {
let RefungibleTokenHandle(handle, token_id) = token;
tests/src/eth/abi/fungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/fungible.json
+++ b/tests/src/eth/abi/fungible.json
@@ -101,6 +101,32 @@
{ "internalType": "uint256", "name": "sub", "type": "uint256" }
],
"internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "spender",
+ "type": "tuple"
+ }
+ ],
+ "name": "allowanceCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
"name": "user",
"type": "tuple"
}
@@ -411,7 +437,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple9[]",
+ "internalType": "struct Tuple11[]",
"name": "amounts",
"type": "tuple[]"
}
tests/src/eth/abi/reFungibleToken.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungibleToken.json
+++ b/tests/src/eth/abi/reFungibleToken.json
@@ -61,6 +61,32 @@
},
{
"inputs": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "spender",
+ "type": "tuple"
+ }
+ ],
+ "name": "allowanceCross",
+ "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "address", "name": "spender", "type": "address" },
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
],
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -353,8 +353,16 @@
bytes value;
}
-/// @dev the ERC-165 identifier for this interface is 0x65789571
+/// @dev the ERC-165 identifier for this interface is 0x85d7dea6
interface ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) external view returns (uint256);
+
/// @notice A description for the collection.
/// @dev EVM selector for this function is: 0x7284e416,
/// or in textual repr: description()
@@ -390,7 +398,7 @@
/// @param amounts array of pairs of account address and amount
/// @dev EVM selector for this function is: 0x1acf2d55,
/// or in textual repr: mintBulk((address,uint256)[])
- function mintBulk(Tuple9[] memory amounts) external returns (bool);
+ function mintBulk(Tuple11[] memory amounts) external returns (bool);
/// @dev EVM selector for this function is: 0x2ada85ff,
/// or in textual repr: transferCross((address,uint256),uint256)
@@ -411,7 +419,7 @@
}
/// @dev anonymous struct
-struct Tuple9 {
+struct Tuple11 {
address field_0;
uint256 field_1;
}
tests/src/eth/api/UniqueRefungibleToken.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungibleToken.sol
+++ b/tests/src/eth/api/UniqueRefungibleToken.sol
@@ -23,8 +23,16 @@
function parentTokenId() external view returns (uint256);
}
-/// @dev the ERC-165 identifier for this interface is 0xe17a7d2b
+/// @dev the ERC-165 identifier for this interface is 0x01d536fc
interface ERC20UniqueExtensions is Dummy, ERC165 {
+ /// @dev Function to check the amount of tokens that an owner allowed to a spender.
+ /// @param owner crossAddress The address which owns the funds.
+ /// @param spender crossAddress The address which will spend the funds.
+ /// @return A uint256 specifying the amount of tokens still available for the spender.
+ /// @dev EVM selector for this function is: 0xe0af4bd7,
+ /// or in textual repr: allowanceCross((address,uint256),(address,uint256))
+ function allowanceCross(CrossAddress memory owner, CrossAddress memory spender) external view returns (uint256);
+
// /// @dev Function that burns an amount of the token of a given account,
// /// deducting from the sender's allowance for said account.
// /// @param from The account whose tokens will be burnt.
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 {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Plain calls', () => {21 let donor: IKeyringPair;22 let alice: IKeyringPair;23 let owner: IKeyringPair;2425 before(async function() {26 await usingEthPlaygrounds(async (helper, privateKey) => {27 donor = await privateKey({filename: __filename});28 [alice, owner] = await helper.arrange.createAccounts([30n, 20n], donor);29 });30 });3132 [33 'substrate' as const,34 'ethereum' as const,35 ].map(testCase => {36 itEth(`Can perform mintCross() for ${testCase} address`, async ({helper}) => {37 // 1. Create receiver depending on the test case:38 const receiverEth = helper.eth.createAccount();39 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);40 const receiverSub = owner;41 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(owner);4243 const ethOwner = await helper.eth.createAccountWithBalance(donor);44 const collection = await helper.ft.mintCollection(alice);45 await collection.addAdmin(alice, {Ethereum: ethOwner});4647 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);48 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', ethOwner);4950 // 2. Mint tokens:51 const result = await collectionEvm.methods.mintCross(testCase === 'ethereum' ? receiverCrossEth : receiverCrossSub, 100).send();5253 const event = result.events.Transfer;54 expect(event.address).to.equal(collectionAddress);55 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');56 expect(event.returnValues.to).to.equal(testCase === 'ethereum' ? receiverEth : helper.address.substrateToEth(receiverSub.address));57 expect(event.returnValues.value).to.equal('100');5859 // 3. Get balance depending on the test case:60 let balance;61 if (testCase === 'ethereum') balance = await collection.getBalance({Ethereum: receiverEth});62 else if (testCase === 'substrate') balance = await collection.getBalance({Substrate: receiverSub.address});63 // 3.1 Check balance:64 expect(balance).to.eq(100n);65 });66 });6768 itEth('Can perform mintBulk()', async ({helper}) => {69 const owner = await helper.eth.createAccountWithBalance(donor);70 const bulkSize = 3;71 const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());72 const collection = await helper.ft.mintCollection(alice);73 await collection.addAdmin(alice, {Ethereum: owner});7475 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);76 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7778 const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (79 [receivers[i], (i + 1) * 10]80 ))).send();81 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);82 for (let i = 0; i < bulkSize; i++) {83 const event = events[i];84 expect(event.address).to.equal(collectionAddress);85 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');86 expect(event.returnValues.to).to.equal(receivers[i]);87 expect(event.returnValues.value).to.equal(String(10 * (i + 1)));88 }89 });9091 // Soft-deprecated92 itEth('Can perform burn()', async ({helper}) => {93 const owner = await helper.eth.createAccountWithBalance(donor);94 const receiver = await helper.eth.createAccountWithBalance(donor);95 const collection = await helper.ft.mintCollection(alice);96 await collection.addAdmin(alice, {Ethereum: owner});9798 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);99 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);100 await contract.methods.mint(receiver, 100).send();101102 const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});103104 const event = result.events.Transfer;105 expect(event.address).to.equal(collectionAddress);106 expect(event.returnValues.from).to.equal(receiver);107 expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');108 expect(event.returnValues.value).to.equal('49');109110 const balance = await contract.methods.balanceOf(receiver).call();111 expect(balance).to.equal('51');112 });113114 itEth('Can perform approve()', async ({helper}) => {115 const owner = await helper.eth.createAccountWithBalance(donor);116 const spender = helper.eth.createAccount();117 const collection = await helper.ft.mintCollection(alice);118 await collection.mint(alice, 200n, {Ethereum: owner});119120 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);121 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);122123 {124 const result = await contract.methods.approve(spender, 100).send({from: owner});125126 const event = result.events.Approval;127 expect(event.address).to.be.equal(collectionAddress);128 expect(event.returnValues.owner).to.be.equal(owner);129 expect(event.returnValues.spender).to.be.equal(spender);130 expect(event.returnValues.value).to.be.equal('100');131 }132133 {134 const allowance = await contract.methods.allowance(owner, spender).call();135 expect(+allowance).to.equal(100);136 }137 });138139 itEth('Can perform approveCross()', async ({helper}) => {140 const owner = await helper.eth.createAccountWithBalance(donor);141 const spender = helper.eth.createAccount();142 const spenderSub = (await helper.arrange.createAccounts([1n], donor))[0];143 const spenderCrossEth = helper.ethCrossAccount.fromAddress(spender);144 const spenderCrossSub = helper.ethCrossAccount.fromKeyringPair(spenderSub);145146147 const collection = await helper.ft.mintCollection(alice);148 await collection.mint(alice, 200n, {Ethereum: owner});149150 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);151 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);152153 {154 const result = await contract.methods.approveCross(spenderCrossEth, 100).send({from: owner});155 const event = result.events.Approval;156 expect(event.address).to.be.equal(collectionAddress);157 expect(event.returnValues.owner).to.be.equal(owner);158 expect(event.returnValues.spender).to.be.equal(spender);159 expect(event.returnValues.value).to.be.equal('100');160 }161162 {163 const allowance = await contract.methods.allowance(owner, spender).call();164 expect(+allowance).to.equal(100);165 }166167168 {169 const result = await contract.methods.approveCross(spenderCrossSub, 100).send({from: owner});170 const event = result.events.Approval;171 expect(event.address).to.be.equal(collectionAddress);172 expect(event.returnValues.owner).to.be.equal(owner);173 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(spenderSub.address));174 expect(event.returnValues.value).to.be.equal('100');175 }176177 {178 const allowance = await collection.getApprovedTokens({Ethereum: owner}, {Substrate: spenderSub.address});179 expect(allowance).to.equal(100n);180 }181182 {183 //TO-DO expect with future allowanceCross(owner, spenderCrossEth).call()184 }185 });186187 itEth('Non-owner and non admin cannot approveCross', async ({helper}) => {188 const nonOwner = await helper.eth.createAccountWithBalance(donor);189 const nonOwnerCross = helper.ethCrossAccount.fromAddress(nonOwner);190 const owner = await helper.eth.createAccountWithBalance(donor);191 const collection = await helper.ft.mintCollection(alice, {name: 'A', description: 'B', tokenPrefix: 'C'});192 await collection.mint(alice, 100n, {Ethereum: owner});193194 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);195 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);196197 await expect(collectionEvm.methods.approveCross(nonOwnerCross, 20).call({from: nonOwner})).to.be.rejectedWith('CantApproveMoreThanOwned');198 });199200201 itEth('Can perform burnFromCross()', async ({helper}) => {202 const sender = await helper.eth.createAccountWithBalance(donor, 100n);203204 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);205206 await collection.mint(owner, 200n, {Substrate: owner.address});207 await collection.approveTokens(owner, {Ethereum: sender}, 100n);208209 const address = helper.ethAddress.fromCollectionId(collection.collectionId);210 const contract = await helper.ethNativeContract.collection(address, 'ft');211212 const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});213214 const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);215 const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});216 const events = result.events;217218 expect(events).to.be.like({219 Transfer: {220 address: helper.ethAddress.fromCollectionId(collection.collectionId),221 event: 'Transfer',222 returnValues: {223 from: helper.address.substrateToEth(owner.address),224 to: '0x0000000000000000000000000000000000000000',225 value: '49',226 },227 },228 Approval: {229 address: helper.ethAddress.fromCollectionId(collection.collectionId),230 returnValues: {231 owner: helper.address.substrateToEth(owner.address),232 spender: sender,233 value: '51',234 },235 event: 'Approval',236 },237 });238239 const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});240 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);241 });242243 itEth('Can perform transferFrom()', async ({helper}) => {244 const owner = await helper.eth.createAccountWithBalance(donor);245 const spender = await helper.eth.createAccountWithBalance(donor);246 const receiver = helper.eth.createAccount();247 const collection = await helper.ft.mintCollection(alice);248 await collection.mint(alice, 200n, {Ethereum: owner});249250 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);251 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);252253 await contract.methods.approve(spender, 100).send();254255 {256 const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});257258 let event = result.events.Transfer;259 expect(event.address).to.be.equal(collectionAddress);260 expect(event.returnValues.from).to.be.equal(owner);261 expect(event.returnValues.to).to.be.equal(receiver);262 expect(event.returnValues.value).to.be.equal('49');263264 event = result.events.Approval;265 expect(event.address).to.be.equal(collectionAddress);266 expect(event.returnValues.owner).to.be.equal(owner);267 expect(event.returnValues.spender).to.be.equal(spender);268 expect(event.returnValues.value).to.be.equal('51');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 itEth('Can perform transferCross()', async ({helper}) => {283 const sender = await helper.eth.createAccountWithBalance(donor);284 const receiverEth = await helper.eth.createAccountWithBalance(donor);285 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);286 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(donor);287 const collection = await helper.ft.mintCollection(alice);288 await collection.mint(alice, 200n, {Ethereum: sender});289290 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);291 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', sender);292293 {294 // Can transferCross to ethereum address:295 const result = await collectionEvm.methods.transferCross(receiverCrossEth, 50).send({from: sender});296 // Check events:297 const event = result.events.Transfer;298 expect(event.address).to.be.equal(collectionAddress);299 expect(event.returnValues.from).to.be.equal(sender);300 expect(event.returnValues.to).to.be.equal(receiverEth);301 expect(event.returnValues.value).to.be.equal('50');302 // Sender's balance decreased:303 const ownerBalance = await collectionEvm.methods.balanceOf(sender).call();304 expect(+ownerBalance).to.equal(150);305 // Receiver's balance increased:306 const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();307 expect(+receiverBalance).to.equal(50);308 }309310 {311 // Can transferCross to substrate address:312 const result = await collectionEvm.methods.transferCross(receiverCrossSub, 50).send({from: sender});313 // Check events:314 const event = result.events.Transfer;315 expect(event.address).to.be.equal(collectionAddress);316 expect(event.returnValues.from).to.be.equal(sender);317 expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(donor.address));318 expect(event.returnValues.value).to.be.equal('50');319 // Sender's balance decreased:320 const senderBalance = await collection.getBalance({Ethereum: sender});321 expect(senderBalance).to.equal(100n);322 // Receiver's balance increased:323 const balance = await collection.getBalance({Substrate: donor.address});324 expect(balance).to.equal(50n);325 }326 });327328 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} incorrect amount`, async ({helper}) => {329 const sender = await helper.eth.createAccountWithBalance(donor);330 const receiverEth = await helper.eth.createAccountWithBalance(donor);331 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);332 const BALANCE = 200n;333 const BALANCE_TO_TRANSFER = BALANCE + 100n;334335 const collection = await helper.ft.mintCollection(alice);336 await collection.mint(alice, BALANCE, {Ethereum: sender});337 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);338 const collectionEvm = await helper.ethNativeContract.collection(collectionAddress, 'ft', sender);339340 // 1. Cannot transfer more than have341 const receiver = testCase === 'transfer' ? receiverEth : receiverCrossEth;342 await expect(collectionEvm.methods[testCase](receiver, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;343 // 2. Zero transfer allowed (EIP-20):344 await collectionEvm.methods[testCase](receiver, 0n).send({from: sender});345 }));346347348 itEth('Can perform transfer()', async ({helper}) => {349 const owner = await helper.eth.createAccountWithBalance(donor);350 const receiver = await helper.eth.createAccountWithBalance(donor);351 const collection = await helper.ft.mintCollection(alice);352 await collection.mint(alice, 200n, {Ethereum: owner});353354 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);355 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);356357 {358 const result = await contract.methods.transfer(receiver, 50).send({from: owner});359360 const event = result.events.Transfer;361 expect(event.address).to.be.equal(collectionAddress);362 expect(event.returnValues.from).to.be.equal(owner);363 expect(event.returnValues.to).to.be.equal(receiver);364 expect(event.returnValues.value).to.be.equal('50');365 }366367 {368 const balance = await contract.methods.balanceOf(owner).call();369 expect(+balance).to.equal(150);370 }371372 {373 const balance = await contract.methods.balanceOf(receiver).call();374 expect(+balance).to.equal(50);375 }376 });377378 itEth('Can perform transferFromCross()', async ({helper}) => {379 const sender = await helper.eth.createAccountWithBalance(donor, 100n);380381 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);382383 const receiver = helper.eth.createAccount();384385 await collection.mint(owner, 200n, {Substrate: owner.address});386 await collection.approveTokens(owner, {Ethereum: sender}, 100n);387388 const address = helper.ethAddress.fromCollectionId(collection.collectionId);389 const contract = await helper.ethNativeContract.collection(address, 'ft');390391 const from = helper.ethCrossAccount.fromKeyringPair(owner);392 const to = helper.ethCrossAccount.fromAddress(receiver);393394 const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});395 const toBalanceBefore = await collection.getBalance({Ethereum: receiver});396397 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});398399 expect(result.events).to.be.like({400 Transfer: {401 address,402 event: 'Transfer',403 returnValues: {404 from: helper.address.substrateToEth(owner.address),405 to: receiver,406 value: '51',407 },408 },409 Approval: {410 address,411 event: 'Approval',412 returnValues: {413 owner: helper.address.substrateToEth(owner.address),414 spender: sender,415 value: '49',416 },417 }});418419 const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});420 expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);421 const toBalanceAfter = await collection.getBalance({Ethereum: receiver});422 expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);423 });424});425426describe('Fungible: Fees', () => {427 let donor: IKeyringPair;428 let alice: IKeyringPair;429430 before(async function() {431 await usingEthPlaygrounds(async (helper, privateKey) => {432 donor = await privateKey({filename: __filename});433 [alice] = await helper.arrange.createAccounts([20n], donor);434 });435 });436437 itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {438 const owner = await helper.eth.createAccountWithBalance(donor);439 const spender = helper.eth.createAccount();440 const collection = await helper.ft.mintCollection(alice);441 await collection.mint(alice, 200n, {Ethereum: owner});442443 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);444 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);445446 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));447 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));448 });449450 itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {451 const owner = await helper.eth.createAccountWithBalance(donor);452 const spender = await helper.eth.createAccountWithBalance(donor);453 const collection = await helper.ft.mintCollection(alice);454 await collection.mint(alice, 200n, {Ethereum: owner});455456 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);457 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);458459 await contract.methods.approve(spender, 100).send({from: owner});460461 const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));462 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));463 });464465 itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {466 const owner = await helper.eth.createAccountWithBalance(donor);467 const receiver = helper.eth.createAccount();468 const collection = await helper.ft.mintCollection(alice);469 await collection.mint(alice, 200n, {Ethereum: owner});470471 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);472 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);473474 const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));475 expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));476 });477});478479describe('Fungible: Substrate calls', () => {480 let donor: IKeyringPair;481 let alice: IKeyringPair;482 let owner: IKeyringPair;483484 before(async function() {485 await usingEthPlaygrounds(async (helper, privateKey) => {486 donor = await privateKey({filename: __filename});487 [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);488 });489 });490491 itEth('Events emitted for approve()', async ({helper}) => {492 const receiver = helper.eth.createAccount();493 const collection = await helper.ft.mintCollection(alice);494 await collection.mint(alice, 200n);495496 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);497 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');498499 const events: any = [];500 contract.events.allEvents((_: any, event: any) => {501 events.push(event);502 });503504 await collection.approveTokens(alice, {Ethereum: receiver}, 100n);505 if (events.length == 0) await helper.wait.newBlocks(1);506 const event = events[0];507508 expect(event.event).to.be.equal('Approval');509 expect(event.address).to.be.equal(collectionAddress);510 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));511 expect(event.returnValues.spender).to.be.equal(receiver);512 expect(event.returnValues.value).to.be.equal('100');513 });514515 itEth('Events emitted for transferFrom()', async ({helper}) => {516 const [bob] = await helper.arrange.createAccounts([10n], donor);517 const receiver = helper.eth.createAccount();518 const collection = await helper.ft.mintCollection(alice);519 await collection.mint(alice, 200n);520 await collection.approveTokens(alice, {Substrate: bob.address}, 100n);521522 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);523 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');524525 const events: any = [];526 contract.events.allEvents((_: any, event: any) => {527 events.push(event);528 });529530 await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);531 if (events.length == 0) await helper.wait.newBlocks(1);532 let event = events[0];533534 expect(event.event).to.be.equal('Transfer');535 expect(event.address).to.be.equal(collectionAddress);536 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));537 expect(event.returnValues.to).to.be.equal(receiver);538 expect(event.returnValues.value).to.be.equal('51');539540 event = events[1];541 expect(event.event).to.be.equal('Approval');542 expect(event.address).to.be.equal(collectionAddress);543 expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));544 expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));545 expect(event.returnValues.value).to.be.equal('49');546 });547548 itEth('Events emitted for transfer()', async ({helper}) => {549 const receiver = helper.eth.createAccount();550 const collection = await helper.ft.mintCollection(alice);551 await collection.mint(alice, 200n);552553 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);554 const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft');555556 const events: any = [];557 contract.events.allEvents((_: any, event: any) => {558 events.push(event);559 });560561 await collection.transfer(alice, {Ethereum:receiver}, 51n);562 if (events.length == 0) await helper.wait.newBlocks(1);563 const event = events[0];564565 expect(event.event).to.be.equal('Transfer');566 expect(event.address).to.be.equal(collectionAddress);567 expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));568 expect(event.returnValues.to).to.be.equal(receiver);569 expect(event.returnValues.value).to.be.equal('51');570 });571572 itEth('Events emitted for transferFromCross()', async ({helper}) => {573 const sender = await helper.eth.createAccountWithBalance(donor, 100n);574575 const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);576577 const receiver = helper.eth.createAccount();578579 await collection.mint(owner, 200n, {Substrate: owner.address});580 await collection.approveTokens(owner, {Ethereum: sender}, 100n);581582 const address = helper.ethAddress.fromCollectionId(collection.collectionId);583 const contract = await helper.ethNativeContract.collection(address, 'ft');584585 const from = helper.ethCrossAccount.fromKeyringPair(owner);586 const to = helper.ethCrossAccount.fromAddress(receiver);587588 const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});589590 expect(result.events).to.be.like({591 Transfer: {592 address,593 event: 'Transfer',594 returnValues: {595 from: helper.address.substrateToEth(owner.address),596 to: receiver,597 value: '51',598 },599 },600 Approval: {601 address,602 event: 'Approval',603 returnValues: {604 owner: helper.address.substrateToEth(owner.address),605 spender: sender,606 value: '49',607 },608 }});609 });610});tests/src/eth/reFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -219,8 +219,16 @@
await rftToken.methods.approve(operator, 15n).send({from: owner});
await contract.methods.setApprovalForAll(operator, true).send({from: owner});
await rftToken.methods.burnFrom(owner, 10n).send({from: operator});
+ }
+ {
const allowance = await rftToken.methods.allowance(owner, operator).call();
- expect(allowance).to.be.equal('5');
+ expect(+allowance).to.be.equal(5);
+ }
+ {
+ const ownerCross = helper.ethCrossAccount.fromAddress(owner);
+ const operatorCross = helper.ethCrossAccount.fromAddress(operator);
+ const allowance = await rftToken.methods.allowanceCross(ownerCross, operatorCross).call();
+ expect(+allowance).to.equal(5);
}
});