git.delta.rocks / unique-network / refs/commits / f553bd96694d

difftreelog

source

tests/src/eth/util/helpers.ts8.3 KiBsourcehistory
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56// eslint-disable-next-line @typescript-eslint/triple-slash-reference7/// <reference path="helpers.d.ts" />89import {ApiPromise} from '@polkadot/api';10import {addressToEvm, evmToAddress} from '@polkadot/util-crypto';11import Web3 from 'web3';12import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api';13import {IKeyringPair} from '@polkadot/types/types';14import {expect} from 'chai';15import {getGenericResult} from '../../util/helpers';16import * as solc from 'solc';17import config from '../../config';18import privateKey from '../../substrate/privateKey';19import contractHelpersAbi from './contractHelpersAbi.json';20import getBalance from '../../substrate/get-balance';2122export const GAS_ARGS = {gas: 0x1000000, gasPrice: '0x01'};2324let web3Connected = false;25export async function usingWeb3<T>(cb: (web3: Web3) => Promise<T> | T): Promise<T> {26  if (web3Connected) throw new Error('do not nest usingWeb3 calls');27  web3Connected = true;2829  const provider = new Web3.providers.WebsocketProvider(config.substrateUrl);30  const web3 = new Web3(provider);3132  try {33    return await cb(web3);34  } finally {35    // provider.disconnect(3000, 'normal disconnect');36    provider.connection.close();37    web3Connected = false;38  }39}4041export function collectionIdToAddress(address: number): string {42  if (address >= 0xffffffff || address < 0) throw new Error('id overflow');43  const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,44    address >> 24,45    (address >> 16) & 0xff,46    (address >> 8) & 0xff,47    address & 0xff,48  ]);49  return Web3.utils.toChecksumAddress('0x' + buf.toString('hex'));50}5152export function createEthAccount(web3: Web3) {53  const account = web3.eth.accounts.create();54  web3.eth.accounts.wallet.add(account.privateKey);55  return account.address;56}5758export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3) {59  const alice = privateKey('//Alice');60  const account = createEthAccount(web3);61  await transferBalanceToEth(api, alice, account);6263  return account;64}6566export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount = 999999999999999) {67  const tx = api.tx.balances.transfer(evmToAddress(target), amount);68  const events = await submitTransactionAsync(source, tx);69  const result = getGenericResult(events);70  expect(result.success).to.be.true;71}7273export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise }) => any, opts: { only?: boolean, skip?: boolean } = {}) {74  let i: any = it;75  if (opts.only) i = i.only;76  else if (opts.skip) i = i.skip;77  i(name, async () => {78    await usingApi(async api => {79      await usingWeb3(async web3 => {80        await cb({api, web3});81      });82    });83  });84}85itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise }) => any) => itWeb3(name, cb, {only: true});86itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise }) => any) => itWeb3(name, cb, {skip: true});8788export async function generateSubstrateEthPair(web3: Web3) {89  const account = web3.eth.accounts.create();90  evmToAddress(account.address);91}9293type NormalizedEvent = {94    address: string,95    event: string,96    args: { [key: string]: string }97};9899export function normalizeEvents(events: any): NormalizedEvent[] {100  const output = [];101  for (const key of Object.keys(events)) {102    if (key.match(/^[0-9]+$/)) {103      output.push(events[key]);104    } else if (Array.isArray(events[key])) {105      output.push(...events[key]);106    } else {107      output.push(events[key]);108    }109  }110  output.sort((a, b) => a.logIndex - b.logIndex);111  return output.map(({address, event, returnValues}) => {112    const args: { [key: string]: string } = {};113    for (const key of Object.keys(returnValues)) {114      if (!key.match(/^[0-9]+$/)) {115        args[key] = returnValues[key];116      }117    }118    return {119      address,120      event,121      args,122    };123  });124}125126export async function recordEvents(contract: any, action: () => Promise<void>): Promise<NormalizedEvent[]> {127  const out: any = [];128  contract.events.allEvents((_: any, event: any) => {129    out.push(event);130  });131  await action();132  return normalizeEvents(out);133}134135export function subToEthLowercase(eth: string): string {136  const bytes = addressToEvm(eth);137  return '0x' + Buffer.from(bytes).toString('hex');138}139140export function subToEth(eth: string): string {141  return Web3.utils.toChecksumAddress(subToEthLowercase(eth));142}143144export function compileContract(name: string, src: string) {145  const out = JSON.parse(solc.compile(JSON.stringify({146    language: 'Solidity',147    sources: {148      [`${name}.sol`]: {149        content: `150          // SPDX-License-Identifier: UNLICENSED151          pragma solidity ^0.8.6;152153          ${src}154        `,155      },156    },157    settings: {158      outputSelection: {159        '*': {160          '*': ['*'],161        },162      },163    },164  }))).contracts[`${name}.sol`][name];165166  return {167    abi: out.abi,168    object: '0x' + out.evm.bytecode.object,169  };170}171172export async function deployFlipper(web3: Web3, deployer: string) {173  const compiled = compileContract('Flipper', `174    contract Flipper {175      bool value = false;176      function flip() public {177        value = !value;178      }179      function getValue() public view returns (bool) {180        return value;181      }182    }183  `);184  const flipperContract = new web3.eth.Contract(compiled.abi, undefined, {185    data: compiled.object,186    from: deployer,187    ...GAS_ARGS,188  });189  const flipper = await flipperContract.deploy({data: compiled.object}).send({from: deployer});190191  return flipper;192}193194export async function deployCollector(web3: Web3, deployer: string) {195  const compiled = compileContract('Collector', `196    contract Collector {197      uint256 collected;198      fallback() external payable {199        giveMoney();200      }201      function giveMoney() public payable {202        collected += msg.value;203      }204      function getCollected() public view returns (uint256) {205        return collected;206      }207      function getUnaccounted() public view returns (uint256) {208        return address(this).balance - collected;209      }210211      function withdraw(address payable target) public {212        target.transfer(collected);213        collected = 0;214      }215    }216  `);217  const collectorContract = new web3.eth.Contract(compiled.abi, undefined, {218    data: compiled.object,219    from: deployer,220    ...GAS_ARGS,221  });222  const collector = await collectorContract.deploy({data: compiled.object}).send({from: deployer});223224  return collector;225}226227export function contractHelpers(web3: Web3, caller: string) {228  return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS});229}230231/**232 * Execute ethereum method call using substrate account233 * @param to target contract234 * @param mkTx - closure, receiving `contract.methods`, and returning method call,235 * to be used as following (assuming `to` = erc20 contract):236 * `m => m.transfer(to, amount)`237 * 238 * # Example239 * ```ts240 * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount));241 * ```242 */243export async function executeEthTxOnSub(api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) {244  const tx = api.tx.evm.call(245    subToEth(from.address),246    to.options.address,247    mkTx(to.methods).encodeABI(),248    value,249    GAS_ARGS.gas,250    GAS_ARGS.gasPrice,251    null,252  );253  const events = await submitTransactionAsync(from, tx);254  expect(events.some(({event: {section, method}}) => section == 'evm' && method == 'Executed')).to.be.true;255}256257export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise<bigint> {258  return (await getBalance(api, [evmToAddress(address)]))[0];259}260261/**262 * Measure how much gas given closure consumes263 * 264 * @param user which user balance will be checked265 */266export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise<any>): Promise<bigint> {267  const before = await ethBalanceViaSub(api, user);268269  await call();270271  const after = await ethBalanceViaSub(api, user);272273  // Can't use .to.be.less, because chai doesn't supports bigint274  expect(after < before).to.be.true;275276  return before - after;277}