diff --git a/scripts/rewards-distribution/calculateRewards/calculateRewards.ts b/scripts/rewards-distribution/calculateRewards/calculateRewards.ts index a5a595f..991a26a 100644 --- a/scripts/rewards-distribution/calculateRewards/calculateRewards.ts +++ b/scripts/rewards-distribution/calculateRewards/calculateRewards.ts @@ -1,6 +1,6 @@ import { Address, Block } from "viem"; import { RFoxLog, StakeLog, UnstakeLog } from "../events"; -import { getStakingAmount, isLogType } from "../helpers"; +import { isLogType } from "../helpers"; import { REWARD_RATE, WAD } from "../constants"; import assert from "assert"; @@ -143,14 +143,14 @@ export const calculateRewards = ( // the previous epoch. This prevents us missing rewards for the first block in the epoch. previousEpochEndBlock: Block, epochEndBlock: Block, - logs: { log: RFoxLog; timestamp: bigint }[], + orderedLogs: { log: RFoxLog; timestamp: bigint }[], ) => { let totalStaked = 0n; let rewardPerTokenStored = 0n; let lastUpdateTimestamp = contractCreationBlock.timestamp; const stakingInfoByAccount: Record = {}; - const stakingLogs = logs.filter( + const stakingLogs = orderedLogs.filter( ( logWithTimestamp, ): logWithTimestamp is { log: StakeLog | UnstakeLog; timestamp: bigint } => @@ -250,19 +250,13 @@ export const calculateRewards = ( ); } - const epochMetadataByAccount: Record< - Address, - { runeAddress: string; earnedRewards: bigint } - > = {}; + const epochMetadataByAccount: Record = {}; for (const [account, epochEndReward] of Object.entries( epochEndRewardsByAccount, )) { - epochMetadataByAccount[account as Address] = { - runeAddress: stakingInfoByAccount[account as Address].runeAddress, - earnedRewards: - epochEndReward - (epochStartRewardsByAccount[account as Address] ?? 0n), - }; + epochMetadataByAccount[account as Address] = + epochEndReward - (epochStartRewardsByAccount[account as Address] ?? 0n); } return epochMetadataByAccount; diff --git a/scripts/rewards-distribution/distributeAmount/distributeAmount.ts b/scripts/rewards-distribution/distributeAmount/distributeAmount.ts index 025d9ff..29ec168 100644 --- a/scripts/rewards-distribution/distributeAmount/distributeAmount.ts +++ b/scripts/rewards-distribution/distributeAmount/distributeAmount.ts @@ -8,8 +8,8 @@ export const distributeAmount = ( totalRuneAmountToDistroBaseUnit: bigint, earnedRewardsByAccount: Record, ) => { - // Set the precision to the maximum possible value to avoid rounding errors - BigNumber.config({ DECIMAL_PLACES: 1e9 }); + // Set the precision to a high value to avoid rounding errors + BigNumber.config({ DECIMAL_PLACES: 100 }); const totalEarnedRewards = Object.values(earnedRewardsByAccount).reduce( (sum, earnedRewards) => sum + earnedRewards, diff --git a/scripts/rewards-distribution/getLatestRuneAddressByAccount.ts b/scripts/rewards-distribution/getLatestRuneAddressByAccount.ts new file mode 100644 index 0000000..3ff5286 --- /dev/null +++ b/scripts/rewards-distribution/getLatestRuneAddressByAccount.ts @@ -0,0 +1,27 @@ +import { Address } from "viem"; +import { RFoxLog, StakeLog, SetRuneAddressLog } from "./events"; +import { hexToUtf8, isLogType } from "./helpers"; + +export const getLatestRuneAddressByAccount = ( + orderedLogs: { log: RFoxLog; timestamp: bigint }[], +) => { + const runeAddressByAccount: Record = {}; + + for (const { log } of orderedLogs) { + if (isLogType("Stake", log)) { + const stakeLog = log as StakeLog; + runeAddressByAccount[stakeLog.args.account] = hexToUtf8( + stakeLog.args.runeAddress, + ); + } + + if (isLogType("SetRuneAddress", log)) { + const setRuneAddressLog = log as SetRuneAddressLog; + runeAddressByAccount[setRuneAddressLog.args.account] = hexToUtf8( + setRuneAddressLog.args.newRuneAddress, + ); + } + } + + return runeAddressByAccount; +}; diff --git a/scripts/rewards-distribution/helpers.ts b/scripts/rewards-distribution/helpers.ts index a022725..98ee42d 100644 --- a/scripts/rewards-distribution/helpers.ts +++ b/scripts/rewards-distribution/helpers.ts @@ -3,11 +3,12 @@ import { ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS, GET_LOGS_BLOCK_STEP_SIZE, } from "./constants"; -import { AbiEvent, Block, Log, PublicClient } from "viem"; +import { AbiEvent, Log, PublicClient } from "viem"; import cliProgress from "cli-progress"; import colors from "ansi-colors"; -import { RFoxEvent, RFoxLog, StakeLog, UnstakeLog } from "./events"; +import { RFoxLog, StakeLog, UnstakeLog } from "./events"; import { stakingV1Abi } from "./generated/abi-types"; +import assert from "assert"; // we cache promises to prevent async race conditions hydrating the cache const blockNumberToTimestampCache: Record> = {}; diff --git a/scripts/rewards-distribution/index.ts b/scripts/rewards-distribution/index.ts index e3a8fd7..2e9f020 100644 --- a/scripts/rewards-distribution/index.ts +++ b/scripts/rewards-distribution/index.ts @@ -14,6 +14,8 @@ import { } from "./input"; import { distributeAmount } from "./distributeAmount/distributeAmount"; import { Address } from "viem"; +import { orderBy } from "lodash"; +import { getLatestRuneAddressByAccount } from "./getLatestRuneAddressByAccount"; const main = async () => { const [currentBlock, [initLog]] = await Promise.all([ @@ -65,19 +67,23 @@ const main = async () => { toBlock, ); - const epochMetadataByAccount = calculateRewards( + // sort logs by block number and log index, ascending + // this is necessary because logs can arrive from RPC out of order + const orderedLogs = orderBy( + logs, + ["blockNumber", "logIndex"], + ["asc", "asc"], + ); + + const earnedRewardsByAccount = calculateRewards( contractCreationBlock, previousEpochEndBlock, epochEndBlock, - logs, + orderedLogs, ); - const earnedRewardsByAccount: Record = {}; - for (const [account, { earnedRewards }] of Object.entries( - epochMetadataByAccount, - )) { - earnedRewardsByAccount[account as Address] = earnedRewards; - } + // Get the latest rune address for each account + const runeAddressByAccount = getLatestRuneAddressByAccount(orderedLogs); await validateRewardsDistribution( publicClient, @@ -96,8 +102,8 @@ const main = async () => { console.log("Rewards distribution calculated successfully!"); - const tableRows = Object.entries(epochMetadataByAccount).map( - ([account, { runeAddress }]) => { + const tableRows = Object.entries(runeAddressByAccount).map( + ([account, runeAddress]) => { return { account, runeAddress, diff --git a/scripts/rewards-distribution/package.json b/scripts/rewards-distribution/package.json index f463579..efaa20e 100644 --- a/scripts/rewards-distribution/package.json +++ b/scripts/rewards-distribution/package.json @@ -10,6 +10,7 @@ "@types/cli-progress": "^3.11.5", "@types/inquirer": "^8.1.3", "@types/jest": "^29.5.12", + "@types/lodash": "^4.17.4", "@types/node": "^20.12.4", "jest": "^29.7.0", "ts-jest": "^29.1.4", @@ -21,6 +22,7 @@ "ansi-colors": "^4.1.3", "cli-progress": "^3.12.0", "inquirer": "8.2.4", + "lodash": "^4.17.21", "viem": "^2.9.9" } } diff --git a/scripts/rewards-distribution/yarn.lock b/scripts/rewards-distribution/yarn.lock index 8baf6b4..dd26746 100644 --- a/scripts/rewards-distribution/yarn.lock +++ b/scripts/rewards-distribution/yarn.lock @@ -853,6 +853,11 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/lodash@^4.17.4": + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7" + integrity sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ== + "@types/node@*": version "20.12.12" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050"