difftreelog
test basic evm integration tests
in: master
7 files changed
tests/src/eth/fungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/fungible.test.ts
@@ -0,0 +1,289 @@
+import privateKey from "../substrate/privateKey";
+import { approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess } from "../util/helpers";
+import { collectionIdToAddress, createEthAccount, itWeb3, normalizeEvents, recordEvents, subToEth, transferBalanceToEth } from "./util/helpers"
+import fungibleAbi from './fungibleAbi.json';
+import { expect } from "chai";
+
+describe('Information getting', () => {
+ itWeb3('totalSupply', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ name: 'token name',
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { substrate: alice.address });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+ const totalSupply = await contract.methods.totalSupply().call();
+
+ // FIXME: always equals to 0, because this method is not implemented
+ expect(totalSupply).to.equal('0');
+ });
+
+ itWeb3('balanceOf', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ name: 'token name',
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+
+ const caller = createEthAccount(web3);
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: caller });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+ const balance = await contract.methods.balanceOf(caller).call();
+
+ expect(balance).to.equal('200');
+ });
+});
+
+describe('Plain calls', () => {
+ itWeb3('Can perform approve()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ name: 'token name',
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner, 999999999999999);
+
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: owner });
+
+ const spender = createEthAccount(web3);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+
+ {
+ const result = await contract.methods.approve(spender, 100).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });
+ const events = normalizeEvents(result.events);
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner,
+ spender,
+ value: '100',
+ }
+ }
+ ]);
+ }
+
+ {
+ const allowance = await contract.methods.allowance(owner, spender).call();
+ expect(+allowance).to.equal(100);
+ }
+ });
+
+ itWeb3('Can perform transferFrom()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ name: 'token name',
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner, 999999999999999);
+
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: owner });
+
+ const spender = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, spender, 999999999999999);
+
+ const receiver = createEthAccount(web3);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+
+ await contract.methods.approve(spender, 100).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });
+
+ {
+ const result = await contract.methods.transferFrom(owner, receiver, 49).send({ from: spender, gas: '0x1000000', gasPrice: '0x01' });
+ const events = normalizeEvents(result.events);
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: receiver,
+ value: '49',
+ }
+ },
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner,
+ spender,
+ value: '51',
+ }
+ }
+ ]);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(receiver).call();
+ expect(+balance).to.equal(49);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(151);
+ }
+ });
+
+ itWeb3('Can perform transfer()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ name: 'token name',
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner, 999999999999999);
+
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n }, { ethereum: owner });
+
+ const receiver = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, receiver, 999999999999999);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+
+ {
+ const result = await contract.methods.transfer(receiver, 50).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });
+ const events = normalizeEvents(result.events);
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: receiver,
+ value: '50',
+ }
+ },
+ ])
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(150);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(receiver).call();
+ expect(+balance).to.equal(50);
+ }
+ });
+});
+
+describe('Substrate calls', () => {
+ itWeb3('Events emitted for approve()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+
+ const receiver = createEthAccount(web3);
+
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ await approveExpectSuccess(collection, 1, alice, { ethereum: receiver }, 100);
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner: subToEth(alice.address),
+ spender: receiver,
+ value: '100',
+ }
+ }
+ ]);
+ });
+
+ itWeb3('Events emitted for transferFrom()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const receiver = createEthAccount(web3);
+
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n });
+ await approveExpectSuccess(collection, 1, alice, bob, 100);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ await transferFromExpectSuccess(collection, 1, bob, alice, { ethereum: receiver }, 51, 'Fungible');
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: subToEth(alice.address),
+ to: receiver,
+ value: '51',
+ }
+ },
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner: subToEth(alice.address),
+ spender: subToEth(bob.address),
+ value: '49',
+ }
+ }
+ ]);
+ });
+
+ itWeb3('Events emitted for transfer()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'Fungible', decimalPoints: 0 }
+ });
+ const alice = privateKey('//Alice');
+
+ const receiver = createEthAccount(web3);
+
+ await createFungibleItemExpectSuccess(alice, collection, { Value: 200n });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ await transferExpectSuccess(collection, 1, alice, { ethereum: receiver }, 51, 'Fungible');
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: subToEth(alice.address),
+ to: receiver,
+ value: '51',
+ }
+ },
+ ]);
+ });
+});
\ No newline at end of file
tests/src/eth/fungibleAbi.jsondiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/fungibleAbi.json
@@ -0,0 +1,175 @@
+[
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_spender",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "approve",
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [],
+ "name": "totalSupply",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_from",
+ "type": "address"
+ },
+ {
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferFrom",
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "name": "balance",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "constant": false,
+ "inputs": [
+ {
+ "name": "_to",
+ "type": "address"
+ },
+ {
+ "name": "_value",
+ "type": "uint256"
+ }
+ ],
+ "name": "transfer",
+ "outputs": [
+ {
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "constant": true,
+ "inputs": [
+ {
+ "name": "_owner",
+ "type": "address"
+ },
+ {
+ "name": "_spender",
+ "type": "address"
+ }
+ ],
+ "name": "allowance",
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "payable": false,
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "spender",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "name": "value",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ }
+]
\ No newline at end of file
tests/src/eth/fungibleMetadataAbi.jsondiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/fungibleMetadataAbi.json
@@ -0,0 +1,41 @@
+[
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "decimals",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ }
+]
\ No newline at end of file
tests/src/eth/metadata.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/metadata.test.ts
@@ -0,0 +1,53 @@
+import { expect } from "chai";
+import privateKey from "../substrate/privateKey";
+import { createCollectionExpectSuccess } from "../util/helpers";
+import { collectionIdToAddress, createEthAccount, itWeb3, transferBalanceToEth } from "./util/helpers";
+import fungibleMetadataAbi from './fungibleMetadataAbi.json';
+
+describe('Common metadata', () => {
+ itWeb3('Returns collection name', async ({ api, web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ name: 'token name',
+ mode: { type: 'NFT' }
+ });
+ const caller = createEthAccount(web3);
+ await transferBalanceToEth(api, privateKey('//Alice'), caller, 999999);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleMetadataAbi as any, address);
+ const name = await contract.methods.name().call({ from: caller });
+
+ expect(name).to.equal('token name');
+ });
+
+ itWeb3('Returns symbol name', async ({ api, web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ tokenPrefix: 'TOK',
+ mode: { type: 'NFT' }
+ });
+ const caller = createEthAccount(web3);
+ await transferBalanceToEth(api, privateKey('//Alice'), caller, 999999);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleMetadataAbi as any, address);
+ const symbol = await contract.methods.symbol().call({ from: caller });
+
+ expect(symbol).to.equal('TOK');
+ });
+});
+
+describe('Fungible metadata', () => {
+ itWeb3('Returns fungible decimals', async ({ api, web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'Fungible', decimalPoints: 6 }
+ });
+ const caller = createEthAccount(web3);
+ await transferBalanceToEth(api, privateKey('//Alice'), caller, 999999);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(fungibleMetadataAbi as any, address);
+ const decimals = await contract.methods.decimals().call({ from: caller });
+
+ expect(+decimals).to.equal(6);
+ })
+})
\ No newline at end of file
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/nonFungible.test.ts
@@ -0,0 +1,280 @@
+import privateKey from "../substrate/privateKey";
+import { approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess } from "../util/helpers";
+import { collectionIdToAddress, createEthAccount, itWeb3, normalizeEvents, recordEvents, subToEth, transferBalanceToEth } from "./util/helpers"
+import nonFungibleAbi from './nonFungibleAbi.json';
+import { expect } from "chai";
+
+describe('Information getting', () => {
+ itWeb3('totalSupply', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ await createItemExpectSuccess(alice, collection, 'NFT', { substrate: alice.address });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+ const totalSupply = await contract.methods.totalSupply().call();
+
+ // FIXME: always equals to 0, because this method is not implemented
+ expect(totalSupply).to.equal('0');
+ });
+
+ itWeb3('balanceOf', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ const caller = createEthAccount(web3);
+ await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+ await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+ await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+ const balance = await contract.methods.balanceOf(caller).call();
+
+ expect(balance).to.equal('3');
+ });
+
+ itWeb3('ownerOf', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ const caller = createEthAccount(web3);
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: caller });
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+ const owner = await contract.methods.ownerOf(tokenId).call();
+
+ expect(owner).to.equal(caller);
+ });
+});
+
+describe.only('Plain calls', () => {
+ itWeb3('Can perform approve()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner, 999999999999999);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });
+
+ const spender = createEthAccount(web3);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ {
+ const result = await contract.methods.approve(spender, tokenId).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });
+ const events = normalizeEvents(result.events);
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner,
+ approved: spender,
+ tokenId: tokenId.toString(),
+ }
+ }
+ ]);
+ }
+ });
+
+ itWeb3('Can perform transferFrom()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner, 999999999999999);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });
+
+ const spender = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, spender, 999999999999999);
+
+ const receiver = createEthAccount(web3);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ await contract.methods.approve(spender, tokenId).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });
+
+ {
+ const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({ from: spender, gas: '0x1000000', gasPrice: '0x01' });
+ const events = normalizeEvents(result.events);
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: receiver,
+ tokenId: tokenId.toString(),
+ },
+ },
+ ]);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(receiver).call();
+ expect(+balance).to.equal(1);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(0);
+ }
+ });
+
+ itWeb3('Can perform transfer()', async ({ web3, api }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ const owner = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, owner, 999999999999999);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });
+
+ const receiver = createEthAccount(web3);
+ await transferBalanceToEth(api, alice, receiver, 999999999999999);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ {
+ const result = await contract.methods.transfer(receiver, tokenId).send({ from: owner, gas: '0x1000000', gasPrice: '0x01' });
+ console.log(result);
+ const events = normalizeEvents(result.events);
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: owner,
+ to: receiver,
+ tokenId: tokenId.toString(),
+ }
+ },
+ ])
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(owner).call();
+ expect(+balance).to.equal(0);
+ }
+
+ {
+ const balance = await contract.methods.balanceOf(receiver).call();
+ expect(+balance).to.equal(1);
+ }
+ });
+});
+
+describe('Substrate calls', () => {
+ itWeb3('Events emitted for approve()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ const receiver = createEthAccount(web3);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ await approveExpectSuccess(collection, tokenId, alice, { ethereum: receiver }, 1);
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Approval',
+ args: {
+ owner: subToEth(alice.address),
+ approved: receiver,
+ tokenId: tokenId.toString(),
+ }
+ }
+ ]);
+ });
+
+ itWeb3('Events emitted for transferFrom()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const receiver = createEthAccount(web3);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');
+ await approveExpectSuccess(collection, tokenId, alice, bob, 1);
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ await transferFromExpectSuccess(collection, tokenId, bob, alice, { ethereum: receiver }, 1, 'NFT');
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: subToEth(alice.address),
+ to: receiver,
+ tokenId: tokenId.toString(),
+ }
+ },
+ ]);
+ });
+
+ itWeb3('Events emitted for transfer()', async ({ web3 }) => {
+ const collection = await createCollectionExpectSuccess({
+ mode: { type: 'NFT' }
+ });
+ const alice = privateKey('//Alice');
+
+ const receiver = createEthAccount(web3);
+
+ const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');
+
+ const address = collectionIdToAddress(collection);
+ const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+ const events = await recordEvents(contract, async () => {
+ await transferExpectSuccess(collection, tokenId, alice, { ethereum: receiver }, 1, 'NFT');
+ });
+
+ expect(events).to.be.deep.equal([
+ {
+ address,
+ event: 'Transfer',
+ args: {
+ from: subToEth(alice.address),
+ to: receiver,
+ tokenId: tokenId.toString(),
+ }
+ },
+ ]);
+ });
+});
\ No newline at end of file
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth1[2 {3 "anonymous": false,4 "inputs": [5 {6 "indexed": true,7 "internalType": "address",8 "name": "owner",9 "type": "address"10 },11 {12 "indexed": true,13 "internalType": "address",14 "name": "approved",15 "type": "address"16 },17 {18 "indexed": true,19 "internalType": "uint256",20 "name": "tokenId",21 "type": "uint256"22 }23 ],24 "name": "Approval",25 "type": "event"26 },27 {28 "anonymous": false,29 "inputs": [30 {31 "indexed": true,32 "internalType": "address",33 "name": "owner",34 "type": "address"35 },36 {37 "indexed": true,38 "internalType": "address",39 "name": "operator",40 "type": "address"41 },42 {43 "indexed": false,44 "internalType": "bool",45 "name": "approved",46 "type": "bool"47 }48 ],49 "name": "ApprovalForAll",50 "type": "event"51 },52 {53 "anonymous": false,54 "inputs": [55 {56 "indexed": true,57 "internalType": "address",58 "name": "from",59 "type": "address"60 },61 {62 "indexed": true,63 "internalType": "address",64 "name": "to",65 "type": "address"66 },67 {68 "indexed": true,69 "internalType": "uint256",70 "name": "tokenId",71 "type": "uint256"72 }73 ],74 "name": "Transfer",75 "type": "event"76 },77 {78 "inputs": [79 {80 "internalType": "address",81 "name": "approved",82 "type": "address"83 },84 {85 "internalType": "uint256",86 "name": "tokenId",87 "type": "uint256"88 }89 ],90 "name": "approve",91 "outputs": [],92 "stateMutability": "payable",93 "type": "function"94 },95 {96 "inputs": [97 {98 "internalType": "address",99 "name": "owner",100 "type": "address"101 }102 ],103 "name": "balanceOf",104 "outputs": [105 {106 "internalType": "uint256",107 "name": "",108 "type": "uint256"109 }110 ],111 "stateMutability": "view",112 "type": "function"113 },114 {115 "inputs": [116 {117 "internalType": "uint256",118 "name": "tokenId",119 "type": "uint256"120 }121 ],122 "name": "getApproved",123 "outputs": [124 {125 "internalType": "address",126 "name": "",127 "type": "address"128 }129 ],130 "stateMutability": "view",131 "type": "function"132 },133 {134 "inputs": [135 {136 "internalType": "address",137 "name": "owner",138 "type": "address"139 },140 {141 "internalType": "address",142 "name": "operator",143 "type": "address"144 }145 ],146 "name": "isApprovedForAll",147 "outputs": [148 {149 "internalType": "bool",150 "name": "",151 "type": "bool"152 }153 ],154 "stateMutability": "view",155 "type": "function"156 },157 {158 "inputs": [],159 "name": "name",160 "outputs": [161 {162 "internalType": "string",163 "name": "res_name",164 "type": "string"165 }166 ],167 "stateMutability": "view",168 "type": "function"169 },170 {171 "inputs": [172 {173 "internalType": "uint256",174 "name": "tokenId",175 "type": "uint256"176 }177 ],178 "name": "ownerOf",179 "outputs": [180 {181 "internalType": "address",182 "name": "",183 "type": "address"184 }185 ],186 "stateMutability": "view",187 "type": "function"188 },189 {190 "inputs": [191 {192 "internalType": "address",193 "name": "from",194 "type": "address"195 },196 {197 "internalType": "address",198 "name": "to",199 "type": "address"200 },201 {202 "internalType": "uint256",203 "name": "tokenId",204 "type": "uint256"205 }206 ],207 "name": "safeTransferFrom",208 "outputs": [],209 "stateMutability": "payable",210 "type": "function"211 },212 {213 "inputs": [214 {215 "internalType": "address",216 "name": "from",217 "type": "address"218 },219 {220 "internalType": "address",221 "name": "to",222 "type": "address"223 },224 {225 "internalType": "uint256",226 "name": "tokenId",227 "type": "uint256"228 },229 {230 "internalType": "bytes",231 "name": "data",232 "type": "bytes"233 }234 ],235 "name": "safeTransferFrom",236 "outputs": [],237 "stateMutability": "payable",238 "type": "function"239 },240 {241 "inputs": [242 {243 "internalType": "address",244 "name": "operator",245 "type": "address"246 },247 {248 "internalType": "bool",249 "name": "approved",250 "type": "bool"251 }252 ],253 "name": "setApprovalForAll",254 "outputs": [],255 "stateMutability": "nonpayable",256 "type": "function"257 },258 {259 "inputs": [260 {261 "internalType": "bytes4",262 "name": "interfaceID",263 "type": "bytes4"264 }265 ],266 "name": "supportsInterface",267 "outputs": [268 {269 "internalType": "bool",270 "name": "",271 "type": "bool"272 }273 ],274 "stateMutability": "pure",275 "type": "function"276 },277 {278 "inputs": [],279 "name": "symbol",280 "outputs": [281 {282 "internalType": "string",283 "name": "res_symbol",284 "type": "string"285 }286 ],287 "stateMutability": "view",288 "type": "function"289 },290 {291 "inputs": [292 {293 "internalType": "uint256",294 "name": "index",295 "type": "uint256"296 }297 ],298 "name": "tokenByIndex",299 "outputs": [300 {301 "internalType": "uint256",302 "name": "",303 "type": "uint256"304 }305 ],306 "stateMutability": "view",307 "type": "function"308 },309 {310 "inputs": [311 {312 "internalType": "address",313 "name": "owner",314 "type": "address"315 },316 {317 "internalType": "uint256",318 "name": "index",319 "type": "uint256"320 }321 ],322 "name": "tokenOfOwnerByIndex",323 "outputs": [324 {325 "internalType": "uint256",326 "name": "",327 "type": "uint256"328 }329 ],330 "stateMutability": "view",331 "type": "function"332 },333 {334 "inputs": [335 {336 "internalType": "uint256",337 "name": "tokenId",338 "type": "uint256"339 }340 ],341 "name": "tokenURI",342 "outputs": [343 {344 "internalType": "string",345 "name": "",346 "type": "string"347 }348 ],349 "stateMutability": "view",350 "type": "function"351 },352 {353 "inputs": [],354 "name": "totalSupply",355 "outputs": [356 {357 "internalType": "uint256",358 "name": "",359 "type": "uint256"360 }361 ],362 "stateMutability": "view",363 "type": "function"364 },365 {366 "inputs": [367 {368 "internalType": "address",369 "name": "from",370 "type": "address"371 },372 {373 "internalType": "address",374 "name": "to",375 "type": "address"376 },377 {378 "internalType": "uint256",379 "name": "tokenId",380 "type": "uint256"381 }382 ],383 "name": "transferFrom",384 "outputs": [],385 "stateMutability": "payable",386 "type": "function"387 },388 {389 "inputs": [390 {391 "internalType": "address",392 "name": "to",393 "type": "address"394 },395 {396 "internalType": "uint256",397 "name": "tokenId",398 "type": "uint256"399 }400 ],401 "name": "transfer",402 "outputs": [],403 "stateMutability": "payable",404 "type": "function"405 }406]tests/src/eth/util/helpers.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/util/helpers.ts
@@ -0,0 +1,116 @@
+import { ApiPromise } from "@polkadot/api";
+import { addressToEvm, evmToAddress } from "@polkadot/util-crypto";
+import Web3 from "web3";
+import usingApi, { submitTransactionAsync } from "../../substrate/substrate-api";
+import { IKeyringPair } from '@polkadot/types/types';
+import { expect } from "chai";
+import { getGenericResult } from "../../util/helpers";
+
+let web3Connected = false;
+export async function usingWeb3<T>(cb: (web3: Web3) => Promise<T> | T): Promise<T> {
+ if (web3Connected) throw new Error('do not nest usingWeb3 calls');
+ web3Connected = true;
+
+ const provider = new Web3.providers.WebsocketProvider("http://localhost:9944");
+ const web3 = new Web3(provider);
+
+ try {
+ return await cb(web3);
+ } finally {
+ // provider.disconnect(3000, 'normal disconnect');
+ provider.connection.close();
+ web3Connected = false;
+ }
+}
+
+export function collectionIdToAddress(address: number): string {
+ if (address >= 0xffffffff || address < 0) throw new Error('id overflow');
+ const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,
+ address >> 24,
+ (address >> 16) & 0xff,
+ (address >> 8) & 0xff,
+ address & 0xff,
+ ]);
+ return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));
+}
+
+export function createEthAccount(web3: Web3) {
+ const account = web3.eth.accounts.create();
+ web3.eth.accounts.wallet.add(account.privateKey);
+ return account.address;
+}
+
+export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount: number) {
+ const tx = api.tx.balances.transfer(evmToAddress(target), amount);
+ const events = await submitTransactionAsync(source, tx);
+ const result = getGenericResult(events);
+ expect(result.success).to.be.true;
+}
+
+export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise }) => any, opts: { only?: boolean, skip?: boolean } = {}) {
+ let i: any = it;
+ if (opts.only) i = i.only;
+ else if (opts.skip) i = i.skip;
+ i(name, async () => {
+ await usingApi(async api => {
+ await usingWeb3(async web3 => {
+ await cb({ api, web3 });
+ });
+ });
+ });
+}
+itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise }) => any) => itWeb3(name, cb, { only: true });
+itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise }) => any) => itWeb3(name, cb, { skip: true });
+
+export async function generateSubstrateEthPair(web3: Web3) {
+ let account = web3.eth.accounts.create();
+ const evm = evmToAddress(account.address);
+}
+
+type NormalizedEvent = {
+ address: string,
+ event: string,
+ args: { [key: string]: string }
+};
+
+export function normalizeEvents(events: any): NormalizedEvent[] {
+ const output = [];
+ for (let key of Object.keys(events)) {
+ if (key.match(/^[0-9]+$/)) {
+ output.push(events[key]);
+ } else if (Array.isArray(events[key])) {
+ output.push(...events[key]);
+ } else {
+ output.push(events[key]);
+ }
+ }
+ output.sort((a, b) => a.logIndex - b.logIndex);
+ return output.map(({ address, event, returnValues }) => {
+ const args: { [key: string]: string } = {};
+ for (let key of Object.keys(returnValues)) {
+ if (!key.match(/^[0-9]+$/)) {
+ args[key] = returnValues[key];
+ }
+ }
+ return {
+ address,
+ event,
+ args,
+ };
+ });
+}
+
+export async function recordEvents(contract: any, action: () => Promise<void>): Promise<NormalizedEvent[]> {
+ const out: any = [];
+ contract.events.allEvents((_: any, event: any) => {
+ out.push(event);
+ });
+ await action();
+ return normalizeEvents(out);
+}
+
+export function subToEth(eth: string): string {
+ const bytes = addressToEvm(eth);
+ const string = '0x' + Buffer.from(bytes).toString('hex');
+ return Web3.utils.toChecksumAddress(string);
+}
\ No newline at end of file