diff --git a/configs/deployed-mainnet.json b/configs/deployed-mainnet.json index 424a747..c0fd05b 100644 --- a/configs/deployed-mainnet.json +++ b/configs/deployed-mainnet.json @@ -18,6 +18,9 @@ "nodeOperatorsRegistryBaseDeployTx": "0x32a2b4ec8b59f2cdde007bbdee23ed1b75491f0ca6acc25d45bf7afbf1c8b4dd", "depositorDeployTx": "0xce40c18b10854166eb69e64171cbd33ec4ba2f369623f2f438bcf684cfed0523", "executionLayerRewardsVaultDeployTx": "0xd72cf25e4a5fe3677b6f9b2ae13771e02ad66f8d2419f333bb8bde3147bd4294", + "depositContract": { + "address": "0x00000000219ab540356cBB839Cbe05303d7705Fa" + }, "daoInitialSettings": { "token": { "name": "Lido DAO Token", @@ -163,9 +166,6 @@ }, "lidoApmDeployTx": "0xfa66476569ecef5790f2d0634997b952862bbca56aa088f151b8049421eeb87b", "lidoApmAddress": "0x0cb113890b04B49455DfE06554e2D784598A29C9", - "depositContract": { - "address": "0x00000000219ab540356cBB839Cbe05303d7705Fa" - }, "createAppReposTx": "0xf48cb21c6be021dd18bd8e02ce89ac7b924245b859f0a8b7c47e88a39016ed41", "newDaoTx": "0x3feabd79e8549ad68d1827c074fa7123815c80206498946293d5373a160fd866", "daoAddress": "0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc", @@ -196,9 +196,7 @@ }, "wstethContractAddress": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "wstethContractDeployTx": "0xaf2c1a501d2b290ef1e84ddcfc7beb3406f8ece2c46dee14e212e8233654ff05", - "wstethContractConstructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - ], + "wstethContractConstructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"], "depositorParams": { "maxDepositsPerBlock": 150, "minDepositBlockDistance": 25, @@ -327,11 +325,7 @@ "contract": "ValidatorsExitBusOracle", "implementation": "0xA89Ea51FddE660f67d1850e03C9c9862d33Bc42c", "address": "0x0De4Ea0184c2ad0BacA7183356Aea5B8d5Bf5c6e", - "constructorArgs": [ - 12, - 1606824023, - "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb" - ], + "constructorArgs": [12, 1606824023, "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb"], "proxyConstructorArgs": [ "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", @@ -372,29 +366,8 @@ "constructorArgs": [ "0xC1d0b3DE6792Bf6b4b37EccdcC24e45978Cfd2Eb", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c", - [ - 20000, - 500, - 1000, - 50, - 600, - 2, - 100, - 7680, - 750000 - ], - [ - [], - [], - [], - [], - [], - [], - [], - [], - [], - [] - ] + [20000, 500, 1000, 50, 600, 2, 100, 7680, 750000], + [[], [], [], [], [], [], [], [], [], []] ] }, "oracleDaemonConfig": { @@ -411,10 +384,7 @@ }, "contract": "OracleDaemonConfig", "address": "0xbf05A929c3D7885a6aeAd833a992dA6E5ac23b09", - "constructorArgs": [ - "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", - [] - ] + "constructorArgs": ["0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", []] }, "withdrawalQueueERC721": { "parameters": { @@ -424,11 +394,7 @@ "contract": "WithdrawalQueueERC721", "implementation": "0xE42C659Dc09109566720EA8b2De186c2Be7D94D9", "address": "0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1", - "constructorArgs": [ - "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", - "Lido: stETH Withdrawal NFT", - "unstETH" - ], + "constructorArgs": ["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", "Lido: stETH Withdrawal NFT", "unstETH"], "proxyConstructorArgs": [ "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", @@ -439,10 +405,7 @@ "address": "0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f", "contract": "WithdrawalVault", "implementation": "0xCC52f17756C04bBa7E377716d7062fC36D7f69Fd", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", - "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", "0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c"] }, "dummyEmptyContract": { "contract": "DummyEmptyContract", @@ -452,17 +415,13 @@ "eip712StETH": { "contract": "EIP712StETH", "address": "0x8F73e4C2A6D852bb4ab2A45E6a9CF5715b3228B7", - "constructorArgs": [ - "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" - ] + "constructorArgs": ["0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"] }, "stakingRouter": { "contract": "StakingRouter", "implementation": "0xD8784e748f59Ba711fB5643191Ec3fAdD50Fb6df", "address": "0xFdDf38947aFB03C621C71b06C9C70bce73f12999", - "constructorArgs": [ - "0x00000000219ab540356cBB839Cbe05303d7705Fa" - ], + "constructorArgs": ["0x00000000219ab540356cBB839Cbe05303d7705Fa"], "proxyConstructorArgs": [ "0x6F6541C2203196fEeDd14CD2C09550dA1CbEDa31", "0x8Ea83AD72396f1E0cD2f8E72b1461db8Eb6aF7B5", @@ -497,4 +456,4 @@ "0x" ] } -} \ No newline at end of file +} diff --git a/programs/chain.ts b/programs/chain.ts new file mode 100644 index 0000000..26835e4 --- /dev/null +++ b/programs/chain.ts @@ -0,0 +1,29 @@ +import { program } from '@command'; +import { getLatestBlock } from '@utils'; + +const accounts = program.command('chain').description('chain utils'); + +accounts + .command('latest-block') + .description('get latest block') + .action(async () => { + const block = await getLatestBlock(); + + const formatOptions: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + timeZoneName: 'short', + hour12: false, + }; + const intl = new Intl.DateTimeFormat('en-GB', formatOptions); + + console.table({ + number: block.number, + hash: block.hash, + timestamp: block.timestamp, + datetime: intl.format(block.timestamp * 1000), + }); + }); diff --git a/programs/common/consensus.ts b/programs/common/consensus.ts index 2cb0ab4..25837ad 100644 --- a/programs/common/consensus.ts +++ b/programs/common/consensus.ts @@ -1,6 +1,6 @@ -import { authorizedCall, getLatestBlock } from '@utils'; +import {authorizedCall, getBlock, getLatestBlock, parseBlock } from '@utils'; import { Command } from 'commander'; -import { Contract, EventLog, formatEther } from 'ethers'; +import { BlockTag, Contract, EventLog, formatEther } from 'ethers'; export const addConsensusCommands = (command: Command, contract: Contract) => { command @@ -133,83 +133,23 @@ export const addConsensusCommands = (command: Command, contract: Contract) => { command .command('closest-report') - .description('returns the closest report') - .action(async () => { - const chainConfig = await contract.getChainConfig(); - const frameConfig = await contract.getFrameConfig(); - - const secondsPerSlot = Number(chainConfig.secondsPerSlot); - const genesisTime = Number(chainConfig.genesisTime); - const slotsPerEpoch = Number(chainConfig.slotsPerEpoch); - - const initialEpoch = Number(frameConfig.initialEpoch); - const epochsPerFrame = Number(frameConfig.epochsPerFrame); - - const computeFrameIndex = (timestamp: number) => { - const epoch = Math.floor((timestamp - genesisTime) / secondsPerSlot / slotsPerEpoch); - return Math.floor((epoch - initialEpoch) / epochsPerFrame); - }; - - const getFrame = (frameIndex: number) => { - const frameStartEpoch = Math.floor(initialEpoch + frameIndex * epochsPerFrame); - const frameStartSlot = frameStartEpoch * slotsPerEpoch; - const nextFrameStartSlot = frameStartSlot + epochsPerFrame * slotsPerEpoch; - - return { - index: frameIndex, - refSlot: frameStartSlot - 1, - reportProcessingDeadlineSlot: nextFrameStartSlot - 1, - }; - }; - - const formatOptions: Intl.DateTimeFormatOptions = { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - timeZoneName: 'short', - hour12: false, - }; - const intl = new Intl.DateTimeFormat('en-GB', formatOptions); - - const slotToTime = (slot: number) => { - const time = (genesisTime + slot * secondsPerSlot) * 1000; - return intl.format(time); - }; - - const nowUnix = Math.floor(Date.now() / 1000); - const currentSlot = Math.floor((nowUnix - genesisTime) / secondsPerSlot); - - const currentFrame = getFrame(computeFrameIndex(nowUnix)); - const nextFrame = getFrame(currentFrame.index + 1); + .description('returns the closest report for the latest or specific block') + .option('-b, --block ', 'block (number or string)', 'latest') + .action(async (options: { block: string }) => { + const blockTag = parseBlock(options.block); + const report = await getClosestReport(contract, blockTag); - const getFrameSlots = (frame: { refSlot: number; reportProcessingDeadlineSlot: number }) => { - const refSlot = frame.refSlot; - const deadlineSlot = frame.reportProcessingDeadlineSlot; - - return [ - { value: 'ref slot', slot: refSlot, time: slotToTime(refSlot) }, - { value: 'deadline slot', slot: deadlineSlot, time: slotToTime(deadlineSlot) }, - ]; - }; - - console.log('current slot'); - console.table([ - { - value: 'current slot', - slot: currentSlot, - time: slotToTime(currentSlot), - }, - ]); + console.log(`closest-report for block ${blockTag}`); + console.log('target slot'); + console.table(report.targetSlot); console.log(); - console.log('current report frame'); - console.table(getFrameSlots(currentFrame)); + console.log('target report frame'); + console.table(report.targetReportFrame); console.log(); console.log('next report frame'); - console.table(getFrameSlots(nextFrame)); + console.table(report.nextReportFrame); }); command @@ -254,3 +194,83 @@ export const addConsensusCommands = (command: Command, contract: Contract) => { console.table(groupedByRefSlot); }); }; + +/** + * Get closest report for specific slot + */ +export const getClosestReport = async (contract: Contract, blockTag: BlockTag) => { + const isNow = blockTag === 'latest'; + + const latestBlock = await getBlock(blockTag); + const nowUnix = latestBlock.timestamp; + const chainConfig = await contract.getChainConfig({ blockTag }); + const frameConfig = await contract.getFrameConfig({ blockTag }); + + const secondsPerSlot = Number(chainConfig.secondsPerSlot); + const genesisTime = Number(chainConfig.genesisTime); + const slotsPerEpoch = Number(chainConfig.slotsPerEpoch); + + const initialEpoch = Number(frameConfig.initialEpoch); + const epochsPerFrame = Number(frameConfig.epochsPerFrame); + + const computeFrameIndex = (timestamp: number) => { + const epoch = Math.floor((timestamp - genesisTime) / secondsPerSlot / slotsPerEpoch); + return Math.floor((epoch - initialEpoch) / epochsPerFrame); + }; + + const getFrame = (frameIndex: number) => { + const frameStartEpoch = Math.floor(initialEpoch + frameIndex * epochsPerFrame); + const frameStartSlot = frameStartEpoch * slotsPerEpoch; + const nextFrameStartSlot = frameStartSlot + epochsPerFrame * slotsPerEpoch; + + return { + index: frameIndex, + refSlot: frameStartSlot - 1, + reportProcessingDeadlineSlot: nextFrameStartSlot - 1, + }; + }; + + const formatOptions: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + timeZoneName: 'short', + hour12: false, + }; + const intl = new Intl.DateTimeFormat('en-GB', formatOptions); + + const slotToTime = (slot: number) => { + const time = (genesisTime + slot * secondsPerSlot) * 1000; + return intl.format(time); + }; + + const currentSlot = Math.floor((nowUnix - genesisTime) / secondsPerSlot); + + const currentFrame = getFrame(computeFrameIndex(nowUnix)); + const nextFrame = getFrame(currentFrame.index + 1); + + const getFrameSlots = (frame: { refSlot: number; reportProcessingDeadlineSlot: number }) => { + const refSlot = frame.refSlot; + const deadlineSlot = frame.reportProcessingDeadlineSlot; + + return [ + { value: 'ref slot', slot: refSlot, time: slotToTime(refSlot) }, + { value: 'deadline slot', slot: deadlineSlot, time: slotToTime(deadlineSlot) }, + ]; + }; + + const report = { + targetSlot: [{ + value: isNow ? 'current slot' : `slot at ${blockTag}`, + slot: currentSlot, + block: latestBlock.number, + time: slotToTime(currentSlot), + }], + targetReportFrame: getFrameSlots(currentFrame), + nextReportFrame: getFrameSlots(nextFrame) + }; + + return report; +} \ No newline at end of file diff --git a/programs/dsm.ts b/programs/dsm.ts index f7f3167..98031cb 100644 --- a/programs/dsm.ts +++ b/programs/dsm.ts @@ -1,6 +1,6 @@ import { program } from '@command'; -import { dsmContract } from '@contracts'; -import { authorizedCall } from '@utils'; +import { dsmContract, stakingRouterContract } from '@contracts'; +import { authorizedCall, getLatestBlock } from '@utils'; import { addLogsCommands, addParsingCommands } from './common'; const dsm = program.command('dsm').description('interact with deposit security module contract'); @@ -146,3 +146,23 @@ dsm .action(async (moduleId) => { await authorizedCall(dsmContract, 'unpauseDeposits', [moduleId]); }); + +dsm + .command('can-deposit-block-distance') + .description('check deposits according to block distance condition') + .argument('', 'staking module id') + .action(async (moduleId) => { + const block = await getLatestBlock(); + const lastDepositBlock = await stakingRouterContract.getStakingModuleLastDepositBlock(moduleId); + const minDepositBlockDistance = await dsmContract.getMinDepositBlockDistance(); + + const result = block.number - lastDepositBlock >= minDepositBlockDistance; + + console.table({ + blockNumber: block.number, + lastDepositBlock: lastDepositBlock, + minDepositBlockDistance: minDepositBlockDistance, + condition: 'blockNumber - lastDepositBlock >= minDepositBlockDistance', + result: result, + }); + }); diff --git a/programs/el-node.ts b/programs/el-node.ts new file mode 100644 index 0000000..36844b9 --- /dev/null +++ b/programs/el-node.ts @@ -0,0 +1,79 @@ +import { program } from '@command'; +import { getLatestBlock } from '../utils/block'; +import { depositContract } from '../contracts/deposit-contract'; +import { provider } from '../providers/el-provider'; +import chalk from 'chalk'; + +const elNode = program.command('el-node').description('test execution node capabilities'); + +elNode + .command('is-synced') + .description('check node is synced') + .action(async () => { + const latestBlockFromNode = await getLatestBlock(); + + const tolerance = 10 * 1000; // 10 seconds + + const now = Math.floor(Date.now() / 1000); + const diff = (now - latestBlockFromNode.timestamp); + const isSynced = (diff) < tolerance; + + console.table({ isSynced, 'timestamp(node)': latestBlockFromNode.timestamp, 'timestamp(local)': now, diff}); + }); + +elNode + .command('is-archive') + .description('check node is in archive mode') + .action(async () => { + const formatOptions: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + timeZoneName: 'short', + hour12: false, + }; + const intl = new Intl.DateTimeFormat('en-GB', formatOptions); + + let step = 5000; + + const blockDeep = 100 * 1000; + const latestBlock = await getLatestBlock(); + const latestBlockNumber = latestBlock.number; + const deepestBlockNumber = latestBlockNumber - blockDeep; + + console.log('latest block', latestBlockNumber, intl.format(latestBlock.timestamp * 1000)); + let blockToCheck = latestBlockNumber; + let lastSuccessfulBlockCheck = latestBlockNumber; + let checkCount = 0; + let output: (string | number)[] = []; + while (blockToCheck >= deepestBlockNumber) { + try { + const deep = blockToCheck - latestBlockNumber; + const block = await provider.getBlock(blockToCheck); + + output = ['checking block', deep, intl.format((block?.timestamp ?? 0) * 1000)]; + + await depositContract.get_deposit_count({blockTag: blockToCheck}); + lastSuccessfulBlockCheck = blockToCheck; + + console.log.apply(this, [...output, chalk.green('History OK')]); + } catch (e) { + if (e instanceof Error) { + console.log.apply(this, [...output, chalk.green('History ') + chalk.red('FAIL'), chalk.magenta(`Can't get historic data:`), `${e.message.substring(0, 60)}...`]); + } + if (checkCount < 10) { + step = Math.floor(step / 5); + blockToCheck = lastSuccessfulBlockCheck; + console.log(chalk.grey('trying with smaller step')); + } else { + console.log('aborting...'); + break; + } + } + + blockToCheck = blockToCheck - step; + checkCount++; + } + }); diff --git a/programs/index.ts b/programs/index.ts index 4fc2f05..d037963 100644 --- a/programs/index.ts +++ b/programs/index.ts @@ -2,8 +2,10 @@ export * from './accounting-consensus'; export * from './accounting-oracle'; export * from './accounts'; export * from './burner'; +export * from './chain'; export * from './deposit-contract'; export * from './dsm'; +export * from './el-node'; export * from './exit-bus-consensus'; export * from './exit-bus-oracle'; export * from './lido'; diff --git a/programs/lido.ts b/programs/lido.ts index b5f8ef2..d1b9d64 100644 --- a/programs/lido.ts +++ b/programs/lido.ts @@ -50,6 +50,14 @@ lido console.log('is stopped', isStopped); }); +lido + .command('can-deposit') + .description('returns is protocol can deposit') + .action(async () => { + const canDeposit = await lidoContract.canDeposit(); + console.log('can deposit', canDeposit); + }); + lido .command('is-staking-paused') .description('returns is staking paused') diff --git a/programs/staking-module/modules.ts b/programs/staking-module/modules.ts index 1f1f5c2..ab89ba4 100644 --- a/programs/staking-module/modules.ts +++ b/programs/staking-module/modules.ts @@ -1,5 +1,5 @@ import { stakingRouterContract } from '@contracts'; -import { Result } from 'ethers'; +import { BlockTag, Result } from 'ethers'; export type StakingModule = { id: number; @@ -14,8 +14,8 @@ export type StakingModule = { exitedValidatorsCount: number; }; -export const getStakingModules = async (): Promise => { - const modules: Result[] = await stakingRouterContract.getStakingModules(); +export const getStakingModules = async (blockTag?: BlockTag): Promise => { + const modules: Result[] = await stakingRouterContract.getStakingModules({ blockTag }); return modules.map((module) => { const { id, diff --git a/programs/staking-router.ts b/programs/staking-router.ts index ee5250b..3b8a0a5 100644 --- a/programs/staking-router.ts +++ b/programs/staking-router.ts @@ -1,6 +1,6 @@ import { program } from '@command'; import { stakingRouterContract } from '@contracts'; -import { authorizedCall } from '@utils'; +import { authorizedCall, parseBlock } from '@utils'; import { Result } from 'ethers'; import { addAccessControlSubCommands, addLogsCommands, addOssifiableProxyCommands, addParsingCommands } from './common'; import { getNodeOperators, getStakingModules } from './staking-module'; @@ -14,8 +14,10 @@ addLogsCommands(router, stakingRouterContract); router .command('modules') .description('returns staking modules') - .action(async () => { - const modules = await getStakingModules(); + .option('-b, --block ', 'block (number or string)', 'latest') + .action(async (options: { block: string }) => { + const blockTag = parseBlock(options.block); + const modules = await getStakingModules(blockTag); console.log('modules', modules); }); diff --git a/programs/validators.ts b/programs/validators.ts index 1acc561..555a10c 100644 --- a/programs/validators.ts +++ b/programs/validators.ts @@ -129,3 +129,14 @@ validators const result = await postToAttestationPool(attesterSlashing); console.log(result); }); + + +validators + .command('info') + .description('fetches validator info by pubkey or validator index') + .argument('', 'validator index of pubkey') + .action(async (indexOrPubkey: string) => { + const validator = await fetchValidator(indexOrPubkey); + + console.log('validator', validator); + }); \ No newline at end of file diff --git a/utils/block.ts b/utils/block.ts index 24c867c..3baa5c6 100644 --- a/utils/block.ts +++ b/utils/block.ts @@ -14,3 +14,15 @@ export const getBlock = async (blockTag: BlockTag) => { return block; }; + +export const parseBlock = (block: string) => { + const isNumeric = (value: string) => { + return /^-?\d+$/.test(value); + } + + if (isNumeric(block)) { + return parseInt(block, 10); + } + + return block; +};