From 3f72418240b5792e2642ed97ad3b63b97ca47cf6 Mon Sep 17 00:00:00 2001 From: Picodes <41673773+Picodes@users.noreply.github.com> Date: Thu, 7 Dec 2023 14:20:09 +0100 Subject: [PATCH] fix: switch to private sdk (#31) * fix: switch to private sdk * .npmrc * feat: add private sdk components to dockerfile * fix: description too long * fix * fix: set env to prod (#32) * fix: overclaimed known * feat: add tc and core support (#33) * fix: tx * fix: dispute bot (#34) * fix: distrib fetching (#35) * feat: add name to dispute bot (#36) * feat: add name to dispute bot * update bot names * fix: fix referee names --------- Co-authored-by: Baptiste Guerin * fix: merkl root computation (#37) * fix: names * feat: alerting delay (#38) --------- Co-authored-by: Baptiste Guerin Co-authored-by: Baptiste Guerin <38328426+BaptistG@users.noreply.github.com> Co-authored-by: Thibaud --- .gitignore | 1 - .npmrc | 1 + Dockerfile | 12 +- cloudrun.yaml | 85 -- package.json | 4 +- scripts/deploy.sh | 8 +- scripts/publish.sh | 3 +- scripts/publishAndDeploy.sh | 5 +- scripts/templates/cloudrun.yaml | 134 ++-- src/bot/dispute.ts | 3 +- src/bot/runner.ts | 30 +- src/bot/validity.ts | 29 +- src/constants/alertingDelay.ts | 12 + src/constants/index.ts | 9 +- src/helpers/index.ts | 16 +- src/helpers/logger/ConsoleLogger.ts | 5 +- src/helpers/logger/DiscordWebhookLogger.ts | 1 + src/providers/on-chain/OnChainProvider.ts | 8 +- src/providers/on-chain/RpcProvider.ts | 33 +- src/types/bot.ts | 1 + src/types/index.ts | 6 +- src/types/interfaces.ts | 10 +- src/utils/discord.ts | 9 +- src/utils/index.ts | 9 +- src/utils/report.ts | 858 --------------------- tests/helpers/ManualChainProvider.ts | 2 +- tests/helpers/testData.ts | 6 +- yarn.lock | 23 +- 28 files changed, 245 insertions(+), 1078 deletions(-) create mode 100644 .npmrc delete mode 100644 cloudrun.yaml create mode 100644 src/constants/alertingDelay.ts delete mode 100644 src/utils/report.ts diff --git a/.gitignore b/.gitignore index 26f68c8..4954ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ src/script/debug.ts .env .env.local .vscode -.npmrc .DS_Store secrets* config diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..af66bba --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@angleprotocol:registry=https://npm.pkg.github.com diff --git a/Dockerfile b/Dockerfile index a020e7d..a4cd2d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ # Use an official Node.js runtime as the builder image -FROM node:16-alpine +FROM node:20-alpine + +ARG GITHUB_TOKEN + +ENV GITHUB_TOKEN $GITHUB_TOKEN # Set the working directory inside the container WORKDIR /app @@ -10,8 +14,12 @@ COPY package.json ./ # Copy the yarn.lock file to the container COPY yarn.lock ./ +COPY .npmrc ./ + # Install the app dependencies -RUN yarn install +RUN echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> .npmrc +RUN yarn install --frozen-lockfile && yarn cache clean +RUN rm -f .npmrc # Copy the tsconfig.json to be able to compile the typescript COPY tsconfig.json ./ diff --git a/cloudrun.yaml b/cloudrun.yaml deleted file mode 100644 index c85ead3..0000000 --- a/cloudrun.yaml +++ /dev/null @@ -1,85 +0,0 @@ -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - generation: 4 - labels: - cloud.googleapis.com/location: europe-west1 - name: merkl-dispute-base # metadata.name -spec: - template: - metadata: - annotations: - autoscaling.knative.dev/maxScale: '2' - autoscaling.knative.dev/minScale: '0' - spec: - containerConcurrency: '1' - containers: - - env: - - name: CHAINID - value: "8453" # spec.template.spec.containers.env[0].value - - name: ENV - value: dev - - name: DISPUTE_BOT_PRIVATE_KEY - valueFrom: - secretKeyRef: - key: latest - name: DISPUTE_BOT_PRIVATE_KEY - - name: PROVIDER_1 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_1 - - name: PROVIDER_10 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_10 - - name: PROVIDER_137 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_137 - - name: PROVIDER_42161 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_42161 - - name: PROVIDER_1101 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_1101 - - name: PROVIDER_8453 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_8453 - - name: DISCORD_TOKEN - valueFrom: - secretKeyRef: - key: latest - name: DISCORD_TOKEN - - name: KEEPER_GITHUB_AUTH_TOKEN - valueFrom: - secretKeyRef: - key: latest - name: KEEPER_GITHUB_AUTH_TOKEN - image: europe-west1-docker.pkg.dev/merkl-dispute-2/registry/merkl-dispute:v0.0.4 # spec.template.spec.containers.image - ports: - - containerPort: '5002' - name: http1 - resources: - limits: - cpu: '1' - memory: 1Gi - startupProbe: - failureThreshold: 1 - periodSeconds: 240 - tcpSocket: - port: '5002' - timeoutSeconds: 240 - serviceAccountName: merkl-dispute-sa@merkl-dispute-2.iam.gserviceaccount.com # spec.template.spec.serviceAccountName - timeoutSeconds: '600' - traffic: - - latestRevision: true - percent: 100 diff --git a/package.json b/package.json index 3f17c4f..e11270f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "publish-image": "chmod +x ./scripts/publish.sh && ./scripts/publish.sh" }, "dependencies": { - "@angleprotocol/sdk": "^3.0.118", + "@angleprotocol/sdk": "0.7.2", "@google-cloud/secret-manager": "^4.2.2", "@octokit/rest": "19.0.13", "@types/chai": "^4.3.6", @@ -41,7 +41,7 @@ "devDependencies": { "@types/express": "^4.17.13", "@types/graceful-fs": "^4.1.5", - "@types/node": "^16.9.6", + "@types/node": "^20.9.2", "@typescript-eslint/eslint-plugin": "^5.2.0", "@typescript-eslint/parser": "^5.2.0", "eslint": "^8.1.0", diff --git a/scripts/deploy.sh b/scripts/deploy.sh index a68165a..5e7cf36 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,8 +2,8 @@ account=$1 version=$2 -chainKeys=("polygon" "ethereum" "optimism" "arbitrum" "zkevm" "base") -chainValues=(137 1 10 42161 1101 8453) +chainKeys=("polygon" "ethereum" "optimism" "arbitrum" "zkevm" "base" "thundercore" "core") +chainValues=(137 1 10 42161 1101 8453 108 1116) for ((i=0; i<${#chainKeys[@]}; i++)) do @@ -22,6 +22,10 @@ do export CHAIN_ID=${chainValues[$i]} yq -i '.spec.template.spec.containers[0].env[0].value= strenv(CHAIN_ID)' ./cloudrun.yaml + [[ $account = 'merkl-dispute-1' ]] && name="Chapron" || name="Pierluigi" + export BOT_NAME=$name + yq -i '.spec.template.spec.containers[0].env[2].value= strenv(BOT_NAME)' ./cloudrun.yaml + export SERVICE_ACCOUNT=merkl-dispute-sa@$account.iam.gserviceaccount.com yq -i '.spec.template.spec.serviceAccountName= strenv(SERVICE_ACCOUNT)' ./cloudrun.yaml diff --git a/scripts/publish.sh b/scripts/publish.sh index 3a7820a..762ce41 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -1,9 +1,10 @@ #!/bin/bash account=$1 version=$2 +token=$3 echo "Building docker image" -docker build -t merkl-dispute:latest --platform linux/amd64 . +docker build --build-arg GITHUB_TOKEN=$token -t merkl-dispute:latest --platform linux/amd64 . docker tag merkl-dispute:latest europe-west1-docker.pkg.dev/$account/registry/merkl-dispute:$version diff --git a/scripts/publishAndDeploy.sh b/scripts/publishAndDeploy.sh index bc3cfbd..84ee3f8 100755 --- a/scripts/publishAndDeploy.sh +++ b/scripts/publishAndDeploy.sh @@ -6,5 +6,8 @@ read version echo "Please enter the account to deploy to: (merkl-dispute-1 or merkl-dispute-2)" read account -sh scripts/publish.sh $account $version +echo "Please enter Github token to download the SDK" +read token + +sh scripts/publish.sh $account $version $token bash scripts/deploy.sh $account $version diff --git a/scripts/templates/cloudrun.yaml b/scripts/templates/cloudrun.yaml index a8063ab..454584d 100644 --- a/scripts/templates/cloudrun.yaml +++ b/scripts/templates/cloudrun.yaml @@ -14,72 +14,74 @@ spec: spec: containerConcurrency: '1' containers: - - env: - - name: CHAINID - value: CHANGE_ME # spec.template.spec.containers.env[0].value - - name: ENV - value: dev - - name: DISPUTE_BOT_PRIVATE_KEY - valueFrom: - secretKeyRef: - key: latest - name: DISPUTE_BOT_PRIVATE_KEY - - name: PROVIDER_1 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_1 - - name: PROVIDER_10 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_10 - - name: PROVIDER_137 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_137 - - name: PROVIDER_42161 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_42161 - - name: PROVIDER_1101 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_1101 - - name: PROVIDER_8453 - valueFrom: - secretKeyRef: - key: latest - name: PROVIDER_8453 - - name: DISCORD_TOKEN - valueFrom: - secretKeyRef: - key: latest - name: DISCORD_TOKEN - - name: KEEPER_GITHUB_AUTH_TOKEN - valueFrom: - secretKeyRef: - key: latest - name: KEEPER_GITHUB_AUTH_TOKEN - image: CHANGE_ME # spec.template.spec.containers.image - ports: - - containerPort: '5002' - name: http1 - resources: - limits: - cpu: '1' - memory: 1Gi - startupProbe: - failureThreshold: 1 - periodSeconds: 240 - tcpSocket: - port: '5002' - timeoutSeconds: 240 + - env: + - name: CHAINID + value: CHANGE_ME # spec.template.spec.containers.env[0].value + - name: ENV + value: prod + - name: BOT_NAME + value: CHANGE_ME # spec.template.spec.containers.env[2].value + - name: DISPUTE_BOT_PRIVATE_KEY + valueFrom: + secretKeyRef: + key: latest + name: DISPUTE_BOT_PRIVATE_KEY + - name: PROVIDER_1 + valueFrom: + secretKeyRef: + key: latest + name: PROVIDER_1 + - name: PROVIDER_10 + valueFrom: + secretKeyRef: + key: latest + name: PROVIDER_10 + - name: PROVIDER_137 + valueFrom: + secretKeyRef: + key: latest + name: PROVIDER_137 + - name: PROVIDER_42161 + valueFrom: + secretKeyRef: + key: latest + name: PROVIDER_42161 + - name: PROVIDER_1101 + valueFrom: + secretKeyRef: + key: latest + name: PROVIDER_1101 + - name: PROVIDER_8453 + valueFrom: + secretKeyRef: + key: latest + name: PROVIDER_8453 + - name: DISCORD_TOKEN + valueFrom: + secretKeyRef: + key: latest + name: DISCORD_TOKEN + - name: KEEPER_GITHUB_AUTH_TOKEN + valueFrom: + secretKeyRef: + key: latest + name: KEEPER_GITHUB_AUTH_TOKEN + image: CHANGE_ME # spec.template.spec.containers.image + ports: + - containerPort: '5002' + name: http1 + resources: + limits: + cpu: '1' + memory: 1Gi + startupProbe: + failureThreshold: 1 + periodSeconds: 240 + tcpSocket: + port: '5002' + timeoutSeconds: 240 serviceAccountName: CHANGE_ME # spec.template.spec.serviceAccountName timeoutSeconds: '600' traffic: - - latestRevision: true - percent: 100 + - latestRevision: true + percent: 100 diff --git a/src/bot/dispute.ts b/src/bot/dispute.ts index 7e180c9..41fdcfe 100644 --- a/src/bot/dispute.ts +++ b/src/bot/dispute.ts @@ -48,7 +48,6 @@ export const approveDisputeStake: Step = async ({ onChainProvider, chainId }, re export const disputeTree: Step = async ({ onChainProvider, chainId }, report) => { try { - const { disputeToken, disputeAmount } = report?.params; const { signer } = report?.disputeReport; const txnOverrides = @@ -59,7 +58,7 @@ export const disputeTree: Step = async ({ onChainProvider, chainId }, report) => } : {}; - const disputeReceipt = await onChainProvider.sendApproveTxn(signer, disputeToken, disputeAmount, txnOverrides); + const disputeReceipt = await onChainProvider.sendDisputeTxn(signer, 'Dispute detected', txnOverrides); return Result.Success({ ...report, disputeReport: { ...report.disputeReport, disputeReceipt } }); } catch (err) { diff --git a/src/bot/runner.ts b/src/bot/runner.ts index d94bf18..d184aaa 100644 --- a/src/bot/runner.ts +++ b/src/bot/runner.ts @@ -1,6 +1,9 @@ +import { HOUR } from '@angleprotocol/sdk'; +import { getAddress } from 'ethers/lib/utils'; import moment from 'moment'; -import { NULL_ADDRESS } from '../constants'; +import { ALLOWED_OVER_CLAIM, NULL_ADDRESS } from '../constants'; +import { ALERTING_DELAY } from '../constants/alertingDelay'; import { buildMerklTree } from '../helpers'; import createDiffTable from '../helpers/diffTable'; import { BotError, MerklReport, Resolver, Result, Step, StepResult } from '../types/bot'; @@ -32,6 +35,7 @@ export const checkOnChainParams: Step = async ({ onChainProvider, logger }, repo return Result.Success({ ...report, params }); } catch (err) { + console.error(err); return Result.Error({ code: BotError.OnChainFetch, reason: `Unable to get on-chain params: ${err}`, report }); } }; @@ -43,7 +47,17 @@ export const checkDisputeWindow: Step = async (context, report) => { if (!!disputer && disputer !== NULL_ADDRESS) return Result.Exit({ reason: 'Already disputed', report }); else if (disputeToken === NULL_ADDRESS) return Result.Exit({ reason: 'No dispute token set', report }); - else if (endOfDisputePeriod <= startTime) return Result.Exit({ reason: 'Not in dispute period', report }); + else if (endOfDisputePeriod <= startTime) { + // Check delay since last dispute period and eventually send an alert + if (endOfDisputePeriod + ALERTING_DELAY[context.chainId] * HOUR <= startTime) { + await context.logger.error( + context, + `Last update was ${((startTime - endOfDisputePeriod) / HOUR)?.toFixed(2)} hours ago`, + BotError.AlertDelay + ); + } + return Result.Exit({ reason: 'Not in dispute period', report }); + } return Result.Success(report); } catch (err) { return Result.Error({ code: BotError.OnChainFetch, reason: `Unable to check dispute status: ${err}`, report }); @@ -125,7 +139,17 @@ export const checkOverclaimedRewards: Step = async ({ onChainProvider }, report) expandedHoldersReport = await validateClaims(onChainProvider, holdersReport); const overclaims = expandedHoldersReport.overclaimed; - if (overclaims.length > 0) throw overclaims.join('\n'); + if ( + overclaims?.filter((a) => { + try { + const add = a?.split(':')[0]; + return !(ALLOWED_OVER_CLAIM?.includes(add?.toLowerCase()) || ALLOWED_OVER_CLAIM?.includes(getAddress(add))); + } catch { + return true; + } + }).length > 0 + ) + throw overclaims.join('\n'); return Result.Success({ ...report, holdersReport: expandedHoldersReport }); } catch (reason) { diff --git a/src/bot/validity.ts b/src/bot/validity.ts index 27d712a..8324d27 100644 --- a/src/bot/validity.ts +++ b/src/bot/validity.ts @@ -37,6 +37,10 @@ export async function validateHolders( startTree.lastUpdateEpoch * HOUR, endTree.lastUpdateEpoch * HOUR ); + const activeDistributionsObject = {}; + for (const dist of activeDistributions) { + activeDistributionsObject[dist.base.rewardId] = dist; + } const poolName = {}; @@ -83,7 +87,7 @@ export async function validateHolders( } let ratePerEpoch; try { - const solidityDist = activeDistributions?.find((d) => d.base.rewardId === k); + const solidityDist = activeDistributionsObject[k]; ratePerEpoch = Int256.from(solidityDist?.base?.amount ?? 0, decimals)?.toNumber() / solidityDist?.base?.numEpoch; } catch { ratePerEpoch = 1; @@ -116,17 +120,28 @@ export async function validateHolders( } for (const k of Object.keys(changePerDistrib)) { - const solidityDist = activeDistributions?.find((d) => d.base.rewardId === k); + const solidityDist = activeDistributionsObject[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) + !!startTree.rewards[k]?.lastUpdateEpoch && + changePerDistrib[k].epoch > (endTree.rewards[k].lastUpdateEpoch - startTree.rewards[k].lastUpdateEpoch) * 1.001 // 0.1% tolerance + ) { + overDistributed.push( + `Distrib ${k.slice(0, 10)} distributed. Theory ${ + endTree.rewards[k].lastUpdateEpoch - startTree.rewards[k].lastUpdateEpoch + }, found ${changePerDistrib[k].epoch} ` + ); + } else if ( + !startTree.rewards[k]?.lastUpdateEpoch && + changePerDistrib[k].epoch > (endTree.rewards[k].lastUpdateEpoch - solidityDist?.base?.epochStart / HOUR) * 1.001 // 0.1% tolerance ) { - overDistributed.push(k); + overDistributed.push( + `Distrib ${k.slice(0, 10)} distributed. Theory ${ + endTree.rewards[k].lastUpdateEpoch - solidityDist?.base?.epochStart / HOUR + }, found ${changePerDistrib[k].epoch} ` + ); } } diff --git a/src/constants/alertingDelay.ts b/src/constants/alertingDelay.ts new file mode 100644 index 0000000..0ea34b5 --- /dev/null +++ b/src/constants/alertingDelay.ts @@ -0,0 +1,12 @@ +import { ChainId } from '@angleprotocol/sdk'; + +export const ALERTING_DELAY: { [chainId: number]: number } = { + [ChainId.POLYGON]: 5, + [ChainId.BASE]: 5, + [ChainId.MAINNET]: 16, + [ChainId.POLYGONZKEVM]: 16, + [ChainId.CORE]: 16, + [ChainId.THUNDERCORE]: 16, + [ChainId.ARBITRUM]: 7, + [ChainId.OPTIMISM]: 9, +}; diff --git a/src/constants/index.ts b/src/constants/index.ts index 609b458..b2957e8 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -4,6 +4,13 @@ export const MULTICALL_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11'; export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; export const GITHUB_URL = `https://raw.githubusercontent.com/AngleProtocol/merkl-rewards/main/`; // TODO switch to public gcloud buckets export const ANGLE_API = `https://api.angle.money/`; -export const MAX_NUM_SUBCALLS = 50; +export const MAX_NUM_SUBCALLS = 20; export const HOUR = 3600; export const YEAR = 3600 * 24 * 365; +export const MERKL_TREE_OPTIONS = { hashLeaves: false, sortLeaves: true, sortPairs: true }; + +export const ALLOWED_OVER_CLAIM = [ + '0x7A42A8274f7b2687c7A583A388d5e56d2987A3f6', + '0x3f9763cE4F230368437f45CE81Be598c253Db338', + '0x2A6Be69cd729288006f831737D5032f15626d52c', +]; diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 36600e4..5c1e3ee 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,8 +1,8 @@ import { AggregatedRewardsType, + AMM, + AMMAlgorithm, AMMAlgorithmMapping, - AMMAlgorithmType, - AMMType, Erc20__factory, Multicall__factory, UnderlyingTreeType, @@ -11,11 +11,11 @@ import { BigNumber, ethers, utils } from 'ethers'; import keccak256 from 'keccak256'; import MerkleTree from 'merkletreejs'; -import { MULTICALL_ADDRESS } from '../constants'; +import { MERKL_TREE_OPTIONS, MULTICALL_ADDRESS } from '../constants'; import { httpProvider } from '../providers'; import { PoolInterface } from '../types'; -export const fetchPoolName = async (chainId: number, pool: string, amm: AMMType) => { +export const fetchPoolName = async (chainId: number, pool: string, amm: AMM) => { const provider = httpProvider(chainId); const multicall = Multicall__factory.connect(MULTICALL_ADDRESS, provider); const poolInterface = PoolInterface(AMMAlgorithmMapping[amm]); @@ -32,7 +32,7 @@ export const fetchPoolName = async (chainId: number, pool: string, amm: AMMType) target: pool, allowFailure: false, }, - ...(AMMAlgorithmMapping[amm] === AMMAlgorithmType.UniswapV3 + ...(AMMAlgorithmMapping[amm] === AMMAlgorithm.UniswapV3 ? [ { callData: poolInterface.encodeFunctionData('fee'), @@ -47,7 +47,7 @@ export const fetchPoolName = async (chainId: number, pool: string, amm: AMMType) const token0 = poolInterface.decodeFunctionResult('token0', res[i++].returnData)[0]; const token1 = poolInterface.decodeFunctionResult('token1', res[i++].returnData)[0]; let fee; - if (AMMAlgorithmMapping[amm] === AMMAlgorithmType.UniswapV3) { + if (AMMAlgorithmMapping[amm] === AMMAlgorithm.UniswapV3) { fee = poolInterface.decodeFunctionResult('fee', res[i].returnData)[0]; } calls = [ @@ -66,7 +66,7 @@ export const fetchPoolName = async (chainId: number, pool: string, amm: AMMType) const token0Symbol = erc20Interface.decodeFunctionResult('symbol', res[0].returnData)[0]; const token1Symbol = erc20Interface.decodeFunctionResult('symbol', res[1].returnData)[0]; - return `${AMMType[amm]} ${token0Symbol}-${token1Symbol}-${fee ?? ``}`; + return `${AMM[amm]} ${token0Symbol}-${token1Symbol}-${fee ?? ``}`; }; export const round = (n: number, dec: number) => Math.round(n * 10 ** dec) / 10 ** dec; @@ -116,7 +116,7 @@ export const buildMerklTree = ( } } } - const tree = new MerkleTree(leaves, keccak256, { hashLeaves: false, sortPairs: true, sortLeaves: false }); + const tree = new MerkleTree(leaves, keccak256, MERKL_TREE_OPTIONS); return { tokens, diff --git a/src/helpers/logger/ConsoleLogger.ts b/src/helpers/logger/ConsoleLogger.ts index ba2afef..f112bc4 100644 --- a/src/helpers/logger/ConsoleLogger.ts +++ b/src/helpers/logger/ConsoleLogger.ts @@ -4,7 +4,7 @@ import { BigNumber } from 'ethers'; import { DisputeContext } from '../../bot/context'; import { OnChainParams } from '../../providers/on-chain/OnChainProvider'; -import { MerklReport } from '../../types/bot'; +import { BotError, MerklReport } from '../../types/bot'; import Logger from './Logger'; export default class ConsoleLogger extends Logger { @@ -52,8 +52,7 @@ export default class ConsoleLogger extends Logger { override error = async (context, reason: string, code?: number, report?: MerklReport) => { const log = (...a) => console.log(chalk.red(...a)); - - log('[CHECKS ERROR]:', reason); + log('[CHECKS ERROR]:', BotError[code], reason); }; override success = async (context, reason: string, report?: MerklReport) => { diff --git a/src/helpers/logger/DiscordWebhookLogger.ts b/src/helpers/logger/DiscordWebhookLogger.ts index 4d75e56..467f3e0 100644 --- a/src/helpers/logger/DiscordWebhookLogger.ts +++ b/src/helpers/logger/DiscordWebhookLogger.ts @@ -62,6 +62,7 @@ export default class DiscordWebhookLogger extends Logger { errorTitles[BotError.TreeFetch] = '🔴 Merkle tree data unavailable'; errorTitles[BotError.NegativeDiff] = '🚸 Negative diff detected'; errorTitles[BotError.AlreadyClaimed] = '🚸 Already claimed detected'; + errorTitles[BotError.AlertDelay] = '🚸 Last update too far ago'; try { await sendDiscordNotification({ diff --git a/src/providers/on-chain/OnChainProvider.ts b/src/providers/on-chain/OnChainProvider.ts index 1904509..888ebcd 100644 --- a/src/providers/on-chain/OnChainProvider.ts +++ b/src/providers/on-chain/OnChainProvider.ts @@ -1,5 +1,5 @@ -import { AMMType } from '@angleprotocol/sdk'; -import { ExtensiveDistributionParametersStructOutput } from '@angleprotocol/sdk/dist/constants/types/DistributionCreator'; +import { AMM } from '@angleprotocol/sdk'; +import { ExtensiveDistributionParametersStructOutput } from '@angleprotocol/sdk/dist/generated/DistributionCreator'; import { BigNumber, ContractReceipt, Overrides, Wallet } from 'ethers'; import { HolderClaims, HolderDetail } from '../../types/holders'; @@ -29,7 +29,7 @@ export default abstract class OnChainProvider extends ExponentialBackoffProvider 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 poolName: (pool: string, amm: AMM) => Promise; protected abstract claimed: (holderDetails: HolderDetail[]) => Promise; protected abstract approve: ( keeper: Wallet, @@ -62,7 +62,7 @@ export default abstract class OnChainProvider extends ExponentialBackoffProvider return this.retryWithExponentialBackoff(this.claimed, this.fetchParams, holderDetails); } - async fetchPoolName(pool: string, amm: AMMType): Promise { + async fetchPoolName(pool: string, amm: AMM): Promise { return this.retryWithExponentialBackoff(this.poolName, this.fetchParams, pool, amm); } diff --git a/src/providers/on-chain/RpcProvider.ts b/src/providers/on-chain/RpcProvider.ts index cb40877..5bc0055 100644 --- a/src/providers/on-chain/RpcProvider.ts +++ b/src/providers/on-chain/RpcProvider.ts @@ -1,14 +1,14 @@ import { + AMM, + AMMAlgorithm, AMMAlgorithmMapping, - AMMAlgorithmType, - AMMType, DistributionCreator__factory, Distributor__factory, Erc20__factory, Multicall__factory, PoolInterface, } from '@angleprotocol/sdk'; -import { Multicall3 } from '@angleprotocol/sdk/dist/constants/types/Multicall'; +import { Multicall3 } from '@angleprotocol/sdk/dist/generated/Multicall'; import { BigNumber, Overrides, providers, Wallet } from 'ethers'; import { HolderDetail } from '../../types/holders'; @@ -64,16 +64,30 @@ export default class RpcProvider extends OnChainProvider { override activeDistributions = async () => { const instance = DistributionCreator__factory.connect(this.distributorCreator, this.provider); - return instance.getActiveDistributions({ blockTag: this.blockNumber }); + 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 }); + let lastIndex = 0; + let distributions = []; + + while (true) { + const call = await instance['getDistributionsBetweenEpochs(uint32,uint32,uint32,uint32)'](start, end, lastIndex, 50); + const rewards = call[0]; + if (!!rewards) { + distributions = distributions.concat(call[0]); + } + + if (lastIndex === call[1].toNumber()) break; + lastIndex = call[1].toNumber(); + } + + return distributions; }; - override poolName = async (pool: string, amm: AMMType) => { + override poolName = async (pool: string, amm: AMM) => { const multicall = Multicall__factory.connect('0xcA11bde05977b3631167028862bE2a173976CA11', this.provider); const poolInterface = PoolInterface(AMMAlgorithmMapping[amm]); const erc20Interface = Erc20__factory.createInterface(); @@ -89,7 +103,7 @@ export default class RpcProvider extends OnChainProvider { target: pool, allowFailure: false, }, - ...(AMMAlgorithmMapping[amm] === AMMAlgorithmType.UniswapV3 + ...(AMMAlgorithmMapping[amm] === AMMAlgorithm.UniswapV3 ? [ { callData: poolInterface.encodeFunctionData('fee'), @@ -104,7 +118,7 @@ export default class RpcProvider extends OnChainProvider { const token0 = poolInterface.decodeFunctionResult('token0', res[i++].returnData)[0]; const token1 = poolInterface.decodeFunctionResult('token1', res[i++].returnData)[0]; let fee; - if (AMMAlgorithmMapping[amm] === AMMAlgorithmType.UniswapV3) { + if (AMMAlgorithmMapping[amm] === AMMAlgorithm.UniswapV3) { fee = poolInterface.decodeFunctionResult('fee', res[i].returnData)[0]; } calls = [ @@ -123,7 +137,7 @@ export default class RpcProvider extends OnChainProvider { const token0Symbol = erc20Interface.decodeFunctionResult('symbol', res[0].returnData)[0]; const token1Symbol = erc20Interface.decodeFunctionResult('symbol', res[1].returnData)[0]; - return `${AMMType[amm]} ${token0Symbol}-${token1Symbol}-${fee ?? ``}`; + return `${AMM[amm]} ${token0Symbol}-${token1Symbol}-${fee ?? ``}`; }; override onChainParams = async () => { @@ -174,6 +188,7 @@ export default class RpcProvider extends OnChainProvider { ]; const result = await batchMulticallCall(multicallContractCall, multicall, { data: calls, blockNumber: this.blockNumber }); + let i = 0; return { disputeToken: distributor.decodeFunctionResult('disputeToken', result[i++])[0], diff --git a/src/types/bot.ts b/src/types/bot.ts index a61bf7a..4bef5c4 100644 --- a/src/types/bot.ts +++ b/src/types/bot.ts @@ -56,6 +56,7 @@ export enum BotError { KeeperCreate, KeeperApprove, KeeperDispute, + AlertDelay, } export type Exit = { err: false; exit: true; res: T }; diff --git a/src/types/index.ts b/src/types/index.ts index 2cf697a..bcfb8fa 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,4 @@ -import { AMMAlgorithmType } from '@angleprotocol/sdk'; +import { AMMAlgorithm } from '@angleprotocol/sdk'; export declare type PositionType = { id: string; @@ -10,14 +10,14 @@ export declare type PositionType = { liquidity: string; }; -export type SwapType = { +export type SwapType = { amount0: string; amount1: string; amountUSD: string; tick: string; timestamp: string; transaction: { blockNumber: string }; -} & (T extends AMMAlgorithmType.AlgebraV1_9 ? { price: string } : { sqrtPriceX96: string }); +} & (T extends AMMAlgorithm.AlgebraV1_9 ? { price: string } : { sqrtPriceX96: string }); export type Price = { [token: string]: number; diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts index 2084147..3157fb5 100644 --- a/src/types/interfaces.ts +++ b/src/types/interfaces.ts @@ -1,12 +1,12 @@ -import { AlgebraV19Pool__factory, AMMAlgorithmType, UniswapV3Pool__factory } from '@angleprotocol/sdk'; +import { AlgebraV19Pool__factory, AMMAlgorithm, UniswapV3Pool__factory } from '@angleprotocol/sdk'; import { Interface } from '@ethersproject/abi'; -export const PoolInterface = (ammType: AMMAlgorithmType): Interface => { - if (ammType === AMMAlgorithmType.AlgebraV1_9) { +export const PoolInterface = (AMM: AMMAlgorithm): Interface => { + if (AMM === AMMAlgorithm.AlgebraV1_9) { return AlgebraV19Pool__factory.createInterface(); - } else if (ammType === AMMAlgorithmType.UniswapV3) { + } else if (AMM === AMMAlgorithm.UniswapV3) { return UniswapV3Pool__factory.createInterface(); - } else if (ammType === AMMAlgorithmType.BaseX) { + } else if (AMM === AMMAlgorithm.BaseX) { return; } else throw new Error('Invalid AMM type'); }; diff --git a/src/utils/discord.ts b/src/utils/discord.ts index 61239b8..8935df1 100644 --- a/src/utils/discord.ts +++ b/src/utils/discord.ts @@ -1,6 +1,8 @@ import { ChainId } from '@angleprotocol/sdk'; import { APIEmbedField, Client, EmbedBuilder, GatewayIntentBits, Partials, TextChannel } from 'discord.js'; +import { getBotName, getEnv } from '.'; + const colorBySeverity = { info: 0x00bfff, success: 0x00dd55, @@ -53,9 +55,14 @@ export async function sendDiscordNotification(params: { return; } + if (!!params.description) { + try { + params.description = params?.description?.slice(0, 1000); + } catch {} + } const exampleEmbed = new EmbedBuilder() .setAuthor({ - name: `Merkle Dispute Bot ${env !== 'prod' ? '[DEV]' : ''}`, + name: `Merkle Dispute Bot ${getEnv() !== 'prod' ? '[DEV]' : getBotName() ?? ''}`, iconURL: 'https://merkl.angle.money/images/merkl-apple-touch-icon.png', url: 'https://github.com/AngleProtocol/merkl-dispute', }) diff --git a/src/utils/index.ts b/src/utils/index.ts index 71bfe30..f87d294 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,7 +1,7 @@ import 'dotenv/config'; import { ChainId, EnvType, Multicall, withRetry } from '@angleprotocol/sdk'; -import { Multicall3 } from '@angleprotocol/sdk/dist/constants/types/Multicall'; +import { Multicall3 } from '@angleprotocol/sdk/dist/generated/Multicall'; import { BytesLike, constants } from 'ethers'; import { MAX_NUM_SUBCALLS } from '../constants'; @@ -23,6 +23,10 @@ export function getChainId(): ChainId { return parseInt(value) as ChainId; } +export function getBotName(): string | undefined { + return process.env['BOT_NAME']; +} + export async function retryWithExponentialBackoff(fn: (...any) => Promise, retries = 5, delay = 500, ...args): Promise { try { const result = await fn(...args); @@ -74,7 +78,8 @@ export async function multicallContractCall( }, !!args.blockNumber ? args.blockNumber : null ); - } catch { + } catch (e) { + console.error(e); throw new Error('❌ failed to decode multicall data'); } return contract.interface.decodeFunctionResult('aggregate3', result)[0].map((r) => r?.returnData); diff --git a/src/utils/report.ts b/src/utils/report.ts deleted file mode 100644 index 353aa78..0000000 --- a/src/utils/report.ts +++ /dev/null @@ -1,858 +0,0 @@ -import { - AggregatedRewardsType, - ALMType, - AMMAlgorithmMapping, - AMMType, - ChainId, - DistributionCreator__factory, - Distributor__factory, - Erc20__factory, - formatNumber, - getAmountsForLiquidity, - getTickAtSqrtRatio, - Int256, - MerklAPIData, - MerklRewardDistributionType, - merklSubgraphAMMEndpoints, - MerklSupportedChainIdsType, - Multicall__factory, - NFTManagerAddress, - NonFungiblePositionManagerInterface, - PoolInterface, - PoolStateName, - registry, - SwapPriceField, - UnderlyingTreeType, -} from '@angleprotocol/sdk'; -import dotenv from 'dotenv'; -import JSBI from 'jsbi'; - -dotenv.config(); - -import { ExtensiveDistributionParametersStructOutput } from '@angleprotocol/sdk/dist/constants/types/DistributionCreator'; -import { Multicall3 } from '@angleprotocol/sdk/dist/constants/types/Multicall'; -import { BN2Number } from '@angleprotocol/sdk/dist/utils'; -import axios from 'axios'; -import { BigNumber, BigNumberish, utils } from 'ethers'; -import { getAddress } from 'ethers/lib/utils'; -import request from 'graphql-request'; - -import { ANGLE_API, GITHUB_URL, HOUR, MULTICALL_ADDRESS, YEAR } from '../constants'; -import { round } from '../helpers'; -import { positionsQuery } from '../helpers/queries'; -import { httpProvider } from '../providers'; -import { MerklIndexType } from '../providers/merkl-roots/GithubRootsProvider'; -import { AccumulatedRewards, PositionType, Price, UserStats } from '../types'; -import { getBlockAfterTimestamp } from '.'; - -export const userParamsCheck = (user: string, pool: string, startTimestamp: number, endTimestamp: number): void => { - if (!getAddress(user)) throw new Error('Invalid user address'); - if (startTimestamp >= endTimestamp) throw new Error('Invalid timestamps'); - if (!!pool && !getAddress(pool)) throw new Error('Invalid pool address'); -}; - -export const poolParamsCheck = (pool: string, startTimestamp: number, endTimestamp: number): void => { - if (startTimestamp >= endTimestamp) throw new Error('Invalid timestamps'); - if (!!pool && !getAddress(pool)) throw new Error('Invalid pool address'); -}; - -export const almCheck = (merklAPIData: MerklAPIData, pool: string, almAddress: string, almType: ALMType): void => { - const ALMname = ALMType[almType]; - const poolApiData = merklAPIData?.pools?.[getAddress(pool)]; - // Check that the almAddress is really linked to this pool - const filteredALM = poolApiData.almDetails.filter((alm) => alm.address === almAddress.toLowerCase()); - - // this would be problematic as we would not be able to distinguish between the two vaults APRs - if (filteredALM.length > 1) throw new Error(`Multiple ${ALMname} for ${getAddress(pool)}`); - if (filteredALM.length === 0 || Number(filteredALM[0].origin) !== almType) throw new Error('Invalid ALM address'); -}; -export const roundDownWhileKeyNotFound = (merklIndex: MerklIndexType, timestamp: number): number => { - let epoch = Math.floor(timestamp / HOUR); - while (!Object.values(merklIndex).includes(epoch)) { - epoch -= 1; - } - return epoch; -}; - -export const fetchAccumulatedRewards = async (chainId: number, epoch: number): Promise => { - return ( - await axios.get(GITHUB_URL + `${chainId + `/backup/rewards_${epoch}.json`}`, { - timeout: 5000, - }) - ).data; -}; - -export const poolName = (poolApiData: MerklAPIData['pools'][string]): string => { - return `${AMMType[poolApiData.amm]} ${poolApiData.tokenSymbol0}-${poolApiData.tokenSymbol1} ${poolApiData.poolFee + '%' ?? ``}`; -}; - -export const fetchReportData = async ( - chainId: number -): Promise<{ prices: Price; merklIndex: MerklIndexType; merklAPIData: MerklAPIData }> => { - const promises = []; - - const prices = {}; - promises.push( - axios.get<{ rate: number; token: string }[]>(ANGLE_API + `v1/prices`).then((res) => { - res.data.forEach((p) => (prices[p.token] = p.rate)); - }) - ); - - let merklIndex: MerklIndexType; - promises.push( - axios - .get(GITHUB_URL + `${chainId + `/index.json`}`, { - timeout: 5000, - }) - .then((res) => { - merklIndex = res.data; - }) - ); - - let merklAPIData: MerklAPIData; - promises.push( - axios - .get(ANGLE_API + `v1/merkl`, { - timeout: 5000, - }) - .then((res) => { - merklAPIData = res.data; - }) - ); - - await Promise.all(promises); - - return { prices, merklIndex, merklAPIData }; -}; - -export const fetchRewardJson = async ( - chainId: ChainId, - merklIndex: MerklIndexType, - startTimestamp: number, - endTimestamp: number -): Promise<{ - startEpoch: number; - endEpoch: number; - startAccumulatedRewards: AggregatedRewardsType; - endAccumulatedRewards: AggregatedRewardsType; -}> => { - const startEpoch = roundDownWhileKeyNotFound(merklIndex, startTimestamp); - const endEpoch = roundDownWhileKeyNotFound(merklIndex, endTimestamp); - let startAccumulatedRewards: AggregatedRewardsType, endAccumulatedRewards: AggregatedRewardsType; - await Promise.all([ - fetchAccumulatedRewards(chainId, startEpoch).then((res) => (startAccumulatedRewards = res)), - fetchAccumulatedRewards(chainId, endEpoch).then((res) => (endAccumulatedRewards = res)), - ]); - return { startEpoch, endEpoch, startAccumulatedRewards, endAccumulatedRewards }; -}; - -export const statsUserPool = async ( - chainId: ChainId, - user: string, - pool: string, - startEpoch: number, - endEpoch: number, - accumulatedRewards: AccumulatedRewards[], - merklAPIData: MerklAPIData, - prices: Price, - countALM: boolean, - log = true -): Promise<{ startStat: UserStats[]; endStat: UserStats[] }> => { - const merklAPIPoolData = merklAPIData?.pools?.[getAddress(pool)]; - const poolRewards = accumulatedRewards.filter((a) => getAddress(a.PoolAddress) === getAddress(pool)); - /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - INTERFACES - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ - const amm = merklAPIPoolData.amm; - const ammAlgo = AMMAlgorithmMapping[amm]; - - const Erc20Interface = Erc20__factory.createInterface(); - const poolInterface = PoolInterface(ammAlgo); - const nftManagerInterface = NonFungiblePositionManagerInterface(ammAlgo); - const poolStateName = PoolStateName[ammAlgo]; - const swapPriceField = SwapPriceField[ammAlgo]; - const periodReward = poolRewards.reduce((prev, curr) => prev + curr.Earned * prices[curr.Token], 0); - - if (log) { - console.log('\n//////////////////////////////////////////////////////////////////////////////////////////////////////////////////\n'); - console.log(`Now, let's break down rewards for the pool ${poolName(merklAPIPoolData)} (${pool}): \n`); - console.log(`Over the period of interest this address earned the following: \n`); - console.table(poolRewards, ['Earned', 'Token', 'Origin']); - console.log( - `At current prices, this is worth ~$${formatNumber(periodReward)}, which would make ~$${formatNumber( - (periodReward * YEAR) / (endEpoch * HOUR - startEpoch * HOUR) - )} over a year. \n` - ); - } - - const alms = merklAPIPoolData.almDetails; - const token0 = merklAPIPoolData.token0; - const token0Decimals = merklAPIPoolData.decimalToken0; - const token0Symbol = merklAPIPoolData.tokenSymbol0; - const token1 = merklAPIPoolData.token1; - const token1Decimals = merklAPIPoolData.decimalToken1; - const token1Symbol = merklAPIPoolData.tokenSymbol1; - const owners = countALM ? [user?.toLowerCase()].concat(alms.map((a) => a.address.toLowerCase())) : [user?.toLowerCase()]; - - const result = await request< - { - nft: PositionType[]; - nftPast: PositionType[]; - direct: PositionType[]; - directPast: PositionType[]; - }, - any - >(merklSubgraphAMMEndpoints('prod')[chainId][amm], positionsQuery, { - owners: owners, - pool: pool?.toLowerCase(), - timestamp: startEpoch * HOUR, - }); - - const directPositions = result.direct.concat(result.directPast); - const nftPositions = result.nft.concat(result.nftPast); - - const startBlockNumber = await getBlockAfterTimestamp(chainId, startEpoch * HOUR); - const endBlockNumber = await getBlockAfterTimestamp(chainId, endEpoch * HOUR); - - const provider = httpProvider(chainId); - const multicall = Multicall__factory.connect(MULTICALL_ADDRESS, provider); - const calls: Multicall3.Call3Struct[] = []; - - // 0 - Pool generic data - calls.push( - { - allowFailure: true, - callData: poolInterface.encodeFunctionData(poolStateName), - target: pool, - }, - { - allowFailure: true, - callData: poolInterface.encodeFunctionData('liquidity'), - target: pool, - }, - { - allowFailure: true, - callData: Erc20Interface.encodeFunctionData('balanceOf', [pool]), - target: token0, - }, - { - allowFailure: true, - callData: Erc20Interface.encodeFunctionData('balanceOf', [pool]), - target: token1, - } - ); - - // 1 - User direct positions - for (const pos of directPositions.filter((p) => p.owner === user.toLowerCase())) { - calls.push({ - allowFailure: true, - callData: poolInterface.encodeFunctionData('positions', [ - utils.solidityKeccak256(['address', 'int24', 'int24'], [pos.owner, pos.tickLower, pos.tickUpper]), - ]), - target: pool, - }); - } - - // 2 - User NFT positions - for (const pos of nftPositions.filter((p) => p.owner === user.toLowerCase())) { - calls.push({ - allowFailure: true, - callData: nftManagerInterface.encodeFunctionData('positions', [pos.id]), - target: NFTManagerAddress[chainId][amm], - }); - } - - if (countALM) { - // ALM data - for (const alm of alms) { - // 3 - ALM NFT positions - for (const pos of directPositions.filter((p) => p.owner === alm.address.toLowerCase())) { - calls.push({ - allowFailure: true, - callData: poolInterface.encodeFunctionData('positions', [ - utils.solidityKeccak256(['address', 'int24', 'int24'], [pos.owner, pos.tickLower, pos.tickUpper]), - ]), - target: pool, - }); - } - - // 4 - ALM NFT positions - for (const pos of nftPositions.filter((p) => p.owner === alm.address.toLowerCase())) { - calls.push({ - allowFailure: true, - callData: nftManagerInterface.encodeFunctionData('positions', [pos.id]), - target: pool, - }); - } - - // 5 - Balance / Total Supply - calls.push( - { - allowFailure: true, - callData: Erc20Interface.encodeFunctionData('totalSupply'), - target: alm.address, - }, - { - allowFailure: true, - callData: Erc20Interface.encodeFunctionData('balanceOf', [user]), - target: alm.address, - } - ); - } - } - - const analyzePoolState = async (blockNumber: number, log = true) => { - const stats = [] as UserStats[]; - - const res = await multicall.callStatic.aggregate3(calls, { blockTag: blockNumber }); - let i = 0; - const sqrtPriceX96 = poolInterface.decodeFunctionResult(poolStateName, res[i++]?.returnData)[swapPriceField]?.toString(); - const liquidityInPool = poolInterface.decodeFunctionResult('liquidity', res[i++]?.returnData)[0]?.toString(); - const amount0InPool = BN2Number(Erc20Interface.decodeFunctionResult('balanceOf', res[i++]?.returnData)[0], token0Decimals); - const amount1InPool = BN2Number(Erc20Interface.decodeFunctionResult('balanceOf', res[i++]?.returnData)[0], token1Decimals); - const tvlInPool = amount0InPool * prices[token0Symbol] + amount1InPool * prices[token1Symbol]; - - const tick = getTickAtSqrtRatio(JSBI.BigInt(sqrtPriceX96)); - const addPositionInArray = (pos: PositionType, type: string, liquidity: BigNumberish) => { - const [amount0, amount1] = getAmountsForLiquidity( - sqrtPriceX96, - Number(pos.tickLower), - Number(pos.tickUpper), - BigNumber.from(liquidity) - ); - const inRange = Number(pos.tickLower) <= tick && tick < Number(pos.tickUpper); - - const tvl = BN2Number(amount0, token0Decimals) * prices[token0Symbol] + BN2Number(amount1, token1Decimals) * prices[token1Symbol]; - const positionRewards = - poolRewards.filter((p) => p.Origin === type)?.reduce((prev, curr) => prev + curr.Earned * prices[curr.Token], 0) ?? 0; - - if (BN2Number(amount0, token0Decimals) > 0 || BN2Number(amount1, token1Decimals) > 0) { - stats.push({ - lowerTick: pos.tickLower, - tick, - upperTick: pos.tickUpper, - type, - amount0: BN2Number(amount0, token0Decimals), - amount1: BN2Number(amount1, token1Decimals), - liquidity: liquidity?.toString(), - inRange, - tvl, - earned: positionRewards, - propFee: inRange ? round(Int256.from(liquidity, 0).mul(10000).div(liquidityInPool).toNumber() / 100, 2) : 0, - propAmount0: inRange ? round((BN2Number(amount0, token0Decimals) / amount0InPool) * 100, 2) : 0, - propAmount1: inRange ? round((BN2Number(amount1, token1Decimals) / amount1InPool) * 100, 2) : 0, - inducedAPR: round(((positionRewards * YEAR) / (endEpoch * HOUR - startEpoch * HOUR) / tvl) * 100, 3), - }); - } - }; - - for (const pos of directPositions.filter((p) => p.owner === user.toLowerCase())) { - try { - const position = poolInterface.decodeFunctionResult('positions', res[i]?.returnData); - addPositionInArray(pos, AMMType[amm], position.liquidity); - } catch (e) { - console.error(e); - } - i++; - } - - for (const pos of nftPositions.filter((p) => p.owner === user.toLowerCase())) { - try { - const position = nftManagerInterface.decodeFunctionResult('positions', res[i]?.returnData); - addPositionInArray(pos, AMMType[amm], position.liquidity); - } catch (e) { - console.error(e); - } - i++; - } - - if (countALM) { - for (const alm of alms) { - try { - let j = i; - let amount0InAlm = 0; - let amount1InAlm = 0; - let liquidityInAlm = BigNumber.from(0); - - for (const pos of directPositions.filter((p) => p.owner === alm.address.toLowerCase())) { - const position = poolInterface.decodeFunctionResult('positions', res[j++]?.returnData); - const [aux0, aux1] = getAmountsForLiquidity( - sqrtPriceX96, - Number(pos.tickLower), - Number(pos.tickUpper), - BigNumber.from(position.liquidity) - ); - const inRange = Number(pos.tickLower) <= tick && tick < Number(pos.tickUpper); - - amount0InAlm += BN2Number(aux0, token0Decimals); - amount1InAlm += BN2Number(aux1, token1Decimals); - if (inRange) liquidityInAlm = liquidityInAlm.add(position.liquidity); - } - - // 4 - ALM NFT positions - for (const pos of nftPositions.filter((p) => p.owner === alm.address.toLowerCase())) { - const position = nftManagerInterface.decodeFunctionResult('positions', res[j++]?.returnData); - const [aux0, aux1] = getAmountsForLiquidity( - sqrtPriceX96, - Number(pos.tickLower), - Number(pos.tickUpper), - BigNumber.from(position.liquidity) - ); - const inRange = Number(pos.tickLower) <= tick && tick < Number(pos.tickUpper); - - amount0InAlm += BN2Number(aux0, token0Decimals); - amount1InAlm += BN2Number(aux1, token1Decimals); - if (inRange) liquidityInAlm = liquidityInAlm.add(position.liquidity); - } - - // 5 - Balance / Total Supply - const supply = BN2Number(Erc20Interface.decodeFunctionResult('totalSupply', res[j++]?.returnData)[0]); - const balance = BN2Number(Erc20Interface.decodeFunctionResult('balanceOf', res[j++]?.returnData)[0]); - const proportion = balance / supply; - - const userAmount0InAlm = proportion * amount0InAlm; - const userAmount1InAlm = proportion * amount1InAlm; - - const type = ALMType[alm.origin]; - const tvl = userAmount0InAlm * prices[token0Symbol] + userAmount1InAlm * prices[token1Symbol]; - const positionRewards = - poolRewards.filter((p) => p.Origin === type)?.reduce((prev, curr) => prev + curr.Earned * prices[curr.Token], 0) ?? 0; - - if (userAmount0InAlm !== 0 || userAmount1InAlm !== 0) { - stats.push({ - type, - amount0: userAmount0InAlm, - amount1: userAmount1InAlm, - liquidity: liquidityInAlm - .mul(Math.round(proportion * 1e8)) - .div(1e8) - .toString(), - tvl, - earned: positionRewards, - propFee: round((proportion * Int256.from(liquidityInAlm, 0).mul(10000).div(liquidityInPool).toNumber()) / 100, 2), - propAmount0: round((userAmount0InAlm / amount0InPool) * 100, 2), - propAmount1: round((userAmount1InAlm / amount1InPool) * 100, 2), - inducedAPR: round(((positionRewards * YEAR) / (endEpoch * HOUR - startEpoch * HOUR) / tvl) * 100, 3), - }); - } - } catch (e) { - // console.error(e); - } - i = - i + - directPositions.filter((p) => p.owner === alm.address.toLowerCase())?.length + - nftPositions.filter((p) => p.owner === alm.address.toLowerCase())?.length + - 2; - } - } - - if (log) { - console.log(`The TVL of the pool at block ${blockNumber} based on current prices was $${tvlInPool}`); - console.table(stats); - } - return stats; - }; - if (log) console.log(`\nState of the pool at the beginning of the period (block ${startBlockNumber}): \n`); - const startStat = await analyzePoolState(startBlockNumber, log); - - if (log) console.log(`\nState of the pool at the end of the period (block ${endBlockNumber}): \n`); - const endStat = await analyzePoolState(endBlockNumber, log); - - return { startStat, endStat }; -}; - -export function aggregatedStats(stats: UserStats[]): number { - const aggregatedStats = stats.reduce( - (curr: { tvl: number; normalisedEarned: number }, stat) => { - curr.tvl += stat.tvl; - curr.normalisedEarned += (stat.inducedAPR * stat.tvl) / 100; - return curr; - }, - { tvl: 0, normalisedEarned: 0 } - ); - return round((aggregatedStats.normalisedEarned / aggregatedStats.tvl) * 100, 3); -} - -export const statsPoolRewardId = async (chainId: ChainId, pool: string, startEpoch: number, endEpoch: number, prices: Price) => { - const DistributionCreatorAddress = registry(chainId)?.Merkl?.DistributionCreator; - - const provider = httpProvider(chainId); - const multicall = Multicall__factory.connect(MULTICALL_ADDRESS, provider); - const calls: Multicall3.Call3Struct[] = []; - const DistributionCreatorInterface = DistributionCreator__factory.createInterface(); - - // 0 - Pool generic data - calls.push({ - allowFailure: true, - callData: DistributionCreatorInterface.encodeFunctionData('getDistributionsBetweenEpochs', [startEpoch * HOUR, endEpoch * HOUR]), - target: DistributionCreatorAddress, - }); - - const result = await multicall.callStatic.aggregate3(calls); - - let i = 0; - const rewards = DistributionCreatorInterface.decodeFunctionResult( - 'getDistributionsBetweenEpochs', - result[i++]?.returnData - )?.[0] as ExtensiveDistributionParametersStructOutput[]; - const filteredRewards = rewards.filter((rewards) => rewards.base.uniV3Pool.toLowerCase() === pool.toLowerCase()); - - const rewardsTokenAmount = filteredRewards.reduce( - (curr: { [token: string]: { tokenAddress: string; tokenDecimal: number; amount: number; dollarAmount: number } }, reward) => { - if (!curr['totalIncentives']) curr['totalIncentives'] = { tokenAddress: '', tokenDecimal: 0, amount: 0, dollarAmount: 0 }; - const normalizer = - (Math.min(endEpoch, reward.base.epochStart / HOUR + reward.base.numEpoch) - Math.max(startEpoch, reward.base.epochStart / HOUR)) / - reward.base.numEpoch; - const amount = BN2Number(reward.base.amount, reward.rewardTokenDecimals) * normalizer; - const dollarAmount = amount * (prices[reward.rewardTokenSymbol] ?? 0); - - if (!curr[reward.rewardTokenSymbol]) - curr[reward.rewardTokenSymbol] = { - tokenAddress: reward.base.rewardToken, - tokenDecimal: reward.rewardTokenDecimals, - amount: amount, - dollarAmount: dollarAmount, - }; - else { - curr[reward.rewardTokenSymbol].amount += amount; - curr[reward.rewardTokenSymbol].dollarAmount += dollarAmount; - } - curr['totalIncentives'].dollarAmount += dollarAmount; - return curr; - }, - {} as { [token: string]: { tokenAddress: string; tokenDecimal: number; amount: number; dollarAmount: number } } - ); - - const distributions = - !!rewards && - rewards.map((reward) => { - return { - base: { - additionalData: reward['base'].additionalData, - amount: reward['base'].amount, - boostedReward: reward['base'].boostedReward, - boostingAddress: reward['base'].boostingAddress, - epochStart: reward['base'].epochStart, - isOutOfRangeIncentivized: reward['base'].isOutOfRangeIncentivized, - numEpoch: reward['base'].numEpoch, - positionWrappers: reward['base'].positionWrappers, - propFees: reward['base'].propFees, - propToken0: reward['base'].propToken0, - propToken1: reward['base'].propToken1, - rewardId: reward['base'].rewardId, - rewardToken: reward['base'].rewardToken, - uniV3Pool: reward['base'].uniV3Pool, - wrapperTypes: reward['base'].wrapperTypes, - }, - poolFee: reward.poolFee, - rewardTokenDecimals: reward.rewardTokenDecimals, - rewardTokenSymbol: reward.rewardTokenSymbol, - token0: { - add: reward['token0'].add, - decimals: reward['token0'].decimals, - poolBalance: reward['token0'].poolBalance, - symbol: reward['token0'].symbol, - }, - token1: { - add: reward['token1'].add, - decimals: reward['token1'].decimals, - poolBalance: reward['token1'].poolBalance, - symbol: reward['token1'].symbol, - }, - } as ExtensiveDistributionParametersStructOutput; - }); - const distributionFiltered = distributions.filter((distribution) => distribution.base.uniV3Pool.toLowerCase() === pool.toLowerCase()); - - return { rewardsTokenAmount, distributionFiltered, distributions }; -}; - -export const rewardsBreakdownPool = async ( - pool: string, - startAccumulatedRewards: AggregatedRewardsType, - endAccumulatedRewards: AggregatedRewardsType -) => { - /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - INTERFACES - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ - - const filteredStartAccumulatedRewards = Object.keys(startAccumulatedRewards.rewards) - .filter((rewardId) => startAccumulatedRewards.rewards[rewardId].pool.toLowerCase() === pool.toLowerCase()) - .reduce((cur, key) => { - return Object.assign(cur, { [key]: startAccumulatedRewards.rewards[key] }); - }, {} as AggregatedRewardsType['rewards']); - - const filteredEndAccumulatedRewards = Object.keys(endAccumulatedRewards.rewards) - .filter((rewardId) => endAccumulatedRewards.rewards[rewardId].pool.toLowerCase() === pool.toLowerCase()) - .reduce((cur, key) => { - return Object.assign(cur, { [key]: endAccumulatedRewards.rewards[key] }); - }, {} as AggregatedRewardsType['rewards']); - - let holders = []; - const diffRewards = {} as UnderlyingTreeType; - const rewardsOriginBreakdown = {} as { [origin: string]: number }; - Object.keys(filteredEndAccumulatedRewards).map((rewardId) => { - diffRewards[rewardId] = {} as MerklRewardDistributionType; - diffRewards[rewardId].holders = {}; - const decimals = filteredEndAccumulatedRewards[rewardId].tokenDecimals; - - Object.keys(filteredEndAccumulatedRewards[rewardId].holders).map((user) => { - const newAmount = filteredEndAccumulatedRewards[rewardId]?.holders?.[user]?.amount; - const oldAmount = filteredStartAccumulatedRewards[rewardId]?.holders?.[user]?.amount; - const newBreakdown = filteredEndAccumulatedRewards[rewardId]?.holders?.[user]?.breakdown; - const oldBreakdown = filteredStartAccumulatedRewards[rewardId]?.holders?.[user]?.breakdown; - - if (newAmount !== oldAmount) { - const userInfo = { - amount: BN2Number(BigNumber.from(newAmount ?? 0).sub(oldAmount ?? 0), decimals).toString(), - breakdown: {} as { [origin: string]: number }, - averageBoost: 0, - }; - - for (const reason of Object.keys(newBreakdown)) { - const earned = Int256.from(BigNumber.from(newBreakdown?.[reason] ?? 0).sub(oldBreakdown?.[reason] ?? 0), decimals).toNumber(); - userInfo.breakdown[reason] = earned; - if (!rewardsOriginBreakdown[reason]) rewardsOriginBreakdown[reason] = 0; - rewardsOriginBreakdown[reason] += earned; - } - - diffRewards[rewardId].holders[user] = userInfo; - } - }); - if (Object.keys(diffRewards[rewardId].holders).length === 0) delete diffRewards[rewardId]; - if (diffRewards[rewardId]) holders.push(...Object.keys(diffRewards[rewardId].holders)); - }); - holders = [...new Set(holders)]; - - return { diffRewards, rewardsOriginBreakdown, holders }; -}; - -export const getReceiverToken = async (token: string, endAccumulatedRewards: AggregatedRewardsType) => { - const filteredEndAccumulatedRewards = Object.keys(endAccumulatedRewards.rewards) - .filter((rewardId) => endAccumulatedRewards.rewards[rewardId].token.toLowerCase() === token.toLowerCase()) - .reduce((cur, key) => { - return Object.assign(cur, { [key]: endAccumulatedRewards.rewards[key] }); - }, {} as AggregatedRewardsType['rewards']); - - let holders = []; - Object.keys(filteredEndAccumulatedRewards).map((rewardId) => { - holders.push(...Object.keys(filteredEndAccumulatedRewards[rewardId].holders)); - }); - holders = [...new Set(holders)]; - - return holders; -}; - -export const rewardsClaimed = async ( - chainId: MerklSupportedChainIdsType, - pool: string, - tokenAddress: string, - tokenDecimals: number, - holders: string[], - startEpoch: number, - endEpoch: number, - startAccumulatedRewards: AggregatedRewardsType, - endAccumulatedRewards: AggregatedRewardsType -) => { - /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - INTERFACES - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ - const distributorAddress = registry(chainId)?.Merkl?.Distributor; - - const provider = httpProvider(chainId); - const multicall = Multicall__factory.connect(MULTICALL_ADDRESS, provider); - const calls: Multicall3.Call3Struct[] = []; - const DistributorInterface = Distributor__factory.createInterface(); - - const startBlockNumber = await getBlockAfterTimestamp(chainId, startEpoch * HOUR); - const endBlockNumber = await getBlockAfterTimestamp(chainId, endEpoch * HOUR); - - // 1 - Check all on chain claimed rewards for the period - holders.map((holder) => - calls.push({ - allowFailure: true, - callData: DistributorInterface.encodeFunctionData('claimed', [holder, tokenAddress]), - target: distributorAddress, - }) - ); - - const claimed = {} as { [holder: string]: number }; - - let result = await multicall.callStatic.aggregate3(calls, { blockTag: endBlockNumber }); - holders.map( - (holder, i) => - (claimed[holder] = BN2Number(DistributorInterface.decodeFunctionResult('claimed', result[i]?.returnData)?.[0], tokenDecimals)) - ); - - result = await multicall.callStatic.aggregate3(calls, { blockTag: startBlockNumber }); - holders.map( - (holder, i) => - (claimed[holder] -= BN2Number(DistributorInterface.decodeFunctionResult('claimed', result[i]?.returnData)?.[0], tokenDecimals)) - ); - - // 2 - Compute total claimable on the token for each users - const breakdownUserClaimable = {} as { - [holder: string]: { totalClaimable: { [breakdown: string]: number }; specificClaimable: { [breakdown: string]: number } }; - }; - // First filter them by token reward - const filteredStartAccumulatedRewards = Object.keys(startAccumulatedRewards.rewards) - .filter((rewardId) => startAccumulatedRewards.rewards[rewardId].token.toLowerCase() === tokenAddress.toLowerCase()) - .reduce((cur, key) => { - return Object.assign(cur, { [key]: startAccumulatedRewards.rewards[key] }); - }, {} as AggregatedRewardsType['rewards']); - - const filteredEndAccumulatedRewards = Object.keys(endAccumulatedRewards.rewards) - .filter((rewardId) => endAccumulatedRewards.rewards[rewardId].token.toLowerCase() === tokenAddress.toLowerCase()) - .reduce((cur, key) => { - return Object.assign(cur, { [key]: endAccumulatedRewards.rewards[key] }); - }, {} as AggregatedRewardsType['rewards']); - - Object.keys(filteredEndAccumulatedRewards).map((rewardId) => { - const rewardPool = filteredEndAccumulatedRewards[rewardId].pool; - const decimals = filteredEndAccumulatedRewards[rewardId].tokenDecimals; - Object.keys(filteredEndAccumulatedRewards[rewardId].holders).map((user) => { - if (holders.includes(user)) { - const newAmount = filteredEndAccumulatedRewards[rewardId]?.holders?.[user]?.amount; - const oldAmount = filteredStartAccumulatedRewards[rewardId]?.holders?.[user]?.amount; - const newBreakdown = filteredEndAccumulatedRewards[rewardId]?.holders?.[user]?.breakdown; - const oldBreakdown = filteredStartAccumulatedRewards[rewardId]?.holders?.[user]?.breakdown; - - if (newAmount !== oldAmount) { - if (!breakdownUserClaimable[user]) breakdownUserClaimable[user] = { totalClaimable: {}, specificClaimable: {} }; - const totalEarned = BN2Number(BigNumber.from(newAmount ?? 0).sub(oldAmount ?? 0), decimals); - if (!breakdownUserClaimable[user].totalClaimable['Total']) breakdownUserClaimable[user].totalClaimable['Total'] = 0; - breakdownUserClaimable[user].totalClaimable['Total'] += totalEarned; - - if (pool.toLowerCase() === rewardPool.toLowerCase()) { - if (!breakdownUserClaimable[user].specificClaimable['Total']) breakdownUserClaimable[user].specificClaimable['Total'] = 0; - breakdownUserClaimable[user].specificClaimable['Total'] += totalEarned; - } - - for (const reason of Object.keys(newBreakdown)) { - const earned = Int256.from(BigNumber.from(newBreakdown?.[reason] ?? 0).sub(oldBreakdown?.[reason] ?? 0), decimals).toNumber(); - if (!breakdownUserClaimable[user].totalClaimable[reason]) breakdownUserClaimable[user].totalClaimable[reason] = 0; - breakdownUserClaimable[user].totalClaimable[reason] += earned; - if (pool.toLowerCase() === rewardPool.toLowerCase()) { - if (!breakdownUserClaimable[user].specificClaimable[reason]) breakdownUserClaimable[user].specificClaimable[reason] = 0; - breakdownUserClaimable[user].specificClaimable[reason] += earned; - } - } - } - } - }); - }); - - // 3 Compute proportionnaly what the users have claimed - const breakdownUserClaimed = {} as { - [holder: string]: { [breakdown: string]: string }; - }; - - Object.keys(breakdownUserClaimable).map((user) => { - breakdownUserClaimed[user] = {}; - for (const origin of Object.keys(breakdownUserClaimable[user].totalClaimable)) { - const percentClaimed = breakdownUserClaimable[user].specificClaimable[origin] / breakdownUserClaimable[user].totalClaimable[origin]; - breakdownUserClaimed[user][origin] = - breakdownUserClaimable[user].totalClaimable[origin] == 0 ? `${0}` : `${percentClaimed * claimed[user]} (${percentClaimed * 100} %)`; - } - }); - - return breakdownUserClaimed; -}; - -export const rewardsUnclaimed = async ( - chainId: MerklSupportedChainIdsType, - tokenAddress: string, - tokenDecimals: number, - holders: string[], - startEpoch: number, - endEpoch: number, - startAccumulatedRewards: AggregatedRewardsType, - endAccumulatedRewards: AggregatedRewardsType -) => { - /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - INTERFACES - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ - const distributorAddress = registry(chainId)?.Merkl?.Distributor; - - const provider = httpProvider(chainId); - const multicall = Multicall__factory.connect(MULTICALL_ADDRESS, provider); - const calls: Multicall3.Call3Struct[] = []; - const DistributorInterface = Distributor__factory.createInterface(); - - const startBlockNumber = await getBlockAfterTimestamp(chainId, startEpoch * HOUR); - const endBlockNumber = await getBlockAfterTimestamp(chainId, endEpoch * HOUR); - - // 1 - Check all on chain claimed rewards for the period - holders.map((holder) => - calls.push({ - allowFailure: true, - callData: DistributorInterface.encodeFunctionData('claimed', [holder, tokenAddress]), - target: distributorAddress, - }) - ); - - const claimed = {} as { [holder: string]: number }; - - let result = await multicall.callStatic.aggregate3(calls, { blockTag: endBlockNumber }); - holders.map( - (holder, i) => - (claimed[holder] = BN2Number(DistributorInterface.decodeFunctionResult('claimed', result[i]?.returnData)?.[0], tokenDecimals)) - ); - - result = await multicall.callStatic.aggregate3(calls, { blockTag: startBlockNumber }); - holders.map( - (holder, i) => - (claimed[holder] -= BN2Number(DistributorInterface.decodeFunctionResult('claimed', result[i]?.returnData)?.[0], tokenDecimals)) - ); - - // First filter them by token reward - const filteredStartAccumulatedRewards = Object.keys(startAccumulatedRewards.rewards) - .filter((rewardId) => startAccumulatedRewards.rewards[rewardId].token.toLowerCase() === tokenAddress.toLowerCase()) - .reduce((cur, key) => { - return Object.assign(cur, { [key]: startAccumulatedRewards.rewards[key] }); - }, {} as AggregatedRewardsType['rewards']); - - const filteredEndAccumulatedRewards = Object.keys(endAccumulatedRewards.rewards) - .filter((rewardId) => endAccumulatedRewards.rewards[rewardId].token.toLowerCase() === tokenAddress.toLowerCase()) - .reduce((cur, key) => { - return Object.assign(cur, { [key]: endAccumulatedRewards.rewards[key] }); - }, {} as AggregatedRewardsType['rewards']); - - const rewardsInfoClaim = {} as { [reason: string]: { claimable: number; claimed: number } }; - if (!rewardsInfoClaim['Total']) - rewardsInfoClaim['Total'] = { - claimable: 0, - claimed: 0, - }; - holders.map((holder) => { - rewardsInfoClaim['Total'].claimed += claimed[holder]; - }); - Object.keys(filteredEndAccumulatedRewards).map((rewardId) => { - const decimals = filteredEndAccumulatedRewards[rewardId].tokenDecimals; - Object.keys(filteredEndAccumulatedRewards[rewardId].holders).map((user) => { - if (holders.includes(user)) { - const newAmount = filteredEndAccumulatedRewards[rewardId]?.holders?.[user]?.amount; - const oldAmount = filteredStartAccumulatedRewards[rewardId]?.holders?.[user]?.amount; - const newBreakdown = filteredEndAccumulatedRewards[rewardId]?.holders?.[user]?.breakdown; - const oldBreakdown = filteredStartAccumulatedRewards[rewardId]?.holders?.[user]?.breakdown; - - if (newAmount !== oldAmount) { - const totalEarned = BN2Number(BigNumber.from(newAmount ?? 0).sub(oldAmount ?? 0), decimals); - if (!rewardsInfoClaim['Total']) rewardsInfoClaim['Total'] = { claimable: 0, claimed: 0 }; - rewardsInfoClaim['Total'].claimable += totalEarned; - - for (const reason of Object.keys(newBreakdown)) { - const earned = Int256.from(BigNumber.from(newBreakdown?.[reason] ?? 0).sub(oldBreakdown?.[reason] ?? 0), decimals).toNumber(); - if (!rewardsInfoClaim[reason]) - rewardsInfoClaim[reason] = { - claimable: 0, - claimed: 0, - }; - rewardsInfoClaim[reason].claimable += earned; - } - } - } - }); - }); - - return rewardsInfoClaim; -}; diff --git a/tests/helpers/ManualChainProvider.ts b/tests/helpers/ManualChainProvider.ts index d54a2fb..c4eab2b 100644 --- a/tests/helpers/ManualChainProvider.ts +++ b/tests/helpers/ManualChainProvider.ts @@ -1,4 +1,4 @@ -import { ExtensiveDistributionParametersStructOutput } from '@angleprotocol/sdk/dist/constants/types/DistributionCreator'; +import { ExtensiveDistributionParametersStructOutput } from '@angleprotocol/sdk/dist/generated/DistributionCreator'; import { BigNumber, ContractReceipt } from 'ethers'; import OnChainProvider, { OnChainParams } from '../../src/providers/on-chain/OnChainProvider'; diff --git a/tests/helpers/testData.ts b/tests/helpers/testData.ts index 4565309..61326e5 100644 --- a/tests/helpers/testData.ts +++ b/tests/helpers/testData.ts @@ -1,9 +1,9 @@ -import { AggregatedRewardsType, AMMType } from '@angleprotocol/sdk'; +import { AggregatedRewardsType, AMM } from '@angleprotocol/sdk'; import { DistributionParametersStructOutput, ExtensiveDistributionParametersStructOutput, UniswapTokenDataStructOutput, -} from '@angleprotocol/sdk/dist/constants/types/DistributionCreator'; +} from '@angleprotocol/sdk/dist/generated/DistributionCreator'; import { BigNumber } from 'ethers'; import { HolderClaims } from '../../src/types/holders'; @@ -12,7 +12,7 @@ export const createTree = (amount: string) => { const defaultTree: AggregatedRewardsType = { rewards: { pesos: { - amm: AMMType.UniswapV3, + amm: AMM.UniswapV3, ammAlgo: 'UniswapV3', boostedAddress: '0xbac10c87B134742D15dA0F8db7Ee252Ce7318534', boostedReward: 1, diff --git a/yarn.lock b/yarn.lock index 0bb3c41..e193783 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@angleprotocol/sdk@^3.0.118": - version "3.0.118" - resolved "https://registry.yarnpkg.com/@angleprotocol/sdk/-/sdk-3.0.118.tgz#63dd9726502606fbe46c155b319d97dd5ca0f802" - integrity sha512-jb0OtjOtJ28WglPZOYClHWxKxZ96brvssw2jGvQjvoz0bQWSYq5pfj2sax7Aa8S7cTXHJbEou3hyj6u4m53yUA== +"@angleprotocol/sdk@0.7.2": + version "0.7.2" + resolved "https://npm.pkg.github.com/download/@angleprotocol/sdk/0.7.2/1129a6e86308909bafd86e26a9c53f6da654fff9#1129a6e86308909bafd86e26a9c53f6da654fff9" + integrity sha512-NLt47SKWaQsXSwNm1AyOhCFELZfjVEbKqrM3riQO1bLXtmzQ1OkhVozRFu1NLhTgzanuCA1vMc6qOa8lb1wBew== dependencies: "@typechain/ethers-v5" "^10.0.0" "@types/lodash" "^4.14.180" @@ -900,10 +900,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== -"@types/node@^16.9.6": - version "16.18.37" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.37.tgz#a1f8728e4dc30163deb41e9b7aba65d0c2d4eda1" - integrity sha512-ql+4dw4PlPFBP495k8JzUX/oMNRI2Ei4PrMHgj8oT4VhGlYUzF4EYr0qk2fW+XBVGIrq8Zzk13m4cvyXZuv4pA== +"@types/node@^20.9.2": + version "20.9.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.2.tgz#002815c8e87fe0c9369121c78b52e800fadc0ac6" + integrity sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg== + dependencies: + undici-types "~5.26.4" "@types/pbkdf2@^3.0.0": version "3.1.0" @@ -3893,6 +3895,11 @@ underscore@~1.13.2: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^5.22.0: version "5.22.1" resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b"