From 55555e0960a4ca784978b5eb883ac3af8e665af8 Mon Sep 17 00:00:00 2001 From: doncesarts Date: Thu, 5 May 2022 14:45:43 +0100 Subject: [PATCH] refactor: refactor seed new allocation task --- README.md | 3 +- hardhat.base.config.ts | 10 +- tasks/registerMerkleDrop.ts | 2 +- tasks/seedNewAllocations.ts | 204 ++++++++++++++++++++---------------- 4 files changed, 121 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 5ec4570..4bb6b27 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ This project uses Hardhat. yarn hardhat deployMerkleDrop --token 0x... --funders 0x...,0x... ### Seeding new allocations (adding a tranche) - + By default this task only prepare the parameters to seed the new allocation, it does not execute it. + If it is needed to execute the transaction then the flag `--isSimulation` must be set to false. # From a local file: yarn hardhat seedNewAllocations --merkle-drop 0x... --balances ./tranche0.json diff --git a/hardhat.base.config.ts b/hardhat.base.config.ts index c511fec..2ec0010 100644 --- a/hardhat.base.config.ts +++ b/hardhat.base.config.ts @@ -16,17 +16,17 @@ const hardhatConfig: HardhatUserConfig = { }, localhost: { url: 'http://localhost:8545' }, fork: { url: 'http://localhost:7545' }, - // export the RPC_URL environment variable to use remote nodes like Alchemy or Infura. eg - // export RPC_URL=https://eth-mainnet.alchemyapi.io/v2/yourApiKey - env: { url: process.env.RPC_URL || '' }, + // export the NODE_URL environment variable to use remote nodes like Alchemy or Infura. eg + // export NODE_URL=https://eth-mainnet.alchemyapi.io/v2/yourApiKey + env: { url: process.env.NODE_URL || '' }, ropsten: { - url: process.env.RPC_URL || '', + url: process.env.NODE_URL || '', accounts: process.env.MNEMONIC ? { mnemonic: process.env.MNEMONIC } : [], gasPrice: 30000000000, blockGasLimit: 8000000, }, kovan: { - url: process.env.RPC_URL || '', + url: process.env.NODE_URL || '', accounts: process.env.MNEMONIC ? { mnemonic: process.env.MNEMONIC } : [], gasPrice: 30000000000, blockGasLimit: 8000000, diff --git a/tasks/registerMerkleDrop.ts b/tasks/registerMerkleDrop.ts index 0cb6a28..c0f97dd 100644 --- a/tasks/registerMerkleDrop.ts +++ b/tasks/registerMerkleDrop.ts @@ -44,7 +44,7 @@ task('registerMerkleDrop', 'Registers a MerkleDrop contract') deployer, ) console.log('Registering MerkleDrop') - let registerTx = await merkleDropTranchesContract.register(merkleDrop) + const registerTx = await merkleDropTranchesContract.register(merkleDrop) console.log(`Sending transaction ${registerTx.hash}`) await registerTx.wait() diff --git a/tasks/seedNewAllocations.ts b/tasks/seedNewAllocations.ts index 5faa465..ffa375d 100644 --- a/tasks/seedNewAllocations.ts +++ b/tasks/seedNewAllocations.ts @@ -3,85 +3,132 @@ import 'tsconfig-paths/register' import { task } from 'hardhat/config' import { URL } from 'url' import { create } from 'ipfs-http-client' -import { constants, BigNumber } from 'ethers' +import { constants, BigNumber, Signer } from 'ethers' import { formatUnits } from 'ethers/lib/utils' +import { DefenderRelayProvider, DefenderRelaySigner } from "defender-relay-client/lib/ethers" +import { Speed } from "defender-relay-client" + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import Confirm from 'prompt-confirm' -import { MerkleDrop__factory, IERC20__factory } from '../types/generated' +import { MerkleDrop__factory, IERC20__factory, IERC20, MerkleDrop } from '../types/generated' import { addressType, JSONBalances, jsonBalancesType } from './params' import { createTreeWithAccounts } from './merkleTree' - +import { boolean } from 'hardhat/internal/core/params/argumentTypes' + +const multisigAddress = "0xF6FF1F7FCEB2cE6d26687EaaB5988b445d0b94a2" + + +const getIPFSUri = async (balances: JSONBalances): Promise => { + + console.log('Pinning file to IPFS') + + const { + protocol, + hostname, + pathname, + } = new URL('https://api.thegraph.com/ipfs/') + const client = create({ + protocol, + host: hostname, + port: 443, + apiPath: `${pathname}/api/v0/`, + }) + + const buffer = Buffer.from(balances.body) + + const addResult = await client.add(buffer, { pin: true }) + const uri = `ipfs://${addResult.cid}` + + console.log('Pinned file', addResult.cid) + return uri; +} + +const getHexRoot = async (balances: JSONBalances): Promise => { + + const merkleTree = createTreeWithAccounts(balances.parsed) + const hexRoot = merkleTree.getHexRoot() + return hexRoot; +} + +async function seedNewAllocations(merkleDropContract: MerkleDrop, hexRoot: string, totalAllocation: BigNumber, uri: string, isSimulation = true) { + console.log(`Seeding new allocations`) + if (isSimulation) { + console.log(`${multisigAddress} must execute ${merkleDropContract.address}.seedNewAllocations(${hexRoot}, ${totalAllocation}, ${uri})`) + + } else { + const seedNewAllocationsTx = await merkleDropContract.seedNewAllocations( + hexRoot, + totalAllocation, + uri + ) + console.log(`Sending transaction ${seedNewAllocationsTx.hash}`) + await seedNewAllocationsTx.wait() + } +} + +async function validateAllowance(tokenContract: IERC20, merkleDrop: string, totalAllocation: BigNumber, isSimulation = true) { + const allowance = await tokenContract.allowance(multisigAddress, merkleDrop) + + if (allowance.lt(totalAllocation)) { + console.log('Approval required; approving infinite') + if (isSimulation) { + console.log(`${multisigAddress} must execute ${tokenContract.address}.approve(${merkleDrop}, ${constants.MaxUint256})`) + } else { + const approvalTx = await tokenContract.approve( + merkleDrop, + constants.MaxUint256 + ) + console.log(`Sending transaction ${approvalTx.hash}`) + await approvalTx.wait() + console.log('Transaction complete') + } + } +} + +export const getDefenderSigner = async (speed: Speed = "fast"): Promise => { + if (!process.env.DEFENDER_API_KEY || !process.env.DEFENDER_API_SECRET) { + console.error(`Defender env vars DEFENDER_API_KEY and/or DEFENDER_API_SECRET have not been set`) + process.exit(1) + } + if (!["safeLow", "average", "fast", "fastest"].includes(speed)) { + console.error(`Defender Relay Speed param must be either 'safeLow', 'average', 'fast' or 'fastest'. Not "${speed}"`) + process.exit(2) + } + const credentials = { + apiKey: process.env.DEFENDER_API_KEY, + apiSecret: process.env.DEFENDER_API_SECRET, + } + const provider = new DefenderRelayProvider(credentials) + return new DefenderRelaySigner(credentials, provider, { speed }) +} task( 'seedNewAllocations', 'Adds a new tranche to an existing MerkleDrop contract', ) - .addParam( - 'merkleDrop', - 'MerkleDrop contract address', - undefined, - addressType, - false, - ) - .addParam( - 'balances', - 'JSON file with address => balance mapping', - undefined, - jsonBalancesType, - false, - ) + .addParam('merkleDrop','MerkleDrop contract address',undefined,addressType,false) + .addParam('balances','JSON file with address => balance mapping',undefined,jsonBalancesType,false) + .addOptionalParam('isSimulation', 'if true, will not send transactions', true, boolean) .setAction( async ( { merkleDrop, balances: balancesPromise, + isSimulation, }: { merkleDrop: string - balances: JSONBalances + balances: JSONBalances, + isSimulation: boolean }, - { ethers, network }, + { network }, ) => { - const [deployer] = await ethers.getSigners() + const singer = await getDefenderSigner("fast") const balances = await balancesPromise - let uri: string - { - console.log('Pinning file to IPFS') - - const { - protocol, - hostname: host, - pathname, - } = new URL('https://api.thegraph.com/ipfs/') - const client = create({ - protocol, - host, - port: 443, - apiPath: `${pathname}/api/v0/`, - }) - - const buffer = Buffer.from(balances.body) - - const addResult = await client.add(buffer, { pin: true }) - uri = `ipfs://${addResult.cid}` - - console.log('Pinned file', addResult.cid) - } - - console.log( - `Connecting using ${await deployer.getAddress()} and url ${ - network.name - }`, - ) - - const merkleDropContract = MerkleDrop__factory.connect( - merkleDrop, - deployer, - ) - - const merkleTree = createTreeWithAccounts(balances.parsed) - const hexRoot = merkleTree.getHexRoot() - + console.log(`Connecting using ${await singer.getAddress()} and url ${network.name}`) + const uri: string = await getIPFSUri(balances); + const hexRoot = await getHexRoot(balances) const totalAllocation = Object.values(balances.parsed).reduce( (prev, balance) => prev.add(balance), BigNumber.from(0), @@ -106,43 +153,18 @@ task( return } } - + // if user input is valid + const merkleDropContract = MerkleDrop__factory.connect(merkleDrop, singer) const token = await merkleDropContract.token() - const tokenContract = IERC20__factory.connect(token, deployer) - - const isFunder = await merkleDropContract.funders(deployer.address) - if (!isFunder) { - console.error('Not a funder') - return - } - - const allowance = await tokenContract.allowance( - deployer.address, - merkleDrop, - ) + const tokenContract = IERC20__factory.connect(token, singer) - if (allowance.lt(totalAllocation)) { - console.log('Approval required; approving infinite') - const approvalTx = await tokenContract.approve( - merkleDrop, - constants.MaxUint256, - ) - console.log(`Sending transaction ${approvalTx.hash}`) - await approvalTx.wait() - console.log('Transaction complete') - } + await validateAllowance(tokenContract, merkleDrop, totalAllocation, isSimulation) - console.log(`Seeding new allocations`) - let seedNewAllocationsTx = await merkleDropContract.seedNewAllocations( - hexRoot, - totalAllocation, - uri, - ) - console.log(`Sending transaction ${seedNewAllocationsTx.hash}`) - - await seedNewAllocationsTx.wait() + await seedNewAllocations(merkleDropContract, hexRoot, totalAllocation, uri, isSimulation) console.log('Transaction complete') }, ) -export {} +export { } + +