git.delta.rocks / unique-network / refs/commits / 16ee61f3b724

difftreelog

test evm payable

Yaroslav Bolyukin2021-08-23parent: #d614bc7.patch.diff
in: master

3 files changed

addedtests/src/eth/payable.test.tsdiffbeforeafterboth
--- /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
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
--- 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, {
modifiedtests/src/substrate/get-balance.tsdiffbeforeafterboth
6import { ApiPromise } from '@polkadot/api';6import { ApiPromise } from '@polkadot/api';
7import {AccountInfo} from '@polkadot/types/interfaces/system';7import {AccountInfo} from '@polkadot/types/interfaces/system';
8import promisifySubstrate from './promisify-substrate';8import promisifySubstrate from './promisify-substrate';
9import { IKeyringPair } from '@polkadot/types/types';
10import { submitTransactionAsync } from './substrate-api';
11import { getGenericResult } from '../util/helpers';
12import { expect } from 'chai';
913
10export default async function getBalance(api: ApiPromise, accounts: string[]): Promise<Array<bigint>> {14export default async function getBalance(api: ApiPromise, accounts: string[]): Promise<Array<bigint>> {
11 const balance = promisifySubstrate(api, (acc: string[]) => api.query.system.account.multi(acc));15 const balance = promisifySubstrate(api, (acc: string[]) => api.query.system.account.multi(acc));
12 const responce = await balance(accounts) as unknown as AccountInfo[];16 const responce = await balance(accounts) as unknown as AccountInfo[];
13 return responce.map((r) => r.data.free.toBigInt().valueOf());17 return responce.map((r) => r.data.free.toBigInt().valueOf());
14}18}
1519
20export async function getBalanceSingle(api: ApiPromise, account: string): Promise<bigint> {
21 return (await getBalance(api, [account]))[0];
22}
23
24export async function transferBalanceExpectSuccess(api: ApiPromise, from: IKeyringPair, to: string, amount: bigint | string) {
25 const tx = api.tx.balances.transfer(to, amount);
26 const events = await submitTransactionAsync(from, tx);
27 const result = getGenericResult(events);
28 expect(result.success).to.be.true;
29}