From 6d61ee2847b488a455514637020bff704169f8c4 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Wed, 02 Jun 2021 19:55:12 +0000 Subject: [PATCH] test: basic evm integration tests --- --- /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 --- /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 --- /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 --- /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 --- /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 --- /dev/null +++ b/tests/src/eth/nonFungibleAbi.json @@ -0,0 +1,406 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "res_name", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "res_symbol", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file --- /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(cb: (web3: Web3) => Promise | T): Promise { + 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): Promise { + 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 -- gitstuff