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, notFalsyOrZeroAddress } from './misc-utils'; import { tEthereumAddress, eContractid, tStringTokenSmallUnits, eEthereumNetwork, AavePools, iParamsPerNetwork, iParamsPerPool, ePolygonNetwork, eXDaiNetwork, eNetwork, iEthereumParamsPerNetwork, iPolygonParamsPerNetwork, iXDaiParamsPerNetwork, eOptimismNetwork, iOptimismParamsPerNetwork, } from './types'; import { MintableERC20 } from '../types/MintableERC20'; import { Artifact } from 'hardhat/types'; import { Artifact as BuidlerArtifact } from '@nomiclabs/buidler/types'; import { verifyEtherscanContract } from './etherscan-verification'; import { getFirstSigner, getIErc20Detailed } from './contracts-getters'; import { usingTenderly, verifyAtTenderly } from './tenderly-utils'; import { usingPolygon, verifyAtPolygon } from './polygon-utils'; import { ConfigNames, loadPoolConfig } from './configuration'; import { ZERO_ADDRESS } from './constants'; import { getDefenderRelaySigner, usingDefender } from './defender-utils'; export type MockTokenMap = { [symbol: string]: MintableERC20 }; export const registerContractInJsonDb = async (contractId: string, contractInstance: Contract) => { const currentNetwork = DRE.network.name; const FORK = process.env.FORK; if (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 => { const ethersSigners = await Promise.all(await DRE.ethers.getSigners()); if (usingDefender()) { const [, ...users] = ethersSigners; return [await getDefenderRelaySigner(), ...users]; } return ethersSigners; }; export const getEthersSignersAddresses = async (): Promise => await Promise.all((await getEthersSigners()).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)) .connect(await getFirstSigner()) .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 (verify) { await verifyContract(id, instance, 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, tenderly } = param as iEthereumParamsPerNetwork; const { matic, mumbai } = param as iPolygonParamsPerNetwork; const { xdai } = param as iXDaiParamsPerNetwork; const { optimism, optimismKovan } = param as iOptimismParamsPerNetwork; if (process.env.FORK) { return param[process.env.FORK as eNetwork] as T; } 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.tenderly: return tenderly; case ePolygonNetwork.matic: return matic; case ePolygonNetwork.mumbai: return mumbai; case eXDaiNetwork.xdai: return xdai; case eOptimismNetwork.optimismKovan: return optimismKovan; case eOptimismNetwork.optimism: return optimism; } }; export const getOptionalParamAddressPerNetwork = ( param: iParamsPerNetwork | undefined | null, network: eNetwork ) => { if (!param) { return ZERO_ADDRESS; } return getParamPerNetwork(param, network); }; export const getParamPerPool = ( { proto, amm, matic, optimism }: iParamsPerPool, pool: AavePools ) => { switch (pool) { case AavePools.proto: return proto; case AavePools.amm: return amm; case AavePools.matic: return matic; case AavePools.optimism: return optimism; 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] ); }; export const verifyContract = async ( id: string, instance: Contract, args: (string | string[])[] ) => { if (usingPolygon()) { await verifyAtPolygon(id, instance, args); } else { if (usingTenderly()) { await verifyAtTenderly(id, instance); } await verifyEtherscanContract(instance.address, args); } return instance; }; export const getContractAddressWithJsonFallback = async ( id: string, pool: ConfigNames ): Promise => { const poolConfig = loadPoolConfig(pool); const network = DRE.network.name; const db = getDb(); const contractAtMarketConfig = getOptionalParamAddressPerNetwork(poolConfig[id], network); if (notFalsyOrZeroAddress(contractAtMarketConfig)) { return contractAtMarketConfig; } const contractAtDb = await getDb().get(`${id}.${DRE.network.name}`).value(); if (contractAtDb?.address) { return contractAtDb.address as tEthereumAddress; } throw Error(`Missing contract address ${id} at Market config and JSON local db`); };