import { Contract, Signer, utils, ethers, BigNumberish } from 'ethers'; import { signTypedData_v4 } from 'eth-sig-util'; import { fromRpcSig, ECDSASignature } from 'ethereumjs-util'; import BigNumber from 'bignumber.js'; import { getDb, DRE, waitForTx } from './misc-utils'; import { tEthereumAddress, eContractid, tStringTokenSmallUnits, eEthereumNetwork, AavePools, iParamsPerNetwork, iParamsPerPool, ePolygonNetwork, eXDaiNetwork, eNetwork, iParamsPerNetworkAll, iEthereumParamsPerNetwork, iPolygonParamsPerNetwork, iXDaiParamsPerNetwork, } from './types'; import { MintableERC20 } from '../types/MintableERC20'; import { Artifact } from 'hardhat/types'; import { Artifact as BuidlerArtifact } from '@nomiclabs/buidler/types'; import { verifyContract } from './etherscan-verification'; import { getIErc20Detailed } from './contracts-getters'; import { usingTenderly } from './tenderly-utils'; export type MockTokenMap = { [symbol: string]: MintableERC20 }; export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => { const currentNetwork = DRE.network.name; const MAINNET_FORK = process.env.MAINNET_FORK === 'true'; if (MAINNET_FORK || (currentNetwork !== 'hardhat' && !currentNetwork.includes('coverage'))) { console.log(`*** ${contractId} ***\n`); console.log(`Network: ${currentNetwork}`); console.log(`tx: ${contractInstance.deployTransaction.hash}`); console.log(`contract address: ${contractInstance.address}`); console.log(`deployer address: ${contractInstance.deployTransaction.from}`); console.log(`gas price: ${contractInstance.deployTransaction.gasPrice}`); console.log(`gas used: ${contractInstance.deployTransaction.gasLimit}`); console.log(`\n******`); console.log(); } await getDb() .set(`${contractId}.${currentNetwork}`, { address: contractInstance.address, deployer: contractInstance.deployTransaction.from, }) .write(); }; export const insertContractAddressInDb = async (id: eContractid, address: tEthereumAddress) => await getDb() .set(`${id}.${DRE.network.name}`, { address, }) .write(); export const rawInsertContractAddressInDb = async (id: string, address: tEthereumAddress) => await getDb() .set(`${id}.${DRE.network.name}`, { address, }) .write(); export const getEthersSigners = async (): Promise => await Promise.all(await DRE.ethers.getSigners()); export const getEthersSignersAddresses = async (): Promise => await Promise.all((await DRE.ethers.getSigners()).map((signer) => signer.getAddress())); export const getCurrentBlock = async () => { return DRE.ethers.provider.getBlockNumber(); }; export const decodeAbiNumber = (data: string): number => parseInt(utils.defaultAbiCoder.decode(['uint256'], data).toString()); export const deployContract = async ( contractName: string, args: any[] ): Promise => { const contract = (await (await DRE.ethers.getContractFactory(contractName)).deploy( ...args )) as ContractType; await waitForTx(contract.deployTransaction); await registerContractInJsonDb(contractName, contract); return contract; }; export const withSaveAndVerify = async ( instance: ContractType, id: string, args: (string | string[])[], verify?: boolean ): Promise => { await waitForTx(instance.deployTransaction); await registerContractInJsonDb(id, instance); if (usingTenderly()) { console.log(); console.log('Doing Tenderly contract verification of', id); await (DRE as any).tenderlyRPC.verify({ name: id, address: instance.address, }); console.log(`Verified ${id} at Tenderly!`); console.log(); } if (verify) { await verifyContract(instance.address, args); } return instance; }; export const getContract = async ( contractName: string, address: string ): Promise => (await DRE.ethers.getContractAt(contractName, address)) as ContractType; export const linkBytecode = (artifact: BuidlerArtifact | Artifact, libraries: any) => { let bytecode = artifact.bytecode; for (const [fileName, fileReferences] of Object.entries(artifact.linkReferences)) { for (const [libName, fixups] of Object.entries(fileReferences)) { const addr = libraries[libName]; if (addr === undefined) { continue; } for (const fixup of fixups) { bytecode = bytecode.substr(0, 2 + fixup.start * 2) + addr.substr(2) + bytecode.substr(2 + (fixup.start + fixup.length) * 2); } } } return bytecode; }; export const getParamPerNetwork = (param: iParamsPerNetwork, network: eNetwork) => { const { main, ropsten, kovan, coverage, buidlerevm, tenderlyMain, } = param as iEthereumParamsPerNetwork; const { matic, mumbai } = param as iPolygonParamsPerNetwork; const { xdai } = param as iXDaiParamsPerNetwork; const MAINNET_FORK = process.env.MAINNET_FORK === 'true'; if (MAINNET_FORK) { return main; } switch (network) { case eEthereumNetwork.coverage: return coverage; case eEthereumNetwork.buidlerevm: return buidlerevm; case eEthereumNetwork.hardhat: return buidlerevm; case eEthereumNetwork.kovan: return kovan; case eEthereumNetwork.ropsten: return ropsten; case eEthereumNetwork.main: return main; case eEthereumNetwork.tenderlyMain: return tenderlyMain; case ePolygonNetwork.matic: return matic; case ePolygonNetwork.mumbai: return mumbai; case eXDaiNetwork.xdai: return xdai; } }; export const getParamPerPool = ({ proto, amm, matic }: iParamsPerPool, pool: AavePools) => { switch (pool) { case AavePools.proto: return proto; case AavePools.amm: return amm; case AavePools.matic: return matic; default: return proto; } }; export const convertToCurrencyDecimals = async (tokenAddress: tEthereumAddress, amount: string) => { const token = await getIErc20Detailed(tokenAddress); let decimals = (await token.decimals()).toString(); return ethers.utils.parseUnits(amount, decimals); }; export const convertToCurrencyUnits = async (tokenAddress: string, amount: string) => { const token = await getIErc20Detailed(tokenAddress); let decimals = new BigNumber(await token.decimals()); const currencyUnit = new BigNumber(10).pow(decimals); const amountInCurrencyUnits = new BigNumber(amount).div(currencyUnit); return amountInCurrencyUnits.toFixed(); }; export const buildPermitParams = ( chainId: number, token: tEthereumAddress, revision: string, tokenName: string, owner: tEthereumAddress, spender: tEthereumAddress, nonce: number, deadline: string, value: tStringTokenSmallUnits ) => ({ types: { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], Permit: [ { name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }, primaryType: 'Permit' as const, domain: { name: tokenName, version: revision, chainId: chainId, verifyingContract: token, }, message: { owner, spender, value, nonce, deadline, }, }); export const getSignatureFromTypedData = ( privateKey: string, typedData: any // TODO: should be TypedData, from eth-sig-utils, but TS doesn't accept it ): ECDSASignature => { const signature = signTypedData_v4(Buffer.from(privateKey.substring(2, 66), 'hex'), { data: typedData, }); return fromRpcSig(signature); }; export const buildLiquiditySwapParams = ( assetToSwapToList: tEthereumAddress[], minAmountsToReceive: BigNumberish[], swapAllBalances: BigNumberish[], permitAmounts: BigNumberish[], deadlines: BigNumberish[], v: BigNumberish[], r: (string | Buffer)[], s: (string | Buffer)[], useEthPath: boolean[] ) => { return ethers.utils.defaultAbiCoder.encode( [ 'address[]', 'uint256[]', 'bool[]', 'uint256[]', 'uint256[]', 'uint8[]', 'bytes32[]', 'bytes32[]', 'bool[]', ], [ assetToSwapToList, minAmountsToReceive, swapAllBalances, permitAmounts, deadlines, v, r, s, useEthPath, ] ); }; export const buildRepayAdapterParams = ( collateralAsset: tEthereumAddress, collateralAmount: BigNumberish, rateMode: BigNumberish, permitAmount: BigNumberish, deadline: BigNumberish, v: BigNumberish, r: string | Buffer, s: string | Buffer, useEthPath: boolean ) => { return ethers.utils.defaultAbiCoder.encode( ['address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint8', 'bytes32', 'bytes32', 'bool'], [collateralAsset, collateralAmount, rateMode, permitAmount, deadline, v, r, s, useEthPath] ); }; export const buildFlashLiquidationAdapterParams = ( collateralAsset: tEthereumAddress, debtAsset: tEthereumAddress, user: tEthereumAddress, debtToCover: BigNumberish, useEthPath: boolean ) => { return ethers.utils.defaultAbiCoder.encode( ['address', 'address', 'address', 'uint256', 'bool'], [collateralAsset, debtAsset, user, debtToCover, useEthPath] ); };