147 lines
5.2 KiB
TypeScript
147 lines
5.2 KiB
TypeScript
|
import { ERC20_TOKEN_ABI } from 'src/core/blockchain/web3-public-service/web3-public/evm-web3-public/constants/erc-20-token-abi';
|
||
|
import { generateAccountsFromMnemonic } from '__tests__/utils/accounts-from-mnemonic';
|
||
|
import {
|
||
|
publicProvidersRPC,
|
||
|
publicProvidersSupportServerUrls
|
||
|
} from '__tests__/utils/configuration';
|
||
|
import { DEFAULT_MNEMONIC } from '__tests__/utils/constants/mnemonic';
|
||
|
import { TOKENS_HOLDERS } from '__tests__/utils/tokens';
|
||
|
import axios from 'axios';
|
||
|
import BigNumber from 'bignumber.js';
|
||
|
import { BlockchainName } from 'src/core/blockchain/models/blockchain-name';
|
||
|
import { Configuration } from 'src/core/sdk/models/configuration';
|
||
|
import * as util from 'util';
|
||
|
import Web3 from 'web3';
|
||
|
import { HttpProvider } from 'web3-core';
|
||
|
import { Token } from 'src/common/tokens';
|
||
|
import { CHAIN_TYPE } from 'src/core/blockchain/models/chain-type';
|
||
|
|
||
|
export class Chain {
|
||
|
private static walletsNumber = 10;
|
||
|
|
||
|
private static wallets = generateAccountsFromMnemonic(DEFAULT_MNEMONIC, Chain.walletsNumber);
|
||
|
|
||
|
private static web3Instances = Object.fromEntries(
|
||
|
Object.entries(publicProvidersRPC).map(([key, value]) => {
|
||
|
const web3 = new Web3(value);
|
||
|
Chain.wallets.forEach(wallet => {
|
||
|
web3.eth.accounts.wallet.add(wallet.privateKey);
|
||
|
});
|
||
|
return [key, web3];
|
||
|
})
|
||
|
);
|
||
|
|
||
|
private static get accounts(): string[] {
|
||
|
return Chain.wallets.map(wallet => wallet.address);
|
||
|
}
|
||
|
|
||
|
public static async reset(
|
||
|
blockchainName: BlockchainName,
|
||
|
blockNumber?: number
|
||
|
): Promise<Chain> {
|
||
|
const rpcUrl = publicProvidersRPC[blockchainName];
|
||
|
const resetUrl = publicProvidersSupportServerUrls[blockchainName];
|
||
|
|
||
|
if (!rpcUrl || !resetUrl) {
|
||
|
throw new Error(`RPC for ${blockchainName} was not set.`);
|
||
|
}
|
||
|
await axios.post(resetUrl, { blockNumber });
|
||
|
|
||
|
return new Chain(Chain.web3Instances[blockchainName], Chain.accounts);
|
||
|
}
|
||
|
|
||
|
private constructor(public web3: Web3, public accounts: string[]) {}
|
||
|
|
||
|
public async getConfiguration(): Promise<Configuration> {
|
||
|
return {
|
||
|
rpcProviders: Object.fromEntries(
|
||
|
Object.entries(publicProvidersRPC).map(([key, value]) => [
|
||
|
key,
|
||
|
{
|
||
|
mainRpc: value
|
||
|
}
|
||
|
])
|
||
|
),
|
||
|
walletProvider: {
|
||
|
[CHAIN_TYPE.EVM]: {
|
||
|
core: this.web3,
|
||
|
address: this.accounts[0]
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
public async increaseTokensBalance(
|
||
|
token: Token,
|
||
|
amount: number | string | BigNumber,
|
||
|
options: { inEtherUnits: boolean } = { inEtherUnits: false }
|
||
|
): Promise<void> {
|
||
|
const weiAmount = options.inEtherUnits
|
||
|
? new BigNumber(amount).multipliedBy(10 ** token.decimals)
|
||
|
: new BigNumber(amount);
|
||
|
const holder = this.getTokenHolderAddress(token);
|
||
|
await this.setBalance(holder, 1, {
|
||
|
inEtherUnits: true
|
||
|
});
|
||
|
|
||
|
const tokenContract = new this.web3.eth.Contract(ERC20_TOKEN_ABI, token.address);
|
||
|
const holderBalance = new BigNumber(await tokenContract.methods.balanceOf(holder).call());
|
||
|
if (holderBalance.lt(weiAmount)) {
|
||
|
throw new Error(`${
|
||
|
token.symbol
|
||
|
} holder balance is not enough to transfer, set other holder to config.
|
||
|
Holder balance is ${holderBalance.toFixed()}, but ${weiAmount.toFixed(
|
||
|
0
|
||
|
)} is required.`);
|
||
|
}
|
||
|
|
||
|
await this.impersonateAccount(holder);
|
||
|
await tokenContract.methods
|
||
|
.transfer(this.accounts[0], weiAmount.toFixed(0))
|
||
|
.send({ from: holder, gas: 100_000 });
|
||
|
await this.stopImpersonateAccount(holder);
|
||
|
}
|
||
|
|
||
|
private getTokenHolderAddress(token: Token): string {
|
||
|
const holder =
|
||
|
TOKENS_HOLDERS[token.blockchain as keyof typeof TOKENS_HOLDERS]?.[token.address];
|
||
|
if (!holder) {
|
||
|
throw new Error(`Holder for ${token.symbol} is not specified.`);
|
||
|
}
|
||
|
return holder;
|
||
|
}
|
||
|
|
||
|
public async impersonateAccount(address: string): Promise<void> {
|
||
|
await this.sendRpcRequest('hardhat_impersonateAccount', [address]);
|
||
|
}
|
||
|
|
||
|
public async stopImpersonateAccount(address: string): Promise<void> {
|
||
|
await this.sendRpcRequest('hardhat_stopImpersonatingAccount', [address]);
|
||
|
}
|
||
|
|
||
|
public async setBalance(
|
||
|
address: string,
|
||
|
value: number | string | BigNumber,
|
||
|
options: { inEtherUnits: boolean } = { inEtherUnits: false }
|
||
|
): Promise<void> {
|
||
|
const bnValue = options.inEtherUnits
|
||
|
? new BigNumber(value).multipliedBy(10 ** 18)
|
||
|
: new BigNumber(value);
|
||
|
|
||
|
const hexValue = `0x${bnValue.toString(16)}`;
|
||
|
|
||
|
await this.sendRpcRequest('hardhat_setBalance', [address, hexValue]);
|
||
|
}
|
||
|
|
||
|
private async sendRpcRequest(method: string, params: string[]): Promise<void> {
|
||
|
await util.promisify(
|
||
|
(this.web3.currentProvider as HttpProvider).send.bind(this.web3.currentProvider)
|
||
|
)({
|
||
|
method,
|
||
|
params,
|
||
|
jsonrpc: '2.0',
|
||
|
id: new Date().getTime()
|
||
|
});
|
||
|
}
|
||
|
}
|