diff --git a/src/cli.ts b/src/cli.ts index 15dbf32..c110607 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -7,6 +7,7 @@ import {addCommand as addFork} from './commands/fork'; import {addCommand as addGovernance} from './commands/governance'; import {addCommand as addIpfsCommand} from './commands/ipfsUpload'; import {addCommand as addCapoCommand} from './commands/capo'; +import {addCommand as addLiquidityMiningCommand} from './commands/liquidityMining'; const program = new Command(); @@ -32,5 +33,6 @@ addDiffSnapshots(program); addFork(program); addIpfsCommand(program); addCapoCommand(program); +addLiquidityMiningCommand(program); program.parse(); diff --git a/src/commands/liquidityMining.ts b/src/commands/liquidityMining.ts new file mode 100644 index 0000000..5483c97 --- /dev/null +++ b/src/commands/liquidityMining.ts @@ -0,0 +1,22 @@ +import fs from 'node:fs'; +import type {Command} from '@commander-js/extra-typings'; +import {generateLiquidityMiningReport} from '../reports/lm-report'; +import {readJsonFile, readJsonString} from '../utils/json'; + +export function addCommand(program: Command) { + program + .command('lm-report') + .description('generate a liquidity mining report') + .argument('') + .option('-o, --out ', 'output path') + .option('--stringMode', 'expects input to be a string, not paths') + .action(async (_source, options) => { + const source = options.stringMode ? readJsonString(_source) : readJsonFile(_source); + const content = await generateLiquidityMiningReport(source); + if (options.out) { + fs.writeFileSync(options.out, content); + } else { + console.log(content); + } + }); +} diff --git a/src/govv3/utils/markdownUtils.ts b/src/govv3/utils/markdownUtils.ts index fdff9d5..117c42e 100644 --- a/src/govv3/utils/markdownUtils.ts +++ b/src/govv3/utils/markdownUtils.ts @@ -105,6 +105,11 @@ export async function addAssetSymbol(client: Client, value: Address) { return `${value} (symbol: ${asset.symbol})`; } +export async function addAssetSymbolWithLink(client: Client, value: Address) { + const asset = await findAsset(client, value); + return `[${asset.symbol}](${toAddressLink(value, false, client)})`; +} + const CL_PROXY_ABI = [ { inputs: [], @@ -147,3 +152,16 @@ export async function addAssetPrice(client: Client, address: Address) { decimals ? prettifyNumber({value: latestAnswer, decimals, showDecimals: true}) : latestAnswer }, description: ${description})`; } + +export function formatTimestamp(timestampInSec: number) { + // Create a new Date object from the timestamp in seconds + const date = new Date(timestampInSec * 1000); + + // Use the Intl.DateTimeFormat API to format the date + return new Intl.DateTimeFormat('en-GB', { + year: 'numeric', + month: 'short', + day: '2-digit', + timeZone: 'GMT', + }).format(date); +} \ No newline at end of file diff --git a/src/reports/__snapshots__/lm-report.spec.ts.snap b/src/reports/__snapshots__/lm-report.spec.ts.snap new file mode 100644 index 0000000..c2e89ad --- /dev/null +++ b/src/reports/__snapshots__/lm-report.spec.ts.snap @@ -0,0 +1,23 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`liquidity mining report > should generate a well formatted liquidity mining report 1`] = ` +"# Liquidity Mining Report + +
+ + + +Asset: [variableDebtPolWMATIC](https://polygonscan.com/address/0x4a1c3aD6Ed28a636ee1751C69071f6be75DEb8B8) +Reward: [MaticX](https://polygonscan.com/address/0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6) +Type: LM_UPDATE +Index: 2109876369033151 +_Transfer Strategy changed to: [0x53F57eAAD604307889D87b747Fc67ea9DE430B01](https://polygonscan.com/address/0x53F57eAAD604307889D87b747Fc67ea9DE430B01)_ +_Reward Oracle changed to: [0xB0A72A5e454890e9715e059e8df8582a6B383DE3](https://polygonscan.com/address/0xB0A72A5e454890e9715e059e8df8582a6B383DE3)_ +| | prev value | newValue | +| - | - | - | +| emissionPerSecond | 1695570156 (24 Sept 2023) | 1739992061 (19 Feb 2025) | +
+ +--- +" +`; diff --git a/src/reports/capo-report.ts b/src/reports/capo-report.ts index 2af0826..45b711f 100644 --- a/src/reports/capo-report.ts +++ b/src/reports/capo-report.ts @@ -1,4 +1,5 @@ import {formatUnits} from 'viem'; +import {formatTimestamp} from '../govv3/utils/markdownUtils'; import {CapoSnapshot} from './snapshot-types'; type Price = { @@ -71,16 +72,3 @@ export async function generateCapoReport(snapshot: CapoSnapshot) { return content; } - -function formatTimestamp(timestampInSec: number) { - // Create a new Date object from the timestamp in seconds - const date = new Date(timestampInSec * 1000); - - // Use the Intl.DateTimeFormat API to format the date - return new Intl.DateTimeFormat('en-GB', { - year: 'numeric', - month: 'short', - day: '2-digit', - timeZone: 'GMT', - }).format(date); -} diff --git a/src/reports/lm-report.spec.ts b/src/reports/lm-report.spec.ts new file mode 100644 index 0000000..e879202 --- /dev/null +++ b/src/reports/lm-report.spec.ts @@ -0,0 +1,15 @@ +import {describe, expect, it} from 'vitest'; +import {readJsonFile} from '../utils/json'; +import {generateLiquidityMiningReport} from './lm-report'; + +describe('liquidity mining report', () => { + it( + 'should generate a well formatted liquidity mining report', + async () => { + const snapshot = readJsonFile('/src/reports/mocks/liquidityMining.json'); + const content = await generateLiquidityMiningReport(snapshot); + expect(content).toMatchSnapshot(); + }, + {timeout: 30000}, + ); +}); diff --git a/src/reports/lm-report.ts b/src/reports/lm-report.ts new file mode 100644 index 0000000..0d63a73 --- /dev/null +++ b/src/reports/lm-report.ts @@ -0,0 +1,35 @@ +import {Client, type Hex} from 'viem'; +import {getClient} from '../utils/getClient'; +import {formatTimestamp, toAddressLink, addAssetSymbolWithLink} from '../govv3/utils/markdownUtils'; +import {LiquidityMiningSnapshot} from './snapshot-types'; + +export async function generateLiquidityMiningReport(snapshot: LiquidityMiningSnapshot) { + let content = ''; + content += `# Liquidity Mining Report\n\n`; + content += '
\n\n'; + + for (const key in snapshot) { + content += '\n\n'; + const object = snapshot[key]; + + content += `Asset: ${await addAssetSymbolWithLink(getClient(object.chainId) as Client, object.asset as Hex)}\n`; + content += `Reward: ${await addAssetSymbolWithLink(getClient(object.chainId) as Client, object.reward as Hex)}\n`; + content += `Type: ${object.type}\n`; + if (object.index) content += `Index: ${object.index}\n`; + + if (object.newTransferStrategy) content += `_Transfer Strategy changed to: ${toAddressLink(object.newTransferStrategy as Hex, true, getClient(object.chainId))}_\n`; + if (object.newRewardOracle) content += `_Reward Oracle changed to: ${toAddressLink(object.newRewardOracle as Hex, true, getClient(object.chainId))}_\n`; + + if (object.newDistributionEnd || object.newEmission) { + content += `| | prev value | newValue |\n`; + content += `| - | - | - |\n`; + if (object.newEmission && object.oldEmission) content += `| emissionPerSecond | ${object.oldEmission} | ${object.newEmission} |\n`; + if (object.newDistributionEnd && object.oldDistributionEnd) content += `| emissionPerSecond | ${object.oldDistributionEnd} (${formatTimestamp(object.oldDistributionEnd)}) | ${object.newDistributionEnd} (${formatTimestamp(object.newDistributionEnd)}) |\n`; + } + + content += '
\n\n'; + content += '---\n'; + } + + return content; +} diff --git a/src/reports/mocks/liquidityMining.json b/src/reports/mocks/liquidityMining.json new file mode 100644 index 0000000..7f5d28f --- /dev/null +++ b/src/reports/mocks/liquidityMining.json @@ -0,0 +1,13 @@ +{ + "0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6_0x4a1c3aD6Ed28a636ee1751C69071f6be75DEb8B8": { + "asset": "0x4a1c3aD6Ed28a636ee1751C69071f6be75DEb8B8", + "chainId": 137, + "index": 2109876369033151, + "newDistributionEnd": 1739992061, + "newRewardOracle": "0xB0A72A5e454890e9715e059e8df8582a6B383DE3", + "newTransferStrategy": "0x53F57eAAD604307889D87b747Fc67ea9DE430B01", + "oldDistributionEnd": 1695570156, + "reward": "0xfa68FB4628DFF1028CFEc22b4162FCcd0d45efb6", + "type": "LM_UPDATE" + } +} \ No newline at end of file diff --git a/src/reports/snapshot-types.ts b/src/reports/snapshot-types.ts index e638c58..f187e52 100644 --- a/src/reports/snapshot-types.ts +++ b/src/reports/snapshot-types.ts @@ -144,3 +144,22 @@ export const capoSnapshotSchema = z.object({ }); export type CapoSnapshot = z.infer; + +export const liquidityMiningSnapshotSchema = z.record( + z.string(), + z.object({ + asset: z.string(), + reward: z.string(), + newDistributionEnd: z.number().optional(), + oldDistributionEnd: z.number().optional(), + newEmission: z.number().optional(), + oldEmission: z.number().optional(), + newRewardOracle: z.string().optional(), + newTransferStrategy: z.string().optional(), + index: z.string().optional(), + type: z.string().optional(), + chainId: z.number(), + }) +); + +export type LiquidityMiningSnapshot = z.infer; \ No newline at end of file