Skip to content

Commit

Permalink
Merge pull request #262 from lidofinance/develop
Browse files Browse the repository at this point in the history
Merge develop to main
  • Loading branch information
jake4take authored Dec 17, 2024
2 parents f18d9df + 8cbefbe commit 01257a7
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 69 deletions.
2 changes: 2 additions & 0 deletions src/events/rewards/rewards.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export const LIDO_EL_REWARDS_RECEIVED_EVENT = 'event ELRewardsReceived(uint256 a
export const LIDO_WITHDRAWALS_RECEIVED_EVENT = 'event WithdrawalsReceived(uint256 amount)';
export const LIDO_TOKEN_REBASED_EVENT =
'event TokenRebased(uint256 indexed reportTimestamp, uint256 timeElapsed, uint256 preTotalShares, uint256 preTotalEther, uint256 postTotalShares, uint256 postTotalEther, uint256 sharesMintedAsFees)';

export const ONE_WEEK_HOURS = 24 * 7;
131 changes: 73 additions & 58 deletions src/events/rewards/rewards.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
LIDO_ETH_DESTRIBUTED_EVENT,
LIDO_TOKEN_REBASED_EVENT,
LIDO_WITHDRAWALS_RECEIVED_EVENT,
ONE_WEEK_HOURS,
} from './rewards.constants';
import { BigNumber } from '@ethersproject/bignumber';
import { LOGGER_PROVIDER, LoggerService } from '../../common/logger';
Expand Down Expand Up @@ -61,7 +62,7 @@ export class RewardsService {
}

protected async updateRewards(): Promise<void> {
const rewardsPerFrame = await this.getLastTotalRewardsPerFrame();
const rewardsPerFrame = await this.getMinLastTotalRewardsPerFrame();

if (!rewardsPerFrame) {
return;
Expand All @@ -72,15 +73,11 @@ export class RewardsService {
this.rewardsStorage.setElRewardsPerFrame(rewardsPerFrame.elRewards);
}

public async getLastTotalRewardsPerFrame(): Promise<{
clRewards: BigNumber;
elRewards: BigNumber;
allRewards: BigNumber;
} | null> {
const framesFromLastReport = await this.getFramesFromLastReport();
if (framesFromLastReport === null) {
public async getMinLastTotalRewardsPerFrame() {
const framesFromLastReports = await this.getFramesFromLastReports();
if (framesFromLastReports === null) {
this.logger.warn(
'last rewards was not updated because last TokenRebase events were not found during last 48 hours.',
'last reward reports were not found because last TokenRebase events were not found during last week.',
{ service: RewardsService.SERVICE_LOG_NAME },
);
return {
Expand All @@ -90,41 +87,58 @@ export class RewardsService {
};
}

const { blockNumber, frames } = framesFromLastReport;
const rewards = await Promise.all(
framesFromLastReports.map(async ({ blockNumber, frames }) => {
const { clRewards, elRewards } = await this.getRewardsByBlockNumber(blockNumber, frames);

if (frames.eq(0)) {
this.logger.warn('last rewards set to 0 because frames passed from last event is 0.', {
service: RewardsService.SERVICE_LOG_NAME,
});
return {
clRewards: BigNumber.from(0),
elRewards: BigNumber.from(0),
allRewards: BigNumber.from(0),
};
}
return {
clRewards,
elRewards,
};
}),
);

const { preCLBalance, postCLBalance } = await this.getEthDistributed(blockNumber);
const elRewards = (await this.getElRewards(blockNumber)) ?? BigNumber.from(0);
const withdrawalsReceived = (await this.getWithdrawalsReceived(blockNumber)) ?? BigNumber.from(0);
let minCL = rewards[0].clRewards;
let minEL = rewards[0].elRewards;

const clValidatorsBalanceDiff = postCLBalance.sub(preCLBalance);
const clRewards = clValidatorsBalanceDiff.add(withdrawalsReceived);
// find minimum for last week
rewards.forEach((r) => {
if (minCL.lt(r.clRewards)) {
minCL = r.clRewards;
}

if (minEL.lt(r.elRewards)) {
minEL = r.elRewards;
}
});

const allRewards = minEL.add(minCL);

const allRewards = clRewards.add(elRewards).div(frames);
this.logger.log(`rewardsPerFrame are updated to ${allRewards.toString()}`, {
service: RewardsService.SERVICE_LOG_NAME,
});

return {
clRewards: clRewards.div(frames),
elRewards: elRewards.div(frames),
clRewards: minCL,
elRewards: minEL,
allRewards,
};
}

protected async get48HoursAgoBlock() {
protected async getRewardsByBlockNumber(blockNumber: number, framesPassed: BigNumber) {
const { preCLBalance, postCLBalance } = await this.getEthDistributed(blockNumber);
const elRewards = (await this.getElRewards(blockNumber)) ?? BigNumber.from(0);
const withdrawalsReceived = (await this.getWithdrawalsReceived(blockNumber)) ?? BigNumber.from(0);

const clValidatorsBalanceDiff = postCLBalance.sub(preCLBalance);
const clRewards = clValidatorsBalanceDiff.add(withdrawalsReceived);

return { clRewards: clRewards.div(framesPassed), elRewards: elRewards.div(framesPassed) };
}

protected async getHoursAgoBlock(hours: number) {
const currentBlock = await this.provider.getBlockNumber();
return currentBlock - Math.ceil((2 * 24 * 60 * 60) / SECONDS_PER_SLOT);
return currentBlock - Math.ceil((hours * 60 * 60) / SECONDS_PER_SLOT);
}

protected async getElRewards(fromBlock: number): Promise<BigNumber> {
Expand All @@ -135,7 +149,7 @@ export class RewardsService {
fromBlock,
address: res.address,
});
const lastLog = logs[logs.length - 1];
const lastLog = logs[0];

if (!lastLog) {
return BigNumber.from(0);
Expand Down Expand Up @@ -166,7 +180,7 @@ export class RewardsService {

this.logger.log('ETHDistributed event logs', { service: RewardsService.SERVICE_LOG_NAME, logsCount: logs.length });

const lastLog = logs[logs.length - 1];
const lastLog = logs[0];

if (!lastLog) {
this.logger.warn('ETHDistributed event is not found for CL balance.', {
Expand Down Expand Up @@ -215,7 +229,7 @@ export class RewardsService {
logsCount: logs.length,
});

const lastLog = logs[logs.length - 1];
const lastLog = logs[0];
if (!lastLog) {
return BigNumber.from(0);
}
Expand All @@ -233,11 +247,8 @@ export class RewardsService {
}

// reports can be skipped, so we need timeElapsed (time from last report)
protected async getFramesFromLastReport(): Promise<{
blockNumber: number;
frames: BigNumber;
} | null> {
const last48HoursAgoBlock = await this.get48HoursAgoBlock();
protected async getFramesFromLastReports() {
const weekAgoBlock = await this.getHoursAgoBlock(ONE_WEEK_HOURS);

const res = this.contractLido.filters.TokenRebased();

Expand All @@ -246,51 +257,55 @@ export class RewardsService {
{
topics: res.topics,
toBlock: 'latest',
fromBlock: last48HoursAgoBlock,
fromBlock: weekAgoBlock,
address: res.address,
},
this.logger,
'TokenRebased',
);

this.logger.log('TokenRebase event logs for last 48 hours', {
this.logger.log('TokenRebase event logs for last week', {
service: RewardsService.SERVICE_LOG_NAME,
logsCount: logs.length,
});

if (logs.length === 0) {
this.logger.warn('TokenRebase events are not found for last 48 hours.', {
this.logger.warn('TokenRebase events are not found for last week.', {
service: RewardsService.SERVICE_LOG_NAME,
});

return null;
}

const lastLog = logs[logs.length - 1];
const parser = new Interface([LIDO_TOKEN_REBASED_EVENT]);
const parsedData = parser.parseLog(lastLog);
const rewardsBlocks = logs.map((log) => {
const parser = new Interface([LIDO_TOKEN_REBASED_EVENT]);
const parsedData = parser.parseLog(log);

this.logger.log('last TokenRebase event for last 48 hours', {
return {
blockNumber: log.blockNumber,
frames: BigNumber.from(parsedData.args.getValue('timeElapsed')).div(
SECONDS_PER_SLOT * SLOTS_PER_EPOCH * this.contractConfig.getEpochsPerFrame(),
),
};
});

this.logger.log('last TokenRebase events for last week', {
service: RewardsService.SERVICE_LOG_NAME,
args: parsedData.args,
timeElapsed: parsedData.args.getValue('timeElapsed'),
blockNumber: lastLog.blockNumber,
rewardsBlocks,
});

return {
blockNumber: lastLog.blockNumber,
frames: BigNumber.from(parsedData.args.getValue('timeElapsed')).div(
SECONDS_PER_SLOT * SLOTS_PER_EPOCH * this.contractConfig.getEpochsPerFrame(),
),
};
return rewardsBlocks;
}

// it includes WithdrawalVault balance and diff between rewards and cached rewards from previous report
async getVaultsBalance() {
async getVaultsBalance(blockNumber: number) {
const chainId = this.configService.get('CHAIN_ID');
const withdrawalVaultAddress = await this.lidoLocator.withdrawalVault();
const withdrawalVaultBalance = await this.provider.getBalance(withdrawalVaultAddress);
const rewardsVaultBalance = await this.provider.getBalance(EXECUTION_REWARDS_VAULT_CONTRACT_ADDRESSES[chainId]);
const withdrawalVaultAddress = await this.lidoLocator.withdrawalVault({ blockTag: blockNumber });
const withdrawalVaultBalance = await this.provider.getBalance(withdrawalVaultAddress, blockNumber);
const rewardsVaultBalance = await this.provider.getBalance(
EXECUTION_REWARDS_VAULT_CONTRACT_ADDRESSES[chainId],
blockNumber,
);
const elRewards = this.rewardsStorage.getElRewardsPerFrame();
const clRewards = this.rewardsStorage.getClRewardsPerFrame();

Expand Down
4 changes: 4 additions & 0 deletions src/http/validators/validators.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class ValidatorsService {
return acc;
}, {} as Record<string, string>);

if (!lastUpdatedAt) {
return null;
}

return {
lastUpdatedAt,
maxExitEpoch,
Expand Down
3 changes: 2 additions & 1 deletion src/jobs/validators/validators.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export class ValidatorsService {
this.validatorsStorageService.setActiveValidatorsCount(activeValidatorCount);
this.validatorsStorageService.setTotalValidatorsCount(data.length);
this.validatorsStorageService.setMaxExitEpoch(latestEpoch);
this.validatorsStorageService.setLastUpdate(Math.floor(Date.now() / 1000));

const frameBalances = await this.getLidoValidatorsWithdrawableBalances(data);
this.validatorsStorageService.setFrameBalances(frameBalances);
Expand All @@ -111,6 +110,8 @@ export class ValidatorsService {
})
.inc();
});

this.validatorsStorageService.setLastUpdate(Math.floor(Date.now() / 1000));
},
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/storage/validators/validators-cache.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export class ValidatorsCacheService {

this.validatorsStorage.setActiveValidatorsCount(Number(data[0]));
this.validatorsStorage.setMaxExitEpoch(data[1]);
this.validatorsStorage.setLastUpdate(Number(data[2]));
this.validatorsStorage.setFrameBalances(this.parseFrameBalances(data[3]));
this.validatorsStorage.setLastUpdate(Number(data[2]));

this.logger.log(`success initialize from cache file ${cacheFileName}`, {
service: ValidatorsCacheService.SERVICE_LOG_NAME,
Expand Down
11 changes: 11 additions & 0 deletions src/waiting-time/waiting-time.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { RewardsService } from 'events/rewards/rewards.service';
import { SECONDS_PER_SLOT, SLOTS_PER_EPOCH } from 'common/genesis-time';

import { WaitingTimeCalculationType } from './waiting-time.types';
import { SimpleFallbackJsonRpcBatchProvider } from '@lido-nestjs/execution';

jest.mock('common/config', () => ({}));

Expand All @@ -24,6 +25,7 @@ describe('WaitingTimeService', () => {
let contractConfig: ContractConfigStorageService;
let genesisTimeService: GenesisTimeService;
let validatorsStorage: ValidatorsStorageService;
let rpcBatchProvider: SimpleFallbackJsonRpcBatchProvider;

// constants
const genesisTime = 1606824023;
Expand Down Expand Up @@ -108,6 +110,12 @@ describe('WaitingTimeService', () => {
getFrameBalances: jest.fn(),
},
},
{
provide: SimpleFallbackJsonRpcBatchProvider,
useValue: {
getBlock: jest.fn(),
},
},
{
provide: GenesisTimeService,
useValue: {
Expand All @@ -130,6 +138,7 @@ describe('WaitingTimeService', () => {
contractConfig = moduleRef.get<ContractConfigStorageService>(ContractConfigStorageService);
genesisTimeService = moduleRef.get<GenesisTimeService>(GenesisTimeService);
validatorsStorage = moduleRef.get<ValidatorsStorageService>(ValidatorsStorageService);
rpcBatchProvider = moduleRef.get<SimpleFallbackJsonRpcBatchProvider>(SimpleFallbackJsonRpcBatchProvider);

// mocks
jest.spyOn(contractConfig, 'getInitialEpoch').mockReturnValue(initialEpoch);
Expand All @@ -145,6 +154,8 @@ describe('WaitingTimeService', () => {
jest.spyOn(validatorsStorage, 'getActiveValidatorsCount').mockReturnValue(10000);
jest.spyOn(validatorsStorage, 'getFrameBalances').mockReturnValue({});
jest.spyOn(service, 'getFrameIsBunker').mockReturnValue(null);
// needed for mock only block number
jest.spyOn(rpcBatchProvider, 'getBlock').mockResolvedValue({ number: 21367114 } as any);
});

afterEach(async () => {
Expand Down
19 changes: 13 additions & 6 deletions src/waiting-time/waiting-time.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
GetWaitingTimeInfoV2Args,
GetWaitingTimeInfoV2Result,
} from './waiting-time.types';
import { SimpleFallbackJsonRpcBatchProvider } from '@lido-nestjs/execution';

@Injectable()
export class WaitingTimeService {
Expand All @@ -51,6 +52,7 @@ export class WaitingTimeService {
protected readonly genesisTimeService: GenesisTimeService,
protected readonly rewardsService: RewardsService,
protected readonly queueInfo: QueueInfoStorageService,
protected readonly provider: SimpleFallbackJsonRpcBatchProvider,
) {}

// preparing all needed number for calculation withdrawal time
Expand All @@ -60,12 +62,14 @@ export class WaitingTimeService {
// nextCalculationAt not needed anymore due to runtime queries to contract
const nextCalculationAt = this.queueInfo.getNextUpdate().toISOString();
const validatorsLastUpdate = this.validators.getLastUpdate();
const block = await this.provider.getBlock('safe');
const blockNumber = block.number;

const [unfinalized, buffer, vaultsBalance] = !cached
? await Promise.all([
this.contractWithdrawal.unfinalizedStETH(),
this.contractLido.getBufferedEther(),
this.rewardsService.getVaultsBalance(),
this.contractWithdrawal.unfinalizedStETH({ blockTag: blockNumber }),
this.contractLido.getBufferedEther({ blockTag: blockNumber }),
this.rewardsService.getVaultsBalance(blockNumber),
])
: [cached.unfinalized, cached.buffer, cached.vaultsBalance];

Expand Down Expand Up @@ -296,10 +300,13 @@ export class WaitingTimeService {
}

public async calculateRequestsTime(ids: string[]) {
const block = await this.provider.getBlock('safe');
const blockNumber = block.number;

const [unfinalized, buffer, vaultsBalance] = await Promise.all([
this.contractWithdrawal.unfinalizedStETH(),
this.contractLido.getBufferedEther(),
this.rewardsService.getVaultsBalance(),
this.contractWithdrawal.unfinalizedStETH({ blockTag: blockNumber }),
this.contractLido.getBufferedEther({ blockTag: blockNumber }),
this.rewardsService.getVaultsBalance(blockNumber),
]);

return Promise.all(
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2950,9 +2950,9 @@ [email protected]:
luxon "^3.2.1"

cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
Expand Down

0 comments on commit 01257a7

Please sign in to comment.