diff --git a/.eslintrc b/.eslintrc index cd3479661e..1ef9de1628 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,7 +18,7 @@ }, "ignorePatterns": ["node_modules/", "artifacts", "coverage", "!.solcover.js", "deployments/dist"], "parserOptions": { - "ecmaVersion": 2020 + "ecmaVersion": 2021 }, "rules": { "comma-dangle": ["warn", "always-multiline"], diff --git a/scripts/v1-nxm/v1-cla-locked.js b/scripts/v1-nxm/v1-cla-locked.js new file mode 100644 index 0000000000..d597963246 --- /dev/null +++ b/scripts/v1-nxm/v1-cla-locked.js @@ -0,0 +1,72 @@ +const { inspect } = require('node:util'); +const fs = require('node:fs'); + +const { Sema } = require('async-sema'); +const { ethers } = require('hardhat'); + +const { getContract } = require('./v1-nxm-push-utils'); + +const OUTPUT_FILE = 'v1-cla-locked-amount.json'; +const ROLE_MEMBER = 2; + +async function getMemberCLA(memberId, claReason, mr, tc) { + process.stdout.write(`\rProcessing memberId ${memberId}`); + + const [member, active] = await mr.memberAtIndex(ROLE_MEMBER, memberId); + + if (!active) { + return { member, amount: '0' }; + } + + const amount = await tc.tokensLocked(member, claReason); + + return { member, amount: amount.toString() }; +} + +const main = async () => { + const v1ClaimAssessment = []; + + const mr = getContract('MemberRoles'); + const tc = getContract('TokenController'); + + const claReason = ethers.utils.formatBytes32String('CLA'); + const memberCount = (await mr.membersLength(ROLE_MEMBER)).toNumber(); + const membersSemaphore = new Sema(100, { capacity: memberCount }); + + console.log('Fetching V1 Pooled Staking stake / rewards for all members...'); + const failedMemberIds = []; + + const memberPromises = Array.from({ length: memberCount }).map(async (_, memberId) => { + await membersSemaphore.acquire(); + + try { + const result = await getMemberCLA(memberId, claReason, mr, tc); + if (result.amount !== '0') { + v1ClaimAssessment.push(result); + } + } catch (e) { + console.error(`Error processing memberId ${memberId}: ${e.message}`); + failedMemberIds.push(memberId); + } + + membersSemaphore.release(); + }); + + await Promise.all(memberPromises); + + console.log(`Found ${v1ClaimAssessment.length} members with locked v1 NXM for CLA`); + console.log(`Failed members: ${inspect(failedMemberIds, { depth: null })}`); + + console.log(`Writing output to ${OUTPUT_FILE}...`); + fs.writeFileSync(OUTPUT_FILE, JSON.stringify(v1ClaimAssessment, null, 2), 'utf8'); + console.log('Done.'); +}; + +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch(e => { + console.log('Unhandled error encountered: ', e.stack); + process.exit(1); + }); +} diff --git a/scripts/v1-nxm/v1-cn-locked.js b/scripts/v1-nxm/v1-cn-locked.js new file mode 100644 index 0000000000..e1a800728a --- /dev/null +++ b/scripts/v1-nxm/v1-cn-locked.js @@ -0,0 +1,103 @@ +const { inspect } = require('node:util'); +const fs = require('node:fs'); + +const { Sema } = require('async-sema'); +const { ethers } = require('hardhat'); + +const { getContract } = require('./v1-nxm-push-utils'); + +const { BigNumber } = ethers; + +const OUTPUT_FILE = 'v1-cn-locked-amount.json'; +const ROLE_MEMBER = 2; + +async function getMemberCN(memberId, mr, tc) { + try { + const [member, active] = await mr.memberAtIndex(ROLE_MEMBER, memberId); + + if (!active) { + return { member, amount: '0' }; + } + + const { coverIds, lockReasons, withdrawableAmount } = await tc.getWithdrawableCoverNotes(member); + const memberLockReasons = await tc.getLockReasons(member); + + const lockReasonIndexCover = {}; + let coverIndex = 0; + for (const lockReason of lockReasons) { + const lockReasonIndex = memberLockReasons.indexOf(lockReason); + lockReasonIndexCover[lockReasonIndex] = BigNumber.from(coverIds[coverIndex++]).toString(); + } + + const sortedIndexes = Object.keys(lockReasonIndexCover).sort((a, b) => a - b); + const sortedCoverIds = []; + for (const index of sortedIndexes) { + sortedCoverIds.push(lockReasonIndexCover[index]); + } + + return { + member, + coverIds: sortedCoverIds, + lockReasonIndexes: sortedIndexes, + amount: withdrawableAmount.toString(), + }; + } catch (error) { + console.error(`Error processing memberId ${memberId}: ${error.message}`); + return { member: 'unknown', amount: '0', error: error.message }; + } +} + +const main = async () => { + const tc = getContract('TokenController'); + const mr = getContract('MemberRoles'); + + const memberCount = (await mr.membersLength(ROLE_MEMBER)).toNumber(); + const membersSemaphore = new Sema(100, { capacity: memberCount }); + const memberLockedCN = []; + const failedMemberIds = []; + + console.log('Fetching locked CN amounts for all members...'); + + const memberPromises = Array.from({ length: memberCount }).map(async (_, memberId) => { + await membersSemaphore.acquire(); + + try { + process.stdout.write(`\rProcessing memberId ${memberId}`); + const lockedCN = await getMemberCN(memberId, mr, tc); + + if (lockedCN.amount !== '0') { + memberLockedCN.push(lockedCN); + } + if (lockedCN.error) { + console.error(`\nError event for memberId ${memberId}: ${lockedCN.error}`); + failedMemberIds.push(memberId); + } + } catch (e) { + console.error(`\nError event for memberId ${memberId}: ${e.message}`); + failedMemberIds.push(memberId); + } finally { + membersSemaphore.release(); + } + }); + + await Promise.all(memberPromises); + + console.log(`\nFound ${memberLockedCN.length} members with locked v1 NXM for CN`); + console.log(`Failed members: ${inspect(failedMemberIds, { depth: null })}`); + + console.log(`Writing output to ${OUTPUT_FILE}...`); + fs.writeFileSync(OUTPUT_FILE, JSON.stringify(memberLockedCN, null, 2), 'utf8'); + console.log('Done.'); +}; + +if (require.main === module) { + const provider = new ethers.providers.JsonRpcProvider(process.env.TEST_ENV_FORK); + main(provider) + .then(() => process.exit(0)) + .catch(e => { + console.log('Unhandled error encountered: ', e.stack); + process.exit(1); + }); +} + +module.exports = main; diff --git a/scripts/v1-nxm/v1-nxm-accounting.js b/scripts/v1-nxm/v1-nxm-accounting.js new file mode 100644 index 0000000000..4b49f79985 --- /dev/null +++ b/scripts/v1-nxm/v1-nxm-accounting.js @@ -0,0 +1,86 @@ +const deployments = require('@nexusmutual/deployments'); +const { ethers } = require('hardhat'); + +const { getContract } = require('./v1-nxm-push-utils'); + +async function logPoolBalances() { + const tokenController = getContract('TokenController'); + const nxm = getContract('NXMToken'); + const stakingPoolFactory = getContract('StakingPoolFactory'); + + let totalDeposits = ethers.BigNumber.from(0); + let totalRewards = ethers.BigNumber.from(0); + + const stakingPoolCount = await stakingPoolFactory.stakingPoolCount(); + + for (let poolId = 1; poolId <= stakingPoolCount.toNumber(); poolId++) { + const { rewards, deposits } = await tokenController.stakingPoolNXMBalances(poolId); + const depositsNXM = ethers.utils.formatEther(deposits); + const rewardsNXM = ethers.utils.formatEther(rewards); + + console.log(`Pool ${poolId}: Deposits: ${depositsNXM} NXM, Rewards: ${rewardsNXM} NXM`); + + totalDeposits = totalDeposits.add(deposits); + totalRewards = totalRewards.add(rewards); + } + + const cnData = require('../../v1-cn-locked-amount.json'); + const claData = require('../../v1-cla-locked-amount.json'); + const cnBalance = cnData.reduce((acc, data) => acc.add(data.amount), ethers.BigNumber.from(0)); + const claBalance = claData.reduce((acc, data) => acc.add(data.amount), ethers.BigNumber.from(0)); + + const totalDepositsNXM = ethers.utils.formatEther(totalDeposits); + const totalRewardsNXM = ethers.utils.formatEther(totalRewards); + const totalCnBalanceNXM = ethers.utils.formatEther(cnBalance); + const totalClaBalanceNXM = ethers.utils.formatEther(claBalance); + + console.log(`Total Deposits: ${totalDepositsNXM} NXM`); + console.log(`Total Rewards: ${totalRewardsNXM} NXM`); + console.log(`CN Balance: ${totalCnBalanceNXM} NXM`); + console.log(`CLA Balance: ${totalClaBalanceNXM} NXM`); + + const totalPoolBalance = totalDeposits.add(totalRewards).add(cnBalance).add(claBalance); + console.log(`EXPECTED Total Pool Balance: ${ethers.utils.formatEther(totalPoolBalance)} NXM`); + + const tokenControllerBalance = await nxm.balanceOf(deployments.addresses.TokenController); + console.log(`ACTUAL Token Controller Balance: ${ethers.utils.formatEther(tokenControllerBalance)} NXM`); + + const difference = tokenControllerBalance.sub(totalPoolBalance); + const differenceNXM = ethers.utils.formatEther(difference); + + console.log(`Difference: ${differenceNXM} NXM`); +} + +/* Get total v1 NXM amounts that is owed to members */ +const getAmounts = (label, usersAndAmounts) => { + const totalAmountNxm = usersAndAmounts.reduce((acc, data) => acc.add(data.amount), ethers.BigNumber.from(0)); + const totalNxm = ethers.utils.formatEther(totalAmountNxm); + console.log(`${label} ${totalNxm} NXM`); + return totalNxm; +}; + +async function legacyPooledStakingAccounting() { + const nxm = getContract('NXMToken'); + + const psBal = await nxm.balanceOf(deployments.addresses.LegacyPooledStaking); + const psBalNxm = ethers.utils.formatEther(psBal); + + const stakeData = require('../../v1-pooled-staking-stake.json'); + const rewardsData = require('../../v1-pooled-staking-rewards.json'); + const v1NxmStake = getAmounts('Stake', stakeData); + const v1NxmRewards = getAmounts('Rewards', rewardsData); + console.log(`LegacyPooledStaking ${psBalNxm} NXM`); + + console.log(`LegacyPooledStaking Difference ${psBalNxm - v1NxmRewards - v1NxmStake} NXM`); +} + +if (require.main === module) { + legacyPooledStakingAccounting() + .then(() => console.log('\n--------------------\n')) + .then(() => logPoolBalances()) + .then(() => process.exit(0)) + .catch(e => { + console.log('Unhandled error encountered: ', e.stack); + process.exit(1); + }); +} diff --git a/scripts/v1-nxm/v1-nxm-estimate-gas.js b/scripts/v1-nxm/v1-nxm-estimate-gas.js new file mode 100644 index 0000000000..3518883f62 --- /dev/null +++ b/scripts/v1-nxm/v1-nxm-estimate-gas.js @@ -0,0 +1,142 @@ +const deployments = require('@nexusmutual/deployments'); +const { Sema } = require('async-sema'); +const { ethers } = require('hardhat'); + +const getContractFactory = provider => { + const signer = provider.getSigner('0x87B2a7559d85f4653f13E6546A14189cd5455d45'); + return async contractName => { + const abi = deployments.abis[contractName]; + const address = deployments.addresses[contractName]; + // const signer = wallet.connect(provider); + return new ethers.Contract(address, abi, signer); + }; +}; + +const main = async provider => { + const factory = getContractFactory(provider); + const [tc, ps] = await Promise.all([factory('TokenController'), factory('LegacyPooledStaking')]); + + console.log('estimating withdrawing all V1 NXM members...'); + + await withdrawCoverNotes(tc); + await withdrawClaimsAssessment(tc); + await withdrawV1StakingStake(ps); + await withdrawV1StakingRewards(ps); +}; + +const withdrawCoverNotes = async tc => { + const coverNotesData = require('../../v1-cn-locked-amount.json'); + + let failed = 0; + let success = 0; + let totalGas = ethers.BigNumber.from(0); + const membersSemaphore = new Sema(10, { capacity: coverNotesData.length }); + + const promises = coverNotesData.map(async (data, i) => { + const { member, coverIds, lockReasonIndexes } = data; + process.stdout.write(`\rmember ${++i} of ${coverNotesData.length}`); + + await membersSemaphore.acquire(); + + try { + const gasEstimate = await tc.estimateGas.withdrawCoverNote(member, coverIds, lockReasonIndexes); + totalGas = totalGas.add(gasEstimate); + success++; + } catch (e) { + console.log(e); + console.log({ member, coverIds, lockReasonIndexes }); + failed++; + } + + membersSemaphore.release(); + }); + + await Promise.all(promises); + console.log('totalGas: ', totalGas); + console.log({ success, failed }); +}; + +const withdrawClaimsAssessment = async tokenController => { + console.log('estimating v1 CLA tokens withdrawal...'); + const usersAndAmount = require('../../v1-cla-locked-amount.json'); + const users = usersAndAmount.map(data => data.member); + const gasEstimate = await tokenController.estimateGas.withdrawClaimAssessmentTokens(users); + console.log('CLA gasEstimate: ', gasEstimate); +}; + +const withdrawV1StakingStake = async pooledStaking => { + const usersAndAmounts = require('../../v1-pooled-staking-stake.json'); + const users = usersAndAmounts.map(data => data.member); + + let totalGas = ethers.BigNumber.from(0); + const membersSemaphore = new Sema(10, { capacity: users.length }); + + const promises = users.map(async (user, i) => { + process.stdout.write(`\rmember ${++i} of ${users.length}`); + + await membersSemaphore.acquire(); + + const gasEstimate = await pooledStaking.estimateGas.withdrawForUser(user); + totalGas = totalGas.add(gasEstimate); + + membersSemaphore.release(); + }); + + await Promise.all(promises); + console.log('totalGas: ', totalGas); +}; + +const withdrawV1StakingRewards = async pooledStaking => { + const usersAndAmounts = require('../../v1-pooled-staking-rewards.json'); + const users = usersAndAmounts.map(data => data.member); + + let totalGas = ethers.BigNumber.from(0); + const membersSemaphore = new Sema(10, { capacity: users.length }); + + const promises = users.map(async (user, i) => { + process.stdout.write(`\rmember ${i + 1} of ${users.length}`); + + await membersSemaphore.acquire(); + + const gasEstimate = await pooledStaking.estimateGas.withdrawReward(user); + totalGas = totalGas.add(gasEstimate); + + membersSemaphore.release(); + }); + + await Promise.all(promises); + console.log('totalGas: ', totalGas); +}; + +if (require.main === module) { + // use default provider and bypass cache when run via cli + const provider = new ethers.providers.JsonRpcProvider(process.env.TEST_ENV_FORK); + main(provider) + .then(() => process.exit(0)) + .catch(e => { + console.log('Unhandled error encountered: ', e.stack); + process.exit(1); + }); +} + +const getGasCost = async (totalGas, gasPriceGwei) => { + // const claGas = 2505458; + // const stakeGas = 26646603; + // const rewardsGas = 48516039; + // const coverNotesGas = 182221106; + // const totalGas = claGas + stakeGas + rewardsGas + coverNotesGas; + // console.log('totalGas: ', totalGas); + + // Convert gas price from gwei to wei + const gasPriceWei = ethers.utils.parseUnits(gasPriceGwei.toString(), 'gwei'); + + // Calculate total cost in wei + const totalCostWei = gasPriceWei.mul(totalGas); + + // Convert wei to ETH + const totalCostEth = ethers.utils.formatEther(totalCostWei); + + return `${totalCostEth.toString()} ETH`; +}; + +module.exports = { getGasCost }; diff --git a/scripts/v1-nxm/v1-nxm-push-utils.js b/scripts/v1-nxm/v1-nxm-push-utils.js new file mode 100644 index 0000000000..87c8b95ac9 --- /dev/null +++ b/scripts/v1-nxm/v1-nxm-push-utils.js @@ -0,0 +1,96 @@ +const fs = require('node:fs/promises'); +const util = require('node:util'); + +const deployments = require('@nexusmutual/deployments'); +const { ethers } = require('hardhat'); + +const PROGRESS_FILE = 'v1-nxm-progress.json'; + +const waitFor = util.promisify(setTimeout); + +function getContract(contractName, providerOrSigner = ethers.provider) { + const abi = deployments[contractName]; + const address = deployments.addresses[contractName]; + if (!abi || !address) { + throw new Error(`address or abi not found for ${contractName} contract`); + } + return new ethers.Contract(address, abi, providerOrSigner); +} + +async function getGasFees(provider, priorityFee) { + const { baseFeePerGas } = await provider.getBlock('pending'); + if (!baseFeePerGas) { + throw new Error('Failed to get baseFeePerGas. Please try again'); + } + const priorityFeeWei = ethers.utils.parseUnits(priorityFee.toString(), 'gwei'); + + return { + maxFeePerGas: baseFeePerGas.add(priorityFeeWei), + maxPriorityFeePerGas: priorityFeeWei, + }; +} + +/* Load / Save Progress */ + +async function loadProgress() { + try { + const data = await fs.readFile(PROGRESS_FILE, 'utf-8'); + return JSON.parse(data); + } catch (error) { + return {}; + } +} + +async function saveProgress(data) { + return fs.writeFile(PROGRESS_FILE, JSON.stringify(data, null, 2)); +} + +/* v1 NXM push contract functions */ + +async function pushCoverNotes({ tc }, batch, txOpts) { + const promises = batch.map(async (item, i) => { + const { member, coverIds, lockReasonIndexes } = item; + const nonce = txOpts.nonce + i; + const tx = await tc.withdrawCoverNote(member, coverIds, lockReasonIndexes, { + ...txOpts, + nonce, + gasLimit: '150000', + }); + await tx.wait(); + }); + await Promise.all(promises); +} + +async function pushClaimsAssessment({ tc }, batch, txOpts) { + const members = batch.map(item => item.member); + const tx = await tc.withdrawClaimAssessmentTokens(members, txOpts); + await tx.wait(); +} + +async function pushV1StakingStake({ ps }, batch, txOpts) { + const promises = batch.map(async (item, i) => { + const tx = await ps.withdrawForUser(item.member, { ...txOpts, nonce: txOpts.nonce + i, gasLimit: '100000' }); + await tx.wait(); + }); + await Promise.all(promises); +} + +async function pushV1StakingRewards({ ps }, batch, txOpts) { + const promises = batch.map(async (item, i) => { + const tx = await ps.withdrawReward(item.member, { ...txOpts, nonce: txOpts.nonce + i, gasLimit: '100000' }); + await tx.wait(); + }); + await Promise.all(promises); +} + +module.exports = { + waitFor, + getContract, + getGasFees, + loadProgress, + saveProgress, + pushCoverNotes, + pushClaimsAssessment, + pushV1StakingStake, + pushV1StakingRewards, +}; diff --git a/scripts/v1-nxm/v1-nxm-push.js b/scripts/v1-nxm/v1-nxm-push.js new file mode 100644 index 0000000000..6dc632197a --- /dev/null +++ b/scripts/v1-nxm/v1-nxm-push.js @@ -0,0 +1,102 @@ +require('dotenv').config(); +const { ethers } = require('ethers'); + +const { + waitFor, + getContract, + getGasFees, + loadProgress, + saveProgress, + pushCoverNotes, + pushClaimsAssessment, + pushV1StakingStake, + pushV1StakingRewards, +} = require('./v1-nxm-push-utils'); + +const types = [ + { name: 'StakingRewards', data: require('../../v1-pooled-staking-rewards.json'), func: pushV1StakingRewards }, + { name: 'StakingStake', data: require('../../v1-pooled-staking-stake.json'), func: pushV1StakingStake }, + { name: 'CoverNotes', data: require('../../v1-cn-locked-amount.json'), func: pushCoverNotes }, + { name: 'ClaimsAssessment', data: require('../../v1-cla-locked-amount.json'), func: pushClaimsAssessment }, +]; + +/** + * Processes V1 NXM push tokens for different types (ClaimsAssessment, StakingStake, StakingRewards, CoverNotes). + * + * @param {number} userMaxFeePerGasGwei - Maximum gas fee user is willing to pay (in Gwei) + * @param {number} priorityFeeGwei - Priority fee (in Gwei) + * @param {number} txPerBlock - Number of transactions to process per block + * + * Features: + * - Only executes txs within the gas fee limit set by the user + * - Batches transactions for efficiency + * - Tracks progress and allows resuming from last processed item (in case of any tx errors) + */ +async function processV1NXM(provider, userMaxFeePerGasGwei, priorityFeeGwei, txPerBlock, manualNonce) { + const userMaxFeePerGas = ethers.utils.parseUnits(userMaxFeePerGasGwei.toString(), 'gwei'); + const signer = new ethers.Wallet(process.env.WALLET_PK, provider); + + const tc = getContract('TokenController', signer); + const ps = getContract('LegacyPooledStaking', signer); + + const progress = await loadProgress(); + + for (const type of types) { + progress[type.name] ||= { processedCount: 0 }; + + let counter = progress[type.name].processedCount; + const totalData = type.data.length; + const remainingData = type.data.slice(progress[type.name].processedCount); + + while (remainingData.length > 0) { + const gasFees = await getGasFees(provider, priorityFeeGwei); + if (gasFees.maxFeePerGas.gt(userMaxFeePerGas)) { + console.log( + 'Gas fee too high. Waiting for next block...', + `maxFeePerGas: ${ethers.utils.formatUnits(gasFees.maxFeePerGas, 'gwei')} gwei`, + `userMaxFeePerGas: ${ethers.utils.formatUnits(userMaxFeePerGas, 'gwei')} gwei`, + ); + await waitFor(15000); // ~15s average block time + continue; + } + + counter += txPerBlock; + process.stdout.write(`\r[${type.name}] Processing members ${counter} of ${totalData}`); + + const batch = remainingData.slice(0, txPerBlock); + const nonce = manualNonce || (await signer.getTransactionCount()); + await type.func({ tc, ps }, batch, { ...gasFees, nonce }); + + progress[type.name].processedCount += batch.length; + await saveProgress(progress); + + remainingData.splice(0, txPerBlock); + } + + console.log(`Successfully pushed all v1 NXM ${type.name}`); + } + + console.log('All v1 NXM types processed successfully.'); +} + +if (require.main === module) { + const args = process.argv.slice(2); + if (args.length < 3) { + console.error('Usage: node v1-nxm-coordinator.js '); + process.exit(1); + } + + const [maxGasFeeGwei, priorityFeeGwei, txPerBlock, manualNonce] = args.map(Number); + const provider = new ethers.providers.JsonRpcProvider(process.env.TEST_ENV_FORK); + + processV1NXM(provider, maxGasFeeGwei, priorityFeeGwei, txPerBlock, manualNonce) + .then(() => process.exit(0)) + .catch(error => { + console.error('Unhandled error:', error); + process.exit(1); + }); +} + +module.exports = { + processV1NXM, +}; diff --git a/scripts/v1-nxm/v1-stake-rewards.js b/scripts/v1-nxm/v1-stake-rewards.js new file mode 100644 index 0000000000..af0ccc3cde --- /dev/null +++ b/scripts/v1-nxm/v1-stake-rewards.js @@ -0,0 +1,97 @@ +const { inspect } = require('node:util'); +const fs = require('node:fs'); + +const { Sema } = require('async-sema'); +const { ethers } = require('hardhat'); + +const { getContract } = require('./v1-nxm-push-utils'); + +const OUTPUT_PATH_STAKE = 'v1-pooled-staking-stake.json'; +const OUTPUT_PATH_REWARDS = 'v1-pooled-staking-rewards.json'; +const ROLE_MEMBER = 2; + +const getMemberV1PooledStaking = async (memberId, mr, ps) => { + process.stdout.write(`\rProcessing memberId ${memberId}`); + + try { + const [member, active] = await mr.memberAtIndex(ROLE_MEMBER, memberId); + + if (!active) { + return { + stake: { member, amount: '0' }, + rewards: { member, amount: '0' }, + }; + } + + const [deposit, rewards] = await Promise.all([ps.stakerDeposit(member), ps.stakerReward(member)]); + + return { + stake: { member, amount: deposit.toString() }, + rewards: { member, amount: rewards.toString() }, + }; + } catch (error) { + console.error(`Error processing memberId ${memberId}: ${error.message}`); + return { + stake: { member: 'unknown', amount: '0', error: error.message }, + rewards: { member: 'unknown', amount: '0', error: error.message }, + }; + } +}; + +const main = async () => { + const v1Stake = []; + const v1Rewards = []; + + const mr = getContract('MemberRoles'); + const ps = getContract('LegacyPooledStaking'); + + const membersCount = await mr.membersLength(ROLE_MEMBER); + const membersSemaphore = new Sema(100, { capacity: membersCount }); + const failedMemberIds = []; + + console.log('Fetching V1 Pooled Staking stake / rewards for all members...'); + + const memberPromises = Array.from({ length: membersCount }).map(async (_, memberId) => { + await membersSemaphore.acquire(); + + try { + const { stake, rewards } = await getMemberV1PooledStaking(memberId, mr, ps); + if (stake.amount !== '0') { + v1Stake.push(stake); + } + if (rewards.amount !== '0') { + v1Rewards.push(rewards); + } + if (stake.error || rewards.error) { + console.error(`\nError event for memberId ${memberId}: ${stake.error || rewards.error}`); + failedMemberIds.push(memberId); + } + } catch (e) { + console.error(`Error processing memberId ${memberId}: ${e.message}`); + failedMemberIds.push(memberId); + } finally { + membersSemaphore.release(); + } + }); + + await Promise.all(memberPromises); + + console.log(`Found ${v1Stake.length} members with v1 Pooled Staking stake`); + console.log(`Found ${v1Rewards.length} members with v1 Pooled Staking rewards`); + console.log(`Failed members: ${inspect(failedMemberIds, { depth: null })}`); + + console.log(`Writing output to ${OUTPUT_PATH_STAKE}/${OUTPUT_PATH_REWARDS}...`); + fs.writeFileSync(OUTPUT_PATH_STAKE, JSON.stringify(v1Stake, null, 2), 'utf8'); + fs.writeFileSync(OUTPUT_PATH_REWARDS, JSON.stringify(v1Rewards, null, 2), 'utf8'); + console.log('Done.'); +}; + +if (require.main === module) { + const provider = new ethers.providers.JsonRpcProvider(process.env.TEST_ENV_FORK); + main(provider, false) + .then(() => process.exit(0)) + .catch(e => { + console.log('Unhandled error encountered: ', e.stack); + process.exit(1); + }); +}