From 52040fc4fc8cd9cdff84e8e3ab4fa0c8602262a5 Mon Sep 17 00:00:00 2001 From: kozyrevdev <73348153+kozyrevdev@users.noreply.github.com> Date: Wed, 25 Aug 2021 20:29:54 +0000 Subject: [PATCH] Merge pull request #185 from UniqueNetwork/test/evm-payable-tests EVM payable tests --- --- /dev/null +++ b/tests/src/eth/payable.test.ts @@ -0,0 +1,98 @@ +import { expect } from 'chai'; +import privateKey from '../substrate/privateKey'; +import { submitTransactionAsync } from '../substrate/substrate-api'; +import waitNewBlocks from '../substrate/wait-new-blocks'; +import { createEthAccountWithBalance, deployCollector, GAS_ARGS, itWeb3, subToEth } from './util/helpers'; +import {evmToAddress} from '@polkadot/util-crypto'; +import { getGenericResult } from '../util/helpers'; +import { getBalanceSingle, transferBalanceExpectSuccess } from '../substrate/get-balance'; + +describe('EVM payable contracts', ()=>{ + itWeb3('Evm contract can receive wei from eth account', async ({api, web3}) => { + const deployer = await createEthAccountWithBalance(api, web3); + const contract = await deployCollector(web3, deployer); + + await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', ...GAS_ARGS}); + await waitNewBlocks(api, 1); + + expect(await contract.methods.getCollected().call()).to.be.equal('10000'); + }); + + itWeb3('Evm contract can receive wei from substrate account', async ({api, web3}) => { + const deployer = await createEthAccountWithBalance(api, web3); + const contract = await deployCollector(web3, deployer); + const alice = privateKey('//Alice'); + + // Transaction fee/value will be payed from subToEth(sender) evm balance, + // which is backed by evmToAddress(subToEth(sender)) substrate balance + await transferBalanceExpectSuccess(api, alice, evmToAddress(subToEth(alice.address)), '1000000000000'); + + { + const tx = api.tx.evm.call( + subToEth(alice.address), + contract.options.address, + contract.methods.giveMoney().encodeABI(), + '10000', + GAS_ARGS.gas, + GAS_ARGS.gasPrice, + null, + ); + const events = await submitTransactionAsync(alice, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + } + + expect(await contract.methods.getCollected().call()).to.be.equal('10000'); + }); + + // We can't handle sending balance to backing storage of evm balance, because evmToAddress operation is irreversible + itWeb3('Wei sent directly to backing storage of evm contract balance is unaccounted', async({api, web3}) => { + const deployer = await createEthAccountWithBalance(api, web3); + const contract = await deployCollector(web3, deployer); + const alice = privateKey('//Alice'); + + await transferBalanceExpectSuccess(api, alice, evmToAddress(contract.options.address), '10000'); + + expect(await contract.methods.getUnaccounted().call()).to.be.equal('10000'); + }); + + itWeb3('Balance can be retrieved from evm contract', async({api, web3}) => { + const FEE_BALANCE = 10n ** 18n; + const CONTRACT_BALANCE = 10n ** 14n; + + const deployer = await createEthAccountWithBalance(api, web3); + const contract = await deployCollector(web3, deployer); + const alice = privateKey('//Alice'); + + await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), ...GAS_ARGS}); + await waitNewBlocks(api, 1); + + const receiver = privateKey(`//Receiver${Date.now()}`); + + // First receive balance on eth balance of bob + { + const ethReceiver = subToEth(receiver.address); + expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0'); + await contract.methods.withdraw(ethReceiver).send({from: deployer}); + expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString()); + } + + // Some balance is required to pay fee for evm.withdraw call + await transferBalanceExpectSuccess(api, alice, receiver.address, FEE_BALANCE.toString()); + + // Withdraw balance from eth to substrate + { + const initialReceiverBalance = await getBalanceSingle(api, receiver.address); + const tx = api.tx.evm.withdraw( + subToEth(receiver.address), + CONTRACT_BALANCE.toString(), + ); + const events = await submitTransactionAsync(receiver, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + const finalReceiverBalance = await getBalanceSingle(api, receiver.address); + + expect(finalReceiverBalance > initialReceiverBalance).to.be.true; + } + }); +}); \ No newline at end of file --- a/tests/src/eth/util/helpers.ts +++ b/tests/src/eth/util/helpers.ts @@ -197,16 +197,27 @@ return flipper; } -export async function deployCollector(web3: Web3 & Web3HttpMarker, deployer: string) { +export async function deployCollector(web3: Web3, deployer: string) { const compiled = compileContract('Collector', ` contract Collector { uint256 collected; + fallback() external payable { + giveMoney(); + } function giveMoney() public payable { collected += msg.value; } function getCollected() public view returns (uint256) { return collected; } + function getUnaccounted() public view returns (uint256) { + return address(this).balance - collected; + } + + function withdraw(address payable target) public { + target.transfer(collected); + collected = 0; + } } `); const Collector = new web3.eth.Contract(compiled.abi, undefined, { --- a/tests/src/substrate/get-balance.ts +++ b/tests/src/substrate/get-balance.ts @@ -6,9 +6,24 @@ import { ApiPromise } from '@polkadot/api'; import {AccountInfo} from '@polkadot/types/interfaces/system'; import promisifySubstrate from './promisify-substrate'; +import { IKeyringPair } from '@polkadot/types/types'; +import { submitTransactionAsync } from './substrate-api'; +import { getGenericResult } from '../util/helpers'; +import { expect } from 'chai'; export default async function getBalance(api: ApiPromise, accounts: string[]): Promise> { const balance = promisifySubstrate(api, (acc: string[]) => api.query.system.account.multi(acc)); const responce = await balance(accounts) as unknown as AccountInfo[]; return responce.map((r) => r.data.free.toBigInt().valueOf()); } + +export async function getBalanceSingle(api: ApiPromise, account: string): Promise { + return (await getBalance(api, [account]))[0]; +} + +export async function transferBalanceExpectSuccess(api: ApiPromise, from: IKeyringPair, to: string, amount: bigint | string) { + const tx = api.tx.balances.transfer(to, amount); + const events = await submitTransactionAsync(from, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; +} \ No newline at end of file -- gitstuff