From 88c636dc47f7b161032267b4ce82e91b88ebfc02 Mon Sep 17 00:00:00 2001 From: picodes Date: Thu, 9 Nov 2023 15:46:27 +0100 Subject: [PATCH] fix --- src/bot/runner.ts | 10 ++++++-- src/bot/validity.ts | 28 +++++++++++++++++++---- src/helpers/index.ts | 15 ++++++------ src/providers/on-chain/OnChainProvider.ts | 5 ++++ src/providers/on-chain/RpcProvider.ts | 6 +++++ src/types/bot.ts | 1 + src/types/holders.ts | 1 + tests/helpers/ManualChainProvider.ts | 6 +++++ 8 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/bot/runner.ts b/src/bot/runner.ts index 81ef782..d94bf18 100644 --- a/src/bot/runner.ts +++ b/src/bot/runner.ts @@ -102,8 +102,14 @@ export const checkHolderValidity: Step = async ({ onChainProvider }, report) => const { startTree, endTree } = report; holdersReport = await validateHolders(onChainProvider, startTree, endTree); const negativeDiffs = holdersReport.negativeDiffs; - - if (negativeDiffs.length > 0) throw negativeDiffs.join('\n'); + const overDistributed = holdersReport.overDistributed; + + if (negativeDiffs.length > 0) { + return Result.Error({ code: BotError.NegativeDiff, reason: negativeDiffs.join('\n'), report: { ...report, holdersReport } }); + } + if (overDistributed.length > 0) { + return Result.Error({ code: BotError.OverDistributed, reason: overDistributed.join('\n'), report: { ...report, holdersReport } }); + } return Result.Success({ ...report, holdersReport }); } catch (reason) { diff --git a/src/bot/validity.ts b/src/bot/validity.ts index 0c60196..27d712a 100644 --- a/src/bot/validity.ts +++ b/src/bot/validity.ts @@ -1,6 +1,7 @@ import { AggregatedRewardsType, Int256 } from '@angleprotocol/sdk'; import { BigNumber } from 'ethers'; +import { HOUR } from '../constants'; import { round } from '../helpers'; import OnChainProvider from '../providers/on-chain/OnChainProvider'; import { DistributionChanges, HolderClaims, HolderDetail, HoldersReport, UnclaimedRewards } from '../types/holders'; @@ -32,7 +33,10 @@ export async function validateHolders( endTree: AggregatedRewardsType ): Promise { const holders = gatherHolders(startTree, endTree); - const activeDistributions = await onChainProvider.fetchActiveDistributions(); + const activeDistributions = await onChainProvider.fetchActiveDistributionsBetween( + startTree.lastUpdateEpoch * HOUR, + endTree.lastUpdateEpoch * HOUR + ); const poolName = {}; @@ -40,6 +44,7 @@ export async function validateHolders( const changePerDistrib: DistributionChanges = {}; const unclaimed: UnclaimedRewards = {}; const negativeDiffs: string[] = []; + const overDistributed: string[] = []; for (const holder of holders) { unclaimed[holder] = {}; @@ -110,7 +115,22 @@ export async function validateHolders( l.percent = (l?.diff / changePerDistrib[l?.distribution]?.diff) * 100; } - return { details, changePerDistrib, unclaimed, negativeDiffs }; + for (const k of Object.keys(changePerDistrib)) { + const solidityDist = activeDistributions?.find((d) => d.base.rewardId === k); + + // Either the distributed amount is less than what would be distributed since the distrib start and there is no dis in the start tree + // Either it's less than what would be distributed since the startTree update + if ( + (!!startTree.rewards[k]?.lastUpdateEpoch && + changePerDistrib[k].epoch > endTree.rewards[k].lastUpdateEpoch - startTree.rewards[k].lastUpdateEpoch) || + (!startTree.rewards[k]?.lastUpdateEpoch && + changePerDistrib[k].epoch > endTree.rewards[k].lastUpdateEpoch - solidityDist.base.epochStart / HOUR) + ) { + overDistributed.push(k); + } + } + + return { details, changePerDistrib, unclaimed, negativeDiffs, overDistributed }; } export async function validateClaims(onChainProvider: OnChainProvider, holdersReport: HoldersReport): Promise { @@ -118,7 +138,7 @@ export async function validateClaims(onChainProvider: OnChainProvider, holdersRe const alreadyClaimed: HolderClaims = await onChainProvider.fetchClaimed(details); const overclaimed: string[] = []; - + // Sort details by distribution and format numbers const expandedDetails = await Promise.all( details @@ -128,7 +148,7 @@ export async function validateClaims(onChainProvider: OnChainProvider, holdersRe .map(async (d) => { const alreadyClaimedValue = round(Int256.from(alreadyClaimed[d.holder][d.tokenAddress], d.decimals).toNumber(), 2); const totalCumulated = round(unclaimed[d.holder][d.symbol].toNumber(), 2); - + if (totalCumulated < alreadyClaimedValue) { overclaimed.push(`${d.holder}: ${alreadyClaimedValue} / ${totalCumulated} ${d.symbol}`); } diff --git a/src/helpers/index.ts b/src/helpers/index.ts index f618ced..36600e4 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -98,7 +98,7 @@ export const buildMerklTree = ( /** * 3 - Build the tree */ - const elements = []; + const leaves = []; for (const u of users) { for (const t of tokens) { let sum = BigNumber.from(0); @@ -108,14 +108,15 @@ export const buildMerklTree = ( sum = sum?.add(distribution?.holders[u]?.amount.toString() ?? 0); } } - const hash = ethers.utils.keccak256( - ethers.utils.defaultAbiCoder.encode(['address', 'address', 'uint256'], [utils.getAddress(u), t, sum]) - ); - - elements.push(hash); + if (!!sum && sum.gt(0)) { + const hash = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode(['address', 'address', 'uint256'], [utils.getAddress(u), t, sum]) + ); + leaves.push(hash); + } } } - const tree = new MerkleTree(elements, keccak256, { hashLeaves: false, sortPairs: true, sortLeaves: false }); + const tree = new MerkleTree(leaves, keccak256, { hashLeaves: false, sortPairs: true, sortLeaves: false }); return { tokens, diff --git a/src/providers/on-chain/OnChainProvider.ts b/src/providers/on-chain/OnChainProvider.ts index 5694c38..1904509 100644 --- a/src/providers/on-chain/OnChainProvider.ts +++ b/src/providers/on-chain/OnChainProvider.ts @@ -28,6 +28,7 @@ export default abstract class OnChainProvider extends ExponentialBackoffProvider protected abstract onChainParams: () => Promise; protected abstract timestampAt: (blockNumber: number) => Promise; protected abstract activeDistributions: () => Promise; + protected abstract activeDistributionsBetween: (start: number, end: number) => Promise; protected abstract poolName: (pool: string, amm: AMMType) => Promise; protected abstract claimed: (holderDetails: HolderDetail[]) => Promise; protected abstract approve: ( @@ -73,6 +74,10 @@ export default abstract class OnChainProvider extends ExponentialBackoffProvider return this.retryWithExponentialBackoff(this.activeDistributions, this.fetchParams); } + async fetchActiveDistributionsBetween(start: number, end: number): Promise { + return this.retryWithExponentialBackoff(this.activeDistributionsBetween, this.fetchParams, start, end); + } + async fetchOnChainParams(): Promise { return this.retryWithExponentialBackoff(this.onChainParams, this.fetchParams); } diff --git a/src/providers/on-chain/RpcProvider.ts b/src/providers/on-chain/RpcProvider.ts index 94c6edf..cb40877 100644 --- a/src/providers/on-chain/RpcProvider.ts +++ b/src/providers/on-chain/RpcProvider.ts @@ -67,6 +67,12 @@ export default class RpcProvider extends OnChainProvider { return instance.getActiveDistributions({ blockTag: this.blockNumber }); }; + override activeDistributionsBetween = async (start: number, end: number) => { + const instance = DistributionCreator__factory.connect(this.distributorCreator, this.provider); + + return instance.getDistributionsBetweenEpochs(start, end, { blockTag: this.blockNumber }); + }; + override poolName = async (pool: string, amm: AMMType) => { const multicall = Multicall__factory.connect('0xcA11bde05977b3631167028862bE2a173976CA11', this.provider); const poolInterface = PoolInterface(AMMAlgorithmMapping[amm]); diff --git a/src/types/bot.ts b/src/types/bot.ts index a21ecce..a61bf7a 100644 --- a/src/types/bot.ts +++ b/src/types/bot.ts @@ -51,6 +51,7 @@ export enum BotError { TreeFetch, TreeRoot, NegativeDiff, + OverDistributed, AlreadyClaimed, KeeperCreate, KeeperApprove, diff --git a/src/types/holders.ts b/src/types/holders.ts index 2e5d70e..60332ea 100644 --- a/src/types/holders.ts +++ b/src/types/holders.ts @@ -33,4 +33,5 @@ export type HoldersReport = { unclaimed: UnclaimedRewards; negativeDiffs: string[]; overclaimed?: string[]; + overDistributed?: string[]; }; diff --git a/tests/helpers/ManualChainProvider.ts b/tests/helpers/ManualChainProvider.ts index e24c9f3..d90ef36 100644 --- a/tests/helpers/ManualChainProvider.ts +++ b/tests/helpers/ManualChainProvider.ts @@ -7,10 +7,12 @@ import { HolderClaims } from '../../src/types/holders'; export default class ManualChainProvider extends OnChainProvider { claimedCall: () => HolderClaims; activeDistributionCall: () => ExtensiveDistributionParametersStructOutput[]; + activeDistributionsBetweenCall: (start: number, end: number) => ExtensiveDistributionParametersStructOutput[]; poolNameCall: () => string; constructor( activeDistributionCall: () => ExtensiveDistributionParametersStructOutput[], + activeDistributionsBetweenCall: () => ExtensiveDistributionParametersStructOutput[], claimedCall: () => HolderClaims, poolNameCall: () => string ) { @@ -24,6 +26,10 @@ export default class ManualChainProvider extends OnChainProvider { return this?.activeDistributionCall(); }; + override activeDistributionsBetween = async (start: number, end: number) => { + return this?.activeDistributionsBetweenCall(start, end); + }; + override claimed = async () => { return this?.claimedCall(); };