From 925d27fd9972ce59686fbb3c2a3125c07c0544e8 Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Fri, 31 May 2024 12:17:48 +1000 Subject: [PATCH 1/3] feat: add input validation to rewards distribution cli --- scripts/rewards-distribution/index.ts | 87 +++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/scripts/rewards-distribution/index.ts b/scripts/rewards-distribution/index.ts index 9939810..9e6000f 100644 --- a/scripts/rewards-distribution/index.ts +++ b/scripts/rewards-distribution/index.ts @@ -10,10 +10,53 @@ import { stakingV1Abi } from "./generated/abi-types"; import assert from "assert"; import { validateRewardsDistribution } from "./validation"; -const inquireBlockRange = async (): Promise<{ +const validatePositiveNumber = (value: number) => { + if (isNaN(value)) { + return "Please enter a valid number"; + } + + if (value < 0) { + return "Please enter a positive value"; + } + + return true; +}; + +const validatePositiveInteger = (value: number) => { + if (!Number.isInteger(value)) { + return "Please enter an integer"; + } + + return validatePositiveNumber(value); +}; + +const createValidateBlockNumber = ( + minimumBlockNumber: bigint, + maximumBlockNumber: bigint, +) => { + return (value: number) => { + if (value < minimumBlockNumber) { + return `Value must be greater than or equal to ${minimumBlockNumber}`; + } + if (value > maximumBlockNumber) { + return `Value must be less than or equal to ${maximumBlockNumber}`; + } + return validatePositiveInteger(value); + }; +}; + +const inquireBlockRange = async ( + minimumBlockNumber: bigint, + maximumBlockNumber: bigint, +): Promise<{ fromBlock: bigint; toBlock: bigint; }> => { + const validateBlockNumber = createValidateBlockNumber( + minimumBlockNumber, + maximumBlockNumber, + ); + const questions: QuestionCollection<{ fromBlock: number; toBlock: number; @@ -21,19 +64,26 @@ const inquireBlockRange = async (): Promise<{ { type: "number", name: "fromBlock", + validate: validateBlockNumber, message: "What is the START block number of this epoch?", default: 216083216, // TODO: remove this default }, { type: "number", name: "toBlock", + validate: (value: number, answers: { fromBlock: number }) => { + if (value <= answers.fromBlock) { + return "'to' block must be greater than 'from' block"; + } + return validateBlockNumber(value); + }, 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) }; }; @@ -42,6 +92,7 @@ const inquireTotalRuneAmountToDistroBaseUnit = async (): Promise => { { type: "number", name: "totalRuneAmountPrecision", + validate: validatePositiveNumber, message: "What is the total amount of RUNE to distribute this epoch? Enter this amount in RUNE, not in base units (RUNE*10^8).", }, @@ -84,7 +135,26 @@ const confirmResponses = async ( }; const main = async () => { - const { fromBlock, toBlock } = await inquireBlockRange(); + const [currentBlock, [initLog]] = await Promise.all([ + publicClient.getBlock({ + blockTag: "latest", + }), + publicClient.getContractEvents({ + address: ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS, + abi: stakingV1Abi, + eventName: "Initialized", + fromBlock: "earliest", + toBlock: "latest", + }), + ]); + + const contractCreationBlockNumber = initLog.blockNumber; + const currentBlockNumber = currentBlock.number; + + const { fromBlock, toBlock } = await inquireBlockRange( + contractCreationBlockNumber, + currentBlockNumber, + ); const totalRuneAmountToDistroBaseUnit = await inquireTotalRuneAmountToDistroBaseUnit(); @@ -95,24 +165,15 @@ const main = async () => { fromBaseUnit(totalRuneAmountToDistroBaseUnit, RUNE_DECIMALS), ); - const [previousEpochEndBlock, epochEndBlock, [initLog]] = await Promise.all([ + const [previousEpochEndBlock, epochEndBlock] = 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, }); From 2822976cd907d0e0d3b91baa17d51d7e144eb752 Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Fri, 31 May 2024 12:20:22 +1000 Subject: [PATCH 2/3] chore: move cli input functions into separate file --- scripts/rewards-distribution/index.ts | 131 +------------------------- scripts/rewards-distribution/input.ts | 129 +++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 126 deletions(-) create mode 100644 scripts/rewards-distribution/input.ts diff --git a/scripts/rewards-distribution/index.ts b/scripts/rewards-distribution/index.ts index 9e6000f..adba924 100644 --- a/scripts/rewards-distribution/index.ts +++ b/scripts/rewards-distribution/index.ts @@ -1,4 +1,3 @@ -import { prompt, type QuestionCollection } from "inquirer"; import { ARBITRUM_RFOX_PROXY_CONTRACT_ADDRESS, RUNE_DECIMALS, @@ -7,132 +6,12 @@ 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 validatePositiveNumber = (value: number) => { - if (isNaN(value)) { - return "Please enter a valid number"; - } - - if (value < 0) { - return "Please enter a positive value"; - } - - return true; -}; - -const validatePositiveInteger = (value: number) => { - if (!Number.isInteger(value)) { - return "Please enter an integer"; - } - - return validatePositiveNumber(value); -}; - -const createValidateBlockNumber = ( - minimumBlockNumber: bigint, - maximumBlockNumber: bigint, -) => { - return (value: number) => { - if (value < minimumBlockNumber) { - return `Value must be greater than or equal to ${minimumBlockNumber}`; - } - if (value > maximumBlockNumber) { - return `Value must be less than or equal to ${maximumBlockNumber}`; - } - return validatePositiveInteger(value); - }; -}; - -const inquireBlockRange = async ( - minimumBlockNumber: bigint, - maximumBlockNumber: bigint, -): Promise<{ - fromBlock: bigint; - toBlock: bigint; -}> => { - const validateBlockNumber = createValidateBlockNumber( - minimumBlockNumber, - maximumBlockNumber, - ); - - const questions: QuestionCollection<{ - fromBlock: number; - toBlock: number; - }> = [ - { - type: "number", - name: "fromBlock", - validate: validateBlockNumber, - message: "What is the START block number of this epoch?", - default: 216083216, // TODO: remove this default - }, - { - type: "number", - name: "toBlock", - validate: (value: number, answers: { fromBlock: number }) => { - if (value <= answers.fromBlock) { - return "'to' block must be greater than 'from' block"; - } - return validateBlockNumber(value); - }, - message: "What is the END block number of this epoch?", - default: 216092990, // TODO: remove this default - }, - ]; - - const { fromBlock, toBlock } = await prompt(questions); - - return { fromBlock: BigInt(fromBlock), toBlock: BigInt(toBlock) }; -}; - -const inquireTotalRuneAmountToDistroBaseUnit = async (): Promise => { - const questions: QuestionCollection<{ totalRuneAmountPrecision: number }> = [ - { - type: "number", - name: "totalRuneAmountPrecision", - validate: validatePositiveNumber, - 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; -}; - -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); - } -}; +import { + confirmResponses, + inquireBlockRange, + inquireTotalRuneAmountToDistroBaseUnit, +} from "./input"; const main = async () => { const [currentBlock, [initLog]] = await Promise.all([ diff --git a/scripts/rewards-distribution/input.ts b/scripts/rewards-distribution/input.ts new file mode 100644 index 0000000..38b2ead --- /dev/null +++ b/scripts/rewards-distribution/input.ts @@ -0,0 +1,129 @@ +import { prompt, type QuestionCollection } from "inquirer"; +import { RUNE_DECIMALS } from "./constants"; +import { toBaseUnit } from "./helpers"; + +const validatePositiveNumber = (value: number) => { + if (isNaN(value)) { + return "Please enter a valid number"; + } + + if (value < 0) { + return "Please enter a positive value"; + } + + return true; +}; + +const validatePositiveInteger = (value: number) => { + if (!Number.isInteger(value)) { + return "Please enter an integer"; + } + + return validatePositiveNumber(value); +}; + +const createValidateBlockNumber = ( + minimumBlockNumber: bigint, + maximumBlockNumber: bigint, +) => { + return (value: number) => { + if (value < minimumBlockNumber) { + return `Value must be greater than or equal to ${minimumBlockNumber}`; + } + if (value > maximumBlockNumber) { + return `Value must be less than or equal to ${maximumBlockNumber}`; + } + return validatePositiveInteger(value); + }; +}; + +export const inquireBlockRange = async ( + minimumBlockNumber: bigint, + maximumBlockNumber: bigint, +): Promise<{ + fromBlock: bigint; + toBlock: bigint; +}> => { + const validateBlockNumber = createValidateBlockNumber( + minimumBlockNumber, + maximumBlockNumber, + ); + + const questions: QuestionCollection<{ + fromBlock: number; + toBlock: number; + }> = [ + { + type: "number", + name: "fromBlock", + validate: validateBlockNumber, + message: "What is the START block number of this epoch?", + default: 216083216, // TODO: remove this default + }, + { + type: "number", + name: "toBlock", + validate: (value: number, answers: { fromBlock: number }) => { + if (value <= answers.fromBlock) { + return "'to' block must be greater than 'from' block"; + } + return validateBlockNumber(value); + }, + message: "What is the END block number of this epoch?", + default: 216092990, // TODO: remove this default + }, + ]; + + const { fromBlock, toBlock } = await prompt(questions); + + return { fromBlock: BigInt(fromBlock), toBlock: BigInt(toBlock) }; +}; + +export const inquireTotalRuneAmountToDistroBaseUnit = + async (): Promise => { + const questions: QuestionCollection<{ totalRuneAmountPrecision: number }> = + [ + { + type: "number", + name: "totalRuneAmountPrecision", + validate: validatePositiveNumber, + 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; + }; + +export 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); + } +}; From 00259547ae42c6b8278b3b237231d0c96659fbbf Mon Sep 17 00:00:00 2001 From: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com> Date: Fri, 31 May 2024 12:53:55 +1000 Subject: [PATCH 3/3] fix: clear prompt input on validation error --- scripts/rewards-distribution/input.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/rewards-distribution/input.ts b/scripts/rewards-distribution/input.ts index 38b2ead..85bf31f 100644 --- a/scripts/rewards-distribution/input.ts +++ b/scripts/rewards-distribution/input.ts @@ -49,6 +49,13 @@ export const inquireBlockRange = async ( maximumBlockNumber, ); + const validateToBlock = (value: number, answers: { fromBlock: number }) => { + if (value <= answers.fromBlock) { + return "'to' block must be greater than 'from' block"; + } + return validateBlockNumber(value); + }; + const questions: QuestionCollection<{ fromBlock: number; toBlock: number; @@ -57,18 +64,19 @@ export const inquireBlockRange = async ( type: "number", name: "fromBlock", validate: validateBlockNumber, + // clear the input on validation error. Return type here is `number|undefined` not boolean (eew) + filter: (input) => + validateBlockNumber(input) === true ? input : undefined, message: "What is the START block number of this epoch?", default: 216083216, // TODO: remove this default }, { type: "number", name: "toBlock", - validate: (value: number, answers: { fromBlock: number }) => { - if (value <= answers.fromBlock) { - return "'to' block must be greater than 'from' block"; - } - return validateBlockNumber(value); - }, + validate: validateToBlock, + // clear the input on validation error. Return type here is `number|undefined` not boolean (eew) + filter: (input, answers) => + validateToBlock(input, answers) === true ? input : undefined, message: "What is the END block number of this epoch?", default: 216092990, // TODO: remove this default }, @@ -87,6 +95,9 @@ export const inquireTotalRuneAmountToDistroBaseUnit = type: "number", name: "totalRuneAmountPrecision", validate: validatePositiveNumber, + // clear the input on validation error. Return type here is `number|undefined` not boolean (eew) + filter: (input) => + validatePositiveNumber(input) === true ? input : undefined, message: "What is the total amount of RUNE to distribute this epoch? Enter this amount in RUNE, not in base units (RUNE*10^8).", },