From 1a1fa9cee0054ab536e93aac4481b22ea8656610 Mon Sep 17 00:00:00 2001
From: woody <125113430+woodenfurniture@users.noreply.github.com>
Date: Fri, 31 May 2024 12:21:47 +1000
Subject: [PATCH] chore: initial rewards distribution cli scaffolding (#50)
* feat: added input prompts for rewards distribution
* wip: rewards distribution cli
* wip: wiring up staking rewards calculation
* wip: staking rewards progression
* feat: staking rewards accounting with on-chain validation
* feat: add progress bar to rewards validation
* chore: remove unused rewards simulation
---
package.json | 3 +
.../calculateRewards/calculateRewards.ts | 263 +++++++++++++++
scripts/rewards-distribution/client.ts | 11 +
scripts/rewards-distribution/constants.ts | 30 +-
scripts/rewards-distribution/helpers.ts | 176 ++++++++++
scripts/rewards-distribution/index.ts | 241 +++++++-------
scripts/rewards-distribution/package.json | 5 +
.../rewards-distribution/simulateStaking.ts | 105 ------
scripts/rewards-distribution/validation.ts | 66 ++++
scripts/rewards-distribution/yarn.lock | 306 +++++++++++++++++-
simulate-rewards-distribution.sh | 38 ---
yarn.lock | 5 +
12 files changed, 971 insertions(+), 278 deletions(-)
create mode 100644 scripts/rewards-distribution/calculateRewards/calculateRewards.ts
create mode 100644 scripts/rewards-distribution/client.ts
delete mode 100644 scripts/rewards-distribution/simulateStaking.ts
create mode 100644 scripts/rewards-distribution/validation.ts
delete mode 100755 simulate-rewards-distribution.sh
diff --git a/package.json b/package.json
index bbdb3db..3f41ca9 100644
--- a/package.json
+++ b/package.json
@@ -17,5 +17,8 @@
},
"simple-git-hooks": {
"pre-commit": "yarn lint:sol && yarn pretty-quick --staged"
+ },
+ "dependencies": {
+ "bignumber.js": "^9.1.2"
}
}
diff --git a/scripts/rewards-distribution/calculateRewards/calculateRewards.ts b/scripts/rewards-distribution/calculateRewards/calculateRewards.ts
new file mode 100644
index 0000000..912f540
--- /dev/null
+++ b/scripts/rewards-distribution/calculateRewards/calculateRewards.ts
@@ -0,0 +1,263 @@
+import { Address, Block } from "viem";
+import { RFoxLog, StakeLog, UnstakeLog } from "../events";
+import { getStakingAmount, isLogType } from "../helpers";
+import { REWARD_RATE, WAD } from "../constants";
+import assert from "assert";
+
+type StakingInfo = {
+ stakingBalance: bigint;
+ earnedRewards: bigint;
+ rewardPerTokenStored: bigint;
+ runeAddress: String;
+};
+
+const getEmptyStakingInfo = () => {
+ return {
+ stakingBalance: 0n,
+ earnedRewards: 0n,
+ rewardPerTokenStored: 0n,
+ runeAddress: "",
+ };
+};
+
+const rewardPerToken = (
+ rewardPerTokenStored: bigint,
+ totalStaked: bigint,
+ currentTimestamp: bigint,
+ lastUpdateTimestamp: bigint,
+) => {
+ if (totalStaked == 0n) {
+ return rewardPerTokenStored;
+ }
+ return (
+ rewardPerTokenStored +
+ ((currentTimestamp - lastUpdateTimestamp) * REWARD_RATE * WAD) / totalStaked
+ );
+};
+
+const earned = (
+ stakingInfo: StakingInfo,
+ rewardPerTokenStored: bigint,
+ totalStaked: bigint,
+ currentTimestamp: bigint,
+ lastUpdateTimestamp: bigint,
+) => {
+ return (
+ (stakingInfo.stakingBalance *
+ (rewardPerToken(
+ rewardPerTokenStored,
+ totalStaked,
+ currentTimestamp,
+ lastUpdateTimestamp,
+ ) -
+ stakingInfo.rewardPerTokenStored)) /
+ WAD +
+ stakingInfo.earnedRewards
+ );
+};
+
+const updateReward = (
+ stakingInfo: StakingInfo,
+ rewardPerTokenStored: bigint,
+ totalStaked: bigint,
+ currentTimestamp: bigint,
+ lastUpdateTimestamp: bigint,
+) => {
+ rewardPerTokenStored = rewardPerToken(
+ rewardPerTokenStored,
+ totalStaked,
+ currentTimestamp,
+ lastUpdateTimestamp,
+ );
+ lastUpdateTimestamp = currentTimestamp;
+ stakingInfo.earnedRewards = earned(
+ stakingInfo,
+ rewardPerTokenStored,
+ totalStaked,
+ currentTimestamp,
+ lastUpdateTimestamp,
+ );
+ stakingInfo.rewardPerTokenStored = rewardPerTokenStored;
+ return { stakingInfo, rewardPerTokenStored, lastUpdateTimestamp };
+};
+
+const stake = (
+ amount: bigint,
+ runeAddress: String,
+ stakingInfo: StakingInfo,
+ rewardPerTokenStored: bigint,
+ totalStaked: bigint,
+ currentTimestamp: bigint,
+ lastUpdateTimestamp: bigint,
+) => {
+ ({ stakingInfo, rewardPerTokenStored, lastUpdateTimestamp } = updateReward(
+ stakingInfo,
+ rewardPerTokenStored,
+ totalStaked,
+ currentTimestamp,
+ lastUpdateTimestamp,
+ ));
+
+ stakingInfo.stakingBalance += amount;
+ stakingInfo.runeAddress = runeAddress;
+ totalStaked += amount;
+
+ return {
+ stakingInfo,
+ rewardPerTokenStored,
+ lastUpdateTimestamp,
+ totalStaked,
+ };
+};
+
+const unstake = (
+ amount: bigint,
+ stakingInfo: StakingInfo,
+ rewardPerTokenStored: bigint,
+ totalStaked: bigint,
+ currentTimestamp: bigint,
+ lastUpdateTimestamp: bigint,
+) => {
+ ({ stakingInfo, rewardPerTokenStored, lastUpdateTimestamp } = updateReward(
+ stakingInfo,
+ rewardPerTokenStored,
+ totalStaked,
+ currentTimestamp,
+ lastUpdateTimestamp,
+ ));
+
+ stakingInfo.stakingBalance -= amount;
+ totalStaked -= amount;
+
+ return {
+ stakingInfo,
+ rewardPerTokenStored,
+ lastUpdateTimestamp,
+ totalStaked,
+ };
+};
+
+export const calculateRewards = (
+ contractCreationBlock: Block,
+ // The reward is computed as an all-time value, so we need to subtract the rewards at the end of
+ // the previous epoch. This prevents us missing rewards for the first block in the epoch.
+ previousEpochEndBlock: Block,
+ epochEndBlock: Block,
+ logs: { log: RFoxLog; timestamp: bigint }[],
+) => {
+ let totalStaked = 0n;
+ let rewardPerTokenStored = 0n;
+ let lastUpdateTimestamp = contractCreationBlock.timestamp;
+ const stakingInfoByAccount: Record
= {};
+
+ const stakingLogs = logs.filter(
+ (
+ logWithTimestamp,
+ ): logWithTimestamp is { log: StakeLog | UnstakeLog; timestamp: bigint } =>
+ isLogType("Stake", logWithTimestamp.log) ||
+ isLogType("Unstake", logWithTimestamp.log),
+ );
+
+ const epochStartRewardsByAccount: Record = {};
+ const epochEndRewardsByAccount: Record = {};
+
+ const previousEpochEndBlockNumber = previousEpochEndBlock.number;
+ const epochEndBlockNumber = epochEndBlock.number;
+
+ assert(
+ previousEpochEndBlockNumber !== null,
+ "Epoch start block number is null",
+ );
+ assert(epochEndBlockNumber !== null, "Epoch end block number is null");
+
+ let hasCalcedStartRewards = false;
+
+ // process logs
+ for (const { log, timestamp: currentTimestamp } of stakingLogs) {
+ // Paranoia in case we get logs past the end of the epoch
+ assert(log.blockNumber <= epochEndBlockNumber);
+
+ // When the block number passes the start of the epoch, assign the reward values for the start of the epoch
+ if (
+ !hasCalcedStartRewards &&
+ log.blockNumber > previousEpochEndBlockNumber
+ ) {
+ for (const [account, stakingInfo] of Object.entries(
+ stakingInfoByAccount,
+ )) {
+ epochStartRewardsByAccount[account as Address] = earned(
+ stakingInfo,
+ rewardPerTokenStored,
+ totalStaked,
+ previousEpochEndBlock.timestamp,
+ lastUpdateTimestamp,
+ );
+
+ hasCalcedStartRewards = true;
+ }
+ }
+
+ let stakingInfo =
+ stakingInfoByAccount[log.args.account] ?? getEmptyStakingInfo();
+
+ switch (true) {
+ case isLogType("Stake", log):
+ ({
+ stakingInfo,
+ rewardPerTokenStored,
+ lastUpdateTimestamp,
+ totalStaked,
+ } = stake(
+ log.args.amount,
+ log.args.runeAddress,
+ stakingInfo,
+ rewardPerTokenStored,
+ totalStaked,
+ currentTimestamp,
+ lastUpdateTimestamp,
+ ));
+ break;
+ case isLogType("Unstake", log):
+ ({
+ stakingInfo,
+ rewardPerTokenStored,
+ lastUpdateTimestamp,
+ totalStaked,
+ } = unstake(
+ log.args.amount,
+ stakingInfo,
+ rewardPerTokenStored,
+ totalStaked,
+ currentTimestamp,
+ lastUpdateTimestamp,
+ ));
+ break;
+ default:
+ break;
+ }
+
+ stakingInfoByAccount[log.args.account] = stakingInfo;
+ }
+
+ // Grab the reward values for the end of the epoch
+ for (const [account, stakingInfo] of Object.entries(stakingInfoByAccount)) {
+ epochEndRewardsByAccount[account as Address] = earned(
+ stakingInfo,
+ rewardPerTokenStored,
+ totalStaked,
+ epochEndBlock.timestamp,
+ lastUpdateTimestamp,
+ );
+ }
+
+ const earnedRewardsByAccount: Record = {};
+
+ for (const [account, epochEndReward] of Object.entries(
+ epochEndRewardsByAccount,
+ )) {
+ earnedRewardsByAccount[account as Address] =
+ epochEndReward - (epochStartRewardsByAccount[account as Address] ?? 0n);
+ }
+
+ return earnedRewardsByAccount;
+};
diff --git a/scripts/rewards-distribution/client.ts b/scripts/rewards-distribution/client.ts
new file mode 100644
index 0000000..65edca4
--- /dev/null
+++ b/scripts/rewards-distribution/client.ts
@@ -0,0 +1,11 @@
+import assert from "assert";
+import { createPublicClient, http } from "viem";
+import { arbitrum } from "viem/chains";
+
+// TODO: dotenv or similar
+assert(process.env.ARBITRUM_JSON_RPC_URL, "ARBITRUM_JSON_RPC_URL is required");
+
+export const publicClient = createPublicClient({
+ chain: arbitrum,
+ transport: http(process.env.ARBITRUM_JSON_RPC_URL),
+});
diff --git a/scripts/rewards-distribution/constants.ts b/scripts/rewards-distribution/constants.ts
index 7f1b481..a347f17 100644
--- a/scripts/rewards-distribution/constants.ts
+++ b/scripts/rewards-distribution/constants.ts
@@ -1,25 +1,13 @@
-import { createPublicClient, createWalletClient, http } from "viem";
-import { privateKeyToAccount } from "viem/accounts";
-import { localhost } from "viem/chains";
+import { Address } from "viem";
-export const localChain = {
- ...localhost,
- id: 31337,
-} as const;
+export const RUNE_DECIMALS = 8;
-export const localOwnerWalletClient = createWalletClient({
- chain: localChain,
- account: privateKeyToAccount(process.env.OWNER_PRIVATE_KEY as `0x${string}`),
- transport: http(process.env.ANVIL_JSON_RPC_URL),
-});
+export const WAD = 1n * 10n ** 18n;
+export const REWARD_RATE = 1_000_000_000n;
-export const localUserWalletClient = createWalletClient({
- chain: localChain,
- account: privateKeyToAccount(process.env.USER_PRIVATE_KEY as `0x${string}`),
- transport: http(process.env.ANVIL_JSON_RPC_URL),
-});
+// The number of blocks to query at a time, when fetching logs
+export const GET_LOGS_BLOCK_STEP_SIZE = 20000n;
-export const localPublicClient = createPublicClient({
- chain: localChain,
- transport: http(process.env.ANVIL_JSON_RPC_URL),
-});
+// RFOX on Arbitrum ERC1967Proxy contract address
+export const ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS: Address =
+ "0xd612B64A134f3D4830542B7463CE8ca8a29D7268";
diff --git a/scripts/rewards-distribution/helpers.ts b/scripts/rewards-distribution/helpers.ts
index fd07336..a022725 100644
--- a/scripts/rewards-distribution/helpers.ts
+++ b/scripts/rewards-distribution/helpers.ts
@@ -1,3 +1,179 @@
+import BigNumber from "bignumber.js";
+import {
+ ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
+ GET_LOGS_BLOCK_STEP_SIZE,
+} from "./constants";
+import { AbiEvent, Block, Log, PublicClient } from "viem";
+import cliProgress from "cli-progress";
+import colors from "ansi-colors";
+import { RFoxEvent, RFoxLog, StakeLog, UnstakeLog } from "./events";
+import { stakingV1Abi } from "./generated/abi-types";
+
+// we cache promises to prevent async race conditions hydrating the cache
+const blockNumberToTimestampCache: Record> = {};
+
+export const getBlockTimestamp = async (
+ publicClient: PublicClient,
+ blockNumber: bigint,
+): Promise => {
+ if (blockNumberToTimestampCache[blockNumber.toString()] !== undefined) {
+ return blockNumberToTimestampCache[blockNumber.toString()];
+ }
+
+ const timestampPromise = publicClient
+ .getBlock({ blockNumber })
+ .then((block) => block.timestamp);
+
+ blockNumberToTimestampCache[blockNumber.toString()] = timestampPromise;
+
+ return timestampPromise;
+};
+
export const assertUnreachable = (x: never): never => {
throw Error(`unhandled case: ${x}`);
};
+
+export const toBaseUnit = (
+ amountPrecision: number,
+ decimals: number,
+): bigint => {
+ const amountBaseUnit = BigInt(
+ new BigNumber(amountPrecision)
+ .times(BigNumber(10).pow(decimals))
+ .toFixed(0),
+ );
+
+ return amountBaseUnit;
+};
+
+export const fromBaseUnit = (
+ amountBaseUnit: bigint,
+ decimals: number,
+): number => {
+ return new BigNumber(amountBaseUnit.toString())
+ .div(BigNumber(10).pow(decimals))
+ .toNumber();
+};
+
+// Get logs from the blockchain in chunks
+export const getLogsChunked = async (
+ publicClient: PublicClient,
+ fromBlock: bigint,
+ toBlock: bigint,
+): Promise<{ log: RFoxLog; timestamp: bigint }[]> => {
+ const logs = [];
+
+ const progressBar = new cliProgress.SingleBar({
+ format:
+ "Fetch Logs Progress |" +
+ colors.cyan("{bar}") +
+ "| {percentage}% || {value}/{total} Pages",
+ barCompleteChar: "\u2588",
+ barIncompleteChar: "\u2591",
+ hideCursor: true,
+ });
+
+ progressBar.start(
+ Math.ceil(Number(toBlock - fromBlock) / Number(GET_LOGS_BLOCK_STEP_SIZE)),
+ 0,
+ {
+ speed: "N/A",
+ },
+ );
+
+ let fromBlockInner = fromBlock;
+
+ while (fromBlockInner < toBlock) {
+ let toBlockInner = fromBlockInner + GET_LOGS_BLOCK_STEP_SIZE;
+
+ // Limit toBlockInner to toBlock so we never go past the upper range
+ if (toBlockInner > toBlock) {
+ toBlockInner = toBlock;
+ }
+
+ const logsChunk = await publicClient.getContractEvents({
+ address: ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
+ abi: stakingV1Abi,
+ fromBlock: fromBlockInner,
+ toBlock: toBlockInner,
+ });
+
+ // Attach the block timestamp for each log
+ /**
+ TEMP: Free-tier rate limiting means we cant do a promise.all here
+ const logsChunkWithTimestamp: { log: RFoxLog; timestamp: bigint }[] =
+ await Promise.all(
+ logsChunk.map(async (log) => {
+ const timestamp = await getBlockTimestamp(
+ publicClient,
+ log.blockNumber,
+ );
+ return {
+ log: log as RFoxLog,
+ timestamp,
+ };
+ }),
+ );
+ */
+ const logsChunkWithTimestamp: { log: RFoxLog; timestamp: bigint }[] = [];
+ for (const log of logsChunk) {
+ const timestamp = await getBlockTimestamp(publicClient, log.blockNumber);
+ logsChunkWithTimestamp.push({
+ log: log as RFoxLog,
+ timestamp,
+ });
+ }
+
+ logs.push(...logsChunkWithTimestamp);
+
+ progressBar.increment();
+
+ fromBlockInner = toBlockInner;
+ }
+
+ progressBar.stop();
+
+ return logs;
+};
+
+export const indexBy = (values: T[], key: K) => {
+ return values.reduce(
+ (acc, value) => {
+ const innerKey = `${value[key]}`;
+ if (!acc[innerKey]) {
+ acc[innerKey] = [];
+ }
+ acc[innerKey].push(value);
+ return acc;
+ },
+ {} as Record,
+ );
+};
+
+export const isEventType = (
+ name: Event["name"],
+ event: AbiEvent,
+): event is Event => {
+ return event.name === name;
+};
+
+export const isLogType = (
+ eventName: L["eventName"],
+ log: Log,
+): log is L => {
+ return (log as L).eventName === eventName;
+};
+
+export const getStakingAmount = (log: StakeLog | UnstakeLog): bigint => {
+ const eventName = log.eventName;
+ switch (eventName) {
+ case "Stake":
+ return log.args.amount;
+ case "Unstake":
+ return -log.args.amount;
+ default:
+ assertUnreachable(eventName);
+ }
+
+ throw Error("should be unreachable");
+};
diff --git a/scripts/rewards-distribution/index.ts b/scripts/rewards-distribution/index.ts
index 625d1e4..9939810 100644
--- a/scripts/rewards-distribution/index.ts
+++ b/scripts/rewards-distribution/index.ts
@@ -1,124 +1,143 @@
-import { Address } from "viem";
-import { StakeLog, UnstakeLog, stakeEvent, unstakeEvent } from "./events";
-import { assertUnreachable } from "./helpers";
-import { simulateStaking } from "./simulateStaking";
-import { localPublicClient } from "./constants";
-import { assert } from "console";
-
-const getStakingLogs = async ({
- fromBlock,
- toBlock,
-}: {
+import { prompt, type QuestionCollection } from "inquirer";
+import {
+ ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
+ RUNE_DECIMALS,
+} from "./constants";
+import { fromBaseUnit, getLogsChunked, toBaseUnit } from "./helpers";
+import { publicClient } from "./client";
+import { calculateRewards } from "./calculateRewards/calculateRewards";
+import { stakingV1Abi } from "./generated/abi-types";
+import assert from "assert";
+import { validateRewardsDistribution } from "./validation";
+
+const inquireBlockRange = async (): Promise<{
fromBlock: bigint;
toBlock: bigint;
-}) => {
- const logs = await localPublicClient.getLogs({
- events: [stakeEvent, unstakeEvent],
- fromBlock,
- toBlock,
- });
- return logs;
+}> => {
+ const questions: QuestionCollection<{
+ fromBlock: number;
+ toBlock: number;
+ }> = [
+ {
+ type: "number",
+ name: "fromBlock",
+ message: "What is the START block number of this epoch?",
+ default: 216083216, // TODO: remove this default
+ },
+ {
+ type: "number",
+ name: "toBlock",
+ message: "What is the END block number of this epoch?",
+ default: 216092990, // TODO: remove this default
+ },
+ ];
+
+ const { fromBlock, toBlock } = await prompt(questions);
+ assert(fromBlock < toBlock, "Start block must be less than end block");
+ return { fromBlock: BigInt(fromBlock), toBlock: BigInt(toBlock) };
};
-const getStakingAmount = (log: StakeLog | UnstakeLog): bigint => {
- const eventName = log.eventName;
- switch (eventName) {
- case "Stake":
- return log.args.amount;
- case "Unstake":
- return -log.args.amount;
- default:
- assertUnreachable(eventName);
- }
-
- throw Error("should be unreachable");
+const inquireTotalRuneAmountToDistroBaseUnit = async (): Promise => {
+ const questions: QuestionCollection<{ totalRuneAmountPrecision: number }> = [
+ {
+ type: "number",
+ name: "totalRuneAmountPrecision",
+ message:
+ "What is the total amount of RUNE to distribute this epoch? Enter this amount in RUNE, not in base units (RUNE*10^8).",
+ },
+ ];
+
+ const { totalRuneAmountPrecision } = await prompt(questions);
+ console.log(totalRuneAmountPrecision);
+ const totalRuneAmountBaseUnit = toBaseUnit(
+ totalRuneAmountPrecision,
+ RUNE_DECIMALS,
+ );
+
+ return totalRuneAmountBaseUnit;
};
-// get the epoch and block reward for a given block number
-// TODO: this is a placeholder function matching the spreadsheet logic
-const getEpochBlockReward = (_epochEndBlockNumber: bigint) => {
- // TODO: blockReward is calculated as half the total rune earned by the DAO divided by the number of blocks in the epoch
- return 10n;
-};
-
-// get the block range for the current epoch
-const getEpochBlockRange = () => {
- // Monkey-patched to 0 and 5 for testing for now since the current simulation only goes up to block 5
- const previousEpochEndBlockNumber = 0n;
- const currentBlockNumber = 500n;
- return {
- fromBlock: previousEpochEndBlockNumber,
- toBlock: currentBlockNumber,
- };
+const confirmResponses = async (
+ fromBlock: bigint,
+ toBlock: bigint,
+ totalRuneAmountToDistroBaseUnit: number,
+) => {
+ const questions: QuestionCollection<{ confirm: boolean }> = [
+ {
+ type: "confirm",
+ name: "confirm",
+ message: [
+ "Do you want to proceed with these values?",
+ `* Start block: ${fromBlock}`,
+ `* End block: ${toBlock}`,
+ `* Total RUNE to distribute: ${totalRuneAmountToDistroBaseUnit} RUNE`,
+ ].join("\n"),
+ },
+ ];
+
+ const { confirm } = await prompt(questions);
+
+ if (!confirm) {
+ console.log("Exiting...");
+ process.exit(0);
+ }
};
-// TODO: this should only process 1 epoch at a time
const main = async () => {
- await simulateStaking();
-
- // iterate all blocks for the current epoch
- const { fromBlock, toBlock } = getEpochBlockRange();
-
- // Grab the first 500 or so blocks so we can simulate rewards distribution without worrying about how many blocks elapsed during contract deployment
- const logs = await getStakingLogs({ fromBlock, toBlock });
- // index logs by block number
- const logsByBlockNumber = logs.reduce>((acc, log) => {
- if (!acc[log.blockNumber.toString()]) {
- acc[log.blockNumber.toString()] = [];
- }
- acc[log.blockNumber.toString()].push(log);
- return acc;
- }, {});
-
- // TODO: these will be initialized from the last epoch's state
- let totalStaked = 0n;
- const balanceByAccountBaseUnit: Record = {};
-
- // this must be initialized to empty
- const epochRewardByAccount: Record = {};
-
- const epochBlockReward = getEpochBlockReward(toBlock);
-
- for (let blockNumber = fromBlock; blockNumber <= toBlock; blockNumber++) {
- const incomingLogs: (StakeLog | UnstakeLog)[] | undefined =
- logsByBlockNumber[blockNumber.toString()];
-
- // process logs if there are any
- if (incomingLogs !== undefined) {
- for (const log of incomingLogs) {
- const account = log.args.account;
- if (!balanceByAccountBaseUnit[account]) {
- balanceByAccountBaseUnit[account] = 0n;
- }
- const stakingAmountBaseUnit = getStakingAmount(log);
- balanceByAccountBaseUnit[account] += stakingAmountBaseUnit;
- totalStaked += stakingAmountBaseUnit;
-
- // clear empty balances
- if (balanceByAccountBaseUnit[account] === 0n) {
- delete balanceByAccountBaseUnit[account];
- }
- }
- }
-
- for (const account of Object.keys(balanceByAccountBaseUnit) as Address[]) {
- // calculate rewards for the current block
- // TODO: Bignumber math should be used here to allow for more precision with floating point numbers
- const proportionOfTotalStaked =
- totalStaked > 0n
- ? Number(balanceByAccountBaseUnit[account]) / Number(totalStaked)
- : 0;
- const reward = Number(epochBlockReward) * proportionOfTotalStaked;
-
- if (epochRewardByAccount[account] == undefined) {
- epochRewardByAccount[account] = 0;
- }
- epochRewardByAccount[account] += reward;
- }
- }
+ const { fromBlock, toBlock } = await inquireBlockRange();
+
+ const totalRuneAmountToDistroBaseUnit =
+ await inquireTotalRuneAmountToDistroBaseUnit();
+
+ await confirmResponses(
+ fromBlock,
+ toBlock,
+ fromBaseUnit(totalRuneAmountToDistroBaseUnit, RUNE_DECIMALS),
+ );
+
+ const [previousEpochEndBlock, epochEndBlock, [initLog]] = await Promise.all([
+ publicClient.getBlock({
+ blockNumber: fromBlock - 1n,
+ }),
+ publicClient.getBlock({
+ blockNumber: toBlock,
+ }),
+ publicClient.getContractEvents({
+ address: ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
+ abi: stakingV1Abi,
+ eventName: "Initialized",
+ fromBlock: "earliest",
+ toBlock: "latest",
+ }),
+ ]);
+
+ const contractCreationBlockNumber = initLog.blockNumber;
+
+ const contractCreationBlock = await publicClient.getBlock({
+ blockNumber: contractCreationBlockNumber,
+ });
+
+ const logs = await getLogsChunked(
+ publicClient,
+ contractCreationBlockNumber,
+ toBlock,
+ );
+
+ const earnedRewardsByAccount = calculateRewards(
+ contractCreationBlock,
+ previousEpochEndBlock,
+ epochEndBlock,
+ logs,
+ );
+
+ await validateRewardsDistribution(
+ publicClient,
+ earnedRewardsByAccount,
+ fromBlock,
+ toBlock,
+ );
- console.log("rewards to be distributed:");
- console.log(epochRewardByAccount);
+ // TODO: Confirm details again before proceeding
};
main();
diff --git a/scripts/rewards-distribution/package.json b/scripts/rewards-distribution/package.json
index f9dd114..6824d4a 100644
--- a/scripts/rewards-distribution/package.json
+++ b/scripts/rewards-distribution/package.json
@@ -4,12 +4,17 @@
"main": "index.ts",
"license": "MIT",
"devDependencies": {
+ "@types/cli-progress": "^3.11.5",
+ "@types/inquirer": "^8.1.3",
"@types/node": "^20.12.4",
"ts-node": "^10.9.2",
"typescript": "^5.4.4"
},
"dependencies": {
"@wagmi/cli": "^2.1.4",
+ "ansi-colors": "^4.1.3",
+ "cli-progress": "^3.12.0",
+ "inquirer": "8.2.4",
"viem": "^2.9.9"
}
}
diff --git a/scripts/rewards-distribution/simulateStaking.ts b/scripts/rewards-distribution/simulateStaking.ts
deleted file mode 100644
index 8b971c0..0000000
--- a/scripts/rewards-distribution/simulateStaking.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import { Address, formatUnits, parseUnits } from "viem";
-
-import {
- localPublicClient,
- localOwnerWalletClient,
- localUserWalletClient,
-} from "./constants";
-import { stakingV1Abi, mockFoxTokenAbi } from "./generated/abi-types";
-
-export const simulateStaking = async () => {
- const ownerWalletClient = localOwnerWalletClient;
- const userWalletClient = localUserWalletClient;
- const publicClient = localPublicClient;
- const bobRuneAddress = "thor17gw75axcnr8747pkanye45pnrwk7p9c3cqncsv";
-
- const [bob] = await localUserWalletClient.getAddresses();
-
- const mockFoxtokenContractAddress = process.env
- .STAKING_TOKEN_ADDRESS as Address;
- const mockStakingContractAddress = process.env
- .STAKING_PROXY_ADDRESS as Address;
-
- const foxDecimals = await publicClient.readContract({
- address: mockFoxtokenContractAddress,
- abi: mockFoxTokenAbi,
- functionName: "decimals",
- args: [],
- });
-
- // Make FOX rain to Bob
- const makeItRainTxHash = await ownerWalletClient.writeContract({
- address: mockFoxtokenContractAddress,
- abi: mockFoxTokenAbi,
- account: bob,
- functionName: "makeItRain",
- args: [bob, parseUnits("1000", foxDecimals)],
- });
-
- await publicClient.waitForTransactionReceipt({ hash: makeItRainTxHash });
- console.log(`1000 FOX tokens sent to Bob`);
-
- // Check Bob's FOX balance
- const bobFoxBalance = await publicClient.readContract({
- address: mockFoxtokenContractAddress,
- abi: mockFoxTokenAbi,
- functionName: "balanceOf",
- args: [bob],
- });
-
- console.log(
- `Bob's FOX balance: ${formatUnits(bobFoxBalance, foxDecimals)} FOX`,
- );
-
- const amountToStakeCryptoPrecision = "100";
- const amountToStakeCryptoBaseUnit = parseUnits(
- amountToStakeCryptoPrecision,
- foxDecimals,
- );
-
- // Approve FOX to be spent by the Staking contract
- const approveTxHash = await userWalletClient.writeContract({
- address: mockFoxtokenContractAddress,
- abi: mockFoxTokenAbi,
- account: bob,
- functionName: "approve",
- args: [mockStakingContractAddress, amountToStakeCryptoBaseUnit],
- });
- const { transactionHash } = await publicClient.waitForTransactionReceipt({
- hash: approveTxHash,
- });
-
- console.log(
- `Granted allowance for ${amountToStakeCryptoPrecision} FOX tokens to be spent by Staking contract: ${transactionHash}`,
- );
-
- // Simulate the staking of FOX tokens so if we see a revert it will be thrown with a reason
- const { request } = await publicClient.simulateContract({
- address: mockStakingContractAddress,
- abi: stakingV1Abi,
- account: bob,
- functionName: "stake",
- args: [amountToStakeCryptoBaseUnit, bobRuneAddress],
- });
-
- const stakeTxHash = await userWalletClient.writeContract(request);
-
- const transactionReceipt = await publicClient.waitForTransactionReceipt({
- hash: stakeTxHash,
- });
- const { transactionHash: stakeTransactionHash } = transactionReceipt;
- console.log(
- `Staked ${amountToStakeCryptoPrecision} FOX from Bob to Staking contract: ${stakeTransactionHash}`,
- );
-
- const bobStakedBalance = await publicClient.readContract({
- address: mockStakingContractAddress,
- abi: stakingV1Abi,
- functionName: "balanceOf",
- args: [bob],
- });
-
- console.log(
- `Bob's staked balance: ${formatUnits(bobStakedBalance, foxDecimals)} FOX`,
- );
-};
diff --git a/scripts/rewards-distribution/validation.ts b/scripts/rewards-distribution/validation.ts
new file mode 100644
index 0000000..4cd84be
--- /dev/null
+++ b/scripts/rewards-distribution/validation.ts
@@ -0,0 +1,66 @@
+import cliProgress from "cli-progress";
+import colors from "ansi-colors";
+import { Address, PublicClient } from "viem";
+import { ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS } from "./constants";
+import { stakingV1Abi } from "./generated/abi-types";
+import assert from "assert";
+
+export const validateRewardsDistribution = async (
+ publicClient: PublicClient,
+ earnedRewardsByAccount: Record,
+ fromBlock: bigint,
+ toBlock: bigint,
+) => {
+ const progressBar = new cliProgress.SingleBar({
+ format:
+ "Validation Progress |" +
+ colors.cyan("{bar}") +
+ "| {percentage}% || {value}/{total} Accounts",
+ barCompleteChar: "\u2588",
+ barIncompleteChar: "\u2591",
+ hideCursor: true,
+ });
+
+ progressBar.start(Object.keys(earnedRewardsByAccount).length, 0, {
+ speed: "N/A",
+ });
+
+ // validate rewards per account against the contract
+ for (const [account, calculatedReward] of Object.entries(
+ earnedRewardsByAccount,
+ )) {
+ const [previousTotalEarnedForAccount, currentTotalEarnedForAccount] =
+ await Promise.all([
+ publicClient.readContract({
+ // TODO: dotenv or similar for contract addresses
+ address: ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
+ abi: stakingV1Abi,
+ functionName: "earned",
+ args: [account as Address],
+ blockNumber: fromBlock - 1n, // The end of the previous epoch
+ }),
+ publicClient.readContract({
+ // TODO: dotenv or similar for contract addresses
+ address: ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS,
+ abi: stakingV1Abi,
+ functionName: "earned",
+ args: [account as Address],
+ blockNumber: toBlock,
+ }),
+ ]);
+
+ const onChainReward =
+ currentTotalEarnedForAccount - previousTotalEarnedForAccount;
+
+ assert(
+ calculatedReward === onChainReward,
+ `Expected reward for ${account} to be ${onChainReward}, got ${calculatedReward}`,
+ );
+
+ progressBar.increment();
+ }
+
+ progressBar.stop();
+
+ console.log("Validation passed.");
+};
diff --git a/scripts/rewards-distribution/yarn.lock b/scripts/rewards-distribution/yarn.lock
index c245335..3020531 100644
--- a/scripts/rewards-distribution/yarn.lock
+++ b/scripts/rewards-distribution/yarn.lock
@@ -227,6 +227,28 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
+"@types/cli-progress@^3.11.5":
+ version "3.11.5"
+ resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.11.5.tgz#9518c745e78557efda057e3f96a5990c717268c3"
+ integrity sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g==
+ dependencies:
+ "@types/node" "*"
+
+"@types/inquirer@^8.1.3":
+ version "8.2.10"
+ resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.10.tgz#9444dce2d764c35bc5bb4d742598aaa4acb6561b"
+ integrity sha512-IdD5NmHyVjWM8SHWo/kPBgtzXatwPkfwzyP3fN1jF2g9BWt5WO+8hL2F4o2GKIYsU40PpqeevuUWvkS/roXJkA==
+ dependencies:
+ "@types/through" "*"
+ rxjs "^7.2.0"
+
+"@types/node@*":
+ version "20.12.12"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050"
+ integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==
+ dependencies:
+ undici-types "~5.26.4"
+
"@types/node@^20.12.4":
version "20.12.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.4.tgz#af5921bd75ccdf3a3d8b3fa75bf3d3359268cd11"
@@ -234,6 +256,13 @@
dependencies:
undici-types "~5.26.4"
+"@types/through@*":
+ version "0.0.33"
+ resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.33.tgz#14ebf599320e1c7851e7d598149af183c6b9ea56"
+ integrity sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==
+ dependencies:
+ "@types/node" "*"
+
"@wagmi/cli@^2.1.4":
version "2.1.4"
resolved "https://registry.npmjs.org/@wagmi/cli/-/cli-2.1.4.tgz#a5427d3f11a52473eba1811b23b498305af03378"
@@ -279,11 +308,35 @@ acorn@^8.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
+ansi-colors@^4.1.3:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
+ integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
+
+ansi-escapes@^4.2.1:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
+ integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
+ dependencies:
+ type-fest "^0.21.3"
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
ansi-regex@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
@@ -307,6 +360,15 @@ binary-extensions@^2.0.0:
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
+bl@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
+ integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
+ dependencies:
+ buffer "^5.5.0"
+ inherits "^2.0.4"
+ readable-stream "^3.4.0"
+
bl@^5.0.0:
version "5.1.0"
resolved "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273"
@@ -323,6 +385,14 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
+buffer@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
+ integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
+ dependencies:
+ base64-js "^1.3.1"
+ ieee754 "^1.1.13"
+
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
@@ -360,6 +430,14 @@ capital-case@^1.0.4:
tslib "^2.0.3"
upper-case-first "^2.0.2"
+chalk@^4.1.0, chalk@^4.1.1:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chalk@^5.0.0:
version "5.3.0"
resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
@@ -383,6 +461,11 @@ change-case@^4.1.2:
snake-case "^3.0.4"
tslib "^2.0.3"
+chardet@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
+ integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
+
chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@@ -398,6 +481,13 @@ chokidar@^3.5.3:
optionalDependencies:
fsevents "~2.3.2"
+cli-cursor@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
+ integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
+ dependencies:
+ restore-cursor "^3.1.0"
+
cli-cursor@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea"
@@ -405,16 +495,40 @@ cli-cursor@^4.0.0:
dependencies:
restore-cursor "^4.0.0"
-cli-spinners@^2.6.1:
+cli-progress@^3.12.0:
+ version "3.12.0"
+ resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942"
+ integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==
+ dependencies:
+ string-width "^4.2.3"
+
+cli-spinners@^2.5.0, cli-spinners@^2.6.1:
version "2.9.2"
resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41"
integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==
+cli-width@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
+ integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
+
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
constant-case@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1"
@@ -480,6 +594,11 @@ dotenv@^16.3.1:
resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
esbuild@^0.19.0:
version "0.19.12"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04"
@@ -509,6 +628,11 @@ esbuild@^0.19.0:
"@esbuild/win32-ia32" "0.19.12"
"@esbuild/win32-x64" "0.19.12"
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
execa@^8.0.1:
version "8.0.1"
resolved "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
@@ -524,6 +648,15 @@ execa@^8.0.1:
signal-exit "^4.1.0"
strip-final-newline "^3.0.0"
+external-editor@^3.0.3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
+ integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
+ dependencies:
+ chardet "^0.7.0"
+ iconv-lite "^0.4.24"
+ tmp "^0.0.33"
+
fast-glob@^3.3.0:
version "3.3.2"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
@@ -542,6 +675,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+figures@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af"
+ integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==
+ dependencies:
+ escape-string-regexp "^1.0.5"
+
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -599,6 +739,11 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
header-case@^2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063"
@@ -612,7 +757,14 @@ human-signals@^5.0.0:
resolved "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
-ieee754@^1.2.1:
+iconv-lite@^0.4.24:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -627,6 +779,27 @@ inherits@^2.0.3, inherits@^2.0.4:
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+inquirer@8.2.4:
+ version "8.2.4"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4"
+ integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==
+ dependencies:
+ ansi-escapes "^4.2.1"
+ chalk "^4.1.1"
+ cli-cursor "^3.1.0"
+ cli-width "^3.0.0"
+ external-editor "^3.0.3"
+ figures "^3.0.0"
+ lodash "^4.17.21"
+ mute-stream "0.0.8"
+ ora "^5.4.1"
+ run-async "^2.4.0"
+ rxjs "^7.5.5"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+ through "^2.3.6"
+ wrap-ansi "^7.0.0"
+
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
@@ -639,6 +812,11 @@ is-extglob@^2.1.1:
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@@ -646,6 +824,11 @@ is-glob@^4.0.1, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-interactive@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
+ integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
+
is-interactive@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90"
@@ -661,6 +844,11 @@ is-stream@^3.0.0:
resolved "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac"
integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==
+is-unicode-supported@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
+ integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
+
is-unicode-supported@^1.1.0:
version "1.3.0"
resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714"
@@ -697,6 +885,19 @@ locate-path@^7.1.0:
dependencies:
p-locate "^6.0.0"
+lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+log-symbols@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
+ integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
+ dependencies:
+ chalk "^4.1.0"
+ is-unicode-supported "^0.1.0"
+
log-symbols@^5.1.0:
version "5.1.0"
resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz#a20e3b9a5f53fac6aeb8e2bb22c07cf2c8f16d93"
@@ -745,6 +946,11 @@ mimic-fn@^4.0.0:
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc"
integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==
+mute-stream@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
+ integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
+
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
@@ -779,6 +985,21 @@ onetime@^6.0.0:
dependencies:
mimic-fn "^4.0.0"
+ora@^5.4.1:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
+ integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==
+ dependencies:
+ bl "^4.1.0"
+ chalk "^4.1.0"
+ cli-cursor "^3.1.0"
+ cli-spinners "^2.5.0"
+ is-interactive "^1.0.0"
+ is-unicode-supported "^0.1.0"
+ log-symbols "^4.1.0"
+ strip-ansi "^6.0.0"
+ wcwidth "^1.0.1"
+
ora@^6.3.1:
version "6.3.1"
resolved "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz#a4e9e5c2cf5ee73c259e8b410273e706a2ad3ed6"
@@ -794,6 +1015,11 @@ ora@^6.3.1:
strip-ansi "^7.0.1"
wcwidth "^1.0.1"
+os-tmpdir@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+ integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
+
p-limit@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz#914af6544ed32bfa54670b061cafcbd04984b644"
@@ -893,6 +1119,14 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
+restore-cursor@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
+ integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
+ dependencies:
+ onetime "^5.1.0"
+ signal-exit "^3.0.2"
+
restore-cursor@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9"
@@ -906,6 +1140,11 @@ reusify@^1.0.4:
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+run-async@^2.4.0:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455"
+ integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==
+
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
@@ -913,11 +1152,23 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
+rxjs@^7.2.0, rxjs@^7.5.5:
+ version "7.8.1"
+ resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
+ integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
+ dependencies:
+ tslib "^2.1.0"
+
safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+"safer-buffer@>= 2.1.2 < 3":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
sentence-case@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f"
@@ -969,6 +1220,15 @@ stdin-discarder@^0.1.0:
dependencies:
bl "^5.0.0"
+string-width@^4.1.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
@@ -976,6 +1236,13 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -988,6 +1255,25 @@ strip-final-newline@^3.0.0:
resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd"
integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+through@^2.3.6:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+ integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
+
+tmp@^0.0.33:
+ version "0.0.33"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+ integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+ dependencies:
+ os-tmpdir "~1.0.2"
+
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -1014,11 +1300,16 @@ ts-node@^10.9.2:
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
-tslib@^2.0.3:
+tslib@^2.0.3, tslib@^2.1.0:
version "2.6.2"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+type-fest@^0.21.3:
+ version "0.21.3"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
+ integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+
typescript@^5.4.4:
version "5.4.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.4.tgz#eb2471e7b0a5f1377523700a21669dce30c2d952"
@@ -1100,6 +1391,15 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
ws@8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
diff --git a/simulate-rewards-distribution.sh b/simulate-rewards-distribution.sh
deleted file mode 100755
index 72206ee..0000000
--- a/simulate-rewards-distribution.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-
-set -ex
-
-forge clean --root foundry
-
-# Default private keys from anvil, assuming the default mnemonic
-# "test test test test test test test test test test test junk"
-export OWNER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
-export USER_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
-export ANVIL_JSON_RPC_URL="http://127.0.0.1:8545"
-
-# Deploy the mock FOX token as the staking token
-stakingTokenDeployOutput=$(
- forge script foundry/script/DeployMockFoxToken.s.sol:DeployMockFoxToken \
- --root foundry \
- --broadcast \
- --rpc-url http://127.0.0.1:8545 \
- --private-key $OWNER_PRIVATE_KEY \
- -vvv
-)
-stakingTokenAddress=$(echo "$stakingTokenDeployOutput" | grep "Contract deployed at:" | awk '{print $4}')
-export STAKING_TOKEN_ADDRESS=$stakingTokenAddress
-
-# Deploy the staking proxy
-stakingProxyDeployOutput=$(
- forge script foundry/script/DeployStaking.s.sol:DeployStaking \
- --root foundry \
- --broadcast \
- --rpc-url http://127.0.0.1:8545 \
- --private-key $OWNER_PRIVATE_KEY \
- -vvv
-)
-stakingProxyAddress=$(echo "$stakingProxyDeployOutput" | grep "Contract deployed at:" | awk '{print $4}')
-export STAKING_PROXY_ADDRESS=$stakingProxyAddress
-
-# Run the rewards distribution simulation
-ts-node scripts/rewards-distribution/index.ts
diff --git a/yarn.lock b/yarn.lock
index f3b1bb8..89ab5bb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -198,6 +198,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+bignumber.js@^9.1.2:
+ version "9.1.2"
+ resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
+ integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"