Skip to content

Commit 01257a7

Browse files
authored
Merge pull request #262 from lidofinance/develop
Merge develop to main
2 parents f18d9df + 8cbefbe commit 01257a7

File tree

8 files changed

+109
-69
lines changed

8 files changed

+109
-69
lines changed

src/events/rewards/rewards.constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ export const LIDO_EL_REWARDS_RECEIVED_EVENT = 'event ELRewardsReceived(uint256 a
44
export const LIDO_WITHDRAWALS_RECEIVED_EVENT = 'event WithdrawalsReceived(uint256 amount)';
55
export const LIDO_TOKEN_REBASED_EVENT =
66
'event TokenRebased(uint256 indexed reportTimestamp, uint256 timeElapsed, uint256 preTotalShares, uint256 preTotalEther, uint256 postTotalShares, uint256 postTotalEther, uint256 sharesMintedAsFees)';
7+
8+
export const ONE_WEEK_HOURS = 24 * 7;

src/events/rewards/rewards.service.ts

Lines changed: 73 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
LIDO_ETH_DESTRIBUTED_EVENT,
1515
LIDO_TOKEN_REBASED_EVENT,
1616
LIDO_WITHDRAWALS_RECEIVED_EVENT,
17+
ONE_WEEK_HOURS,
1718
} from './rewards.constants';
1819
import { BigNumber } from '@ethersproject/bignumber';
1920
import { LOGGER_PROVIDER, LoggerService } from '../../common/logger';
@@ -61,7 +62,7 @@ export class RewardsService {
6162
}
6263

6364
protected async updateRewards(): Promise<void> {
64-
const rewardsPerFrame = await this.getLastTotalRewardsPerFrame();
65+
const rewardsPerFrame = await this.getMinLastTotalRewardsPerFrame();
6566

6667
if (!rewardsPerFrame) {
6768
return;
@@ -72,15 +73,11 @@ export class RewardsService {
7273
this.rewardsStorage.setElRewardsPerFrame(rewardsPerFrame.elRewards);
7374
}
7475

75-
public async getLastTotalRewardsPerFrame(): Promise<{
76-
clRewards: BigNumber;
77-
elRewards: BigNumber;
78-
allRewards: BigNumber;
79-
} | null> {
80-
const framesFromLastReport = await this.getFramesFromLastReport();
81-
if (framesFromLastReport === null) {
76+
public async getMinLastTotalRewardsPerFrame() {
77+
const framesFromLastReports = await this.getFramesFromLastReports();
78+
if (framesFromLastReports === null) {
8279
this.logger.warn(
83-
'last rewards was not updated because last TokenRebase events were not found during last 48 hours.',
80+
'last reward reports were not found because last TokenRebase events were not found during last week.',
8481
{ service: RewardsService.SERVICE_LOG_NAME },
8582
);
8683
return {
@@ -90,41 +87,58 @@ export class RewardsService {
9087
};
9188
}
9289

93-
const { blockNumber, frames } = framesFromLastReport;
90+
const rewards = await Promise.all(
91+
framesFromLastReports.map(async ({ blockNumber, frames }) => {
92+
const { clRewards, elRewards } = await this.getRewardsByBlockNumber(blockNumber, frames);
9493

95-
if (frames.eq(0)) {
96-
this.logger.warn('last rewards set to 0 because frames passed from last event is 0.', {
97-
service: RewardsService.SERVICE_LOG_NAME,
98-
});
99-
return {
100-
clRewards: BigNumber.from(0),
101-
elRewards: BigNumber.from(0),
102-
allRewards: BigNumber.from(0),
103-
};
104-
}
94+
return {
95+
clRewards,
96+
elRewards,
97+
};
98+
}),
99+
);
105100

106-
const { preCLBalance, postCLBalance } = await this.getEthDistributed(blockNumber);
107-
const elRewards = (await this.getElRewards(blockNumber)) ?? BigNumber.from(0);
108-
const withdrawalsReceived = (await this.getWithdrawalsReceived(blockNumber)) ?? BigNumber.from(0);
101+
let minCL = rewards[0].clRewards;
102+
let minEL = rewards[0].elRewards;
109103

110-
const clValidatorsBalanceDiff = postCLBalance.sub(preCLBalance);
111-
const clRewards = clValidatorsBalanceDiff.add(withdrawalsReceived);
104+
// find minimum for last week
105+
rewards.forEach((r) => {
106+
if (minCL.lt(r.clRewards)) {
107+
minCL = r.clRewards;
108+
}
109+
110+
if (minEL.lt(r.elRewards)) {
111+
minEL = r.elRewards;
112+
}
113+
});
114+
115+
const allRewards = minEL.add(minCL);
112116

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

118121
return {
119-
clRewards: clRewards.div(frames),
120-
elRewards: elRewards.div(frames),
122+
clRewards: minCL,
123+
elRewards: minEL,
121124
allRewards,
122125
};
123126
}
124127

125-
protected async get48HoursAgoBlock() {
128+
protected async getRewardsByBlockNumber(blockNumber: number, framesPassed: BigNumber) {
129+
const { preCLBalance, postCLBalance } = await this.getEthDistributed(blockNumber);
130+
const elRewards = (await this.getElRewards(blockNumber)) ?? BigNumber.from(0);
131+
const withdrawalsReceived = (await this.getWithdrawalsReceived(blockNumber)) ?? BigNumber.from(0);
132+
133+
const clValidatorsBalanceDiff = postCLBalance.sub(preCLBalance);
134+
const clRewards = clValidatorsBalanceDiff.add(withdrawalsReceived);
135+
136+
return { clRewards: clRewards.div(framesPassed), elRewards: elRewards.div(framesPassed) };
137+
}
138+
139+
protected async getHoursAgoBlock(hours: number) {
126140
const currentBlock = await this.provider.getBlockNumber();
127-
return currentBlock - Math.ceil((2 * 24 * 60 * 60) / SECONDS_PER_SLOT);
141+
return currentBlock - Math.ceil((hours * 60 * 60) / SECONDS_PER_SLOT);
128142
}
129143

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

140154
if (!lastLog) {
141155
return BigNumber.from(0);
@@ -166,7 +180,7 @@ export class RewardsService {
166180

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

169-
const lastLog = logs[logs.length - 1];
183+
const lastLog = logs[0];
170184

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

218-
const lastLog = logs[logs.length - 1];
232+
const lastLog = logs[0];
219233
if (!lastLog) {
220234
return BigNumber.from(0);
221235
}
@@ -233,11 +247,8 @@ export class RewardsService {
233247
}
234248

235249
// reports can be skipped, so we need timeElapsed (time from last report)
236-
protected async getFramesFromLastReport(): Promise<{
237-
blockNumber: number;
238-
frames: BigNumber;
239-
} | null> {
240-
const last48HoursAgoBlock = await this.get48HoursAgoBlock();
250+
protected async getFramesFromLastReports() {
251+
const weekAgoBlock = await this.getHoursAgoBlock(ONE_WEEK_HOURS);
241252

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

@@ -246,51 +257,55 @@ export class RewardsService {
246257
{
247258
topics: res.topics,
248259
toBlock: 'latest',
249-
fromBlock: last48HoursAgoBlock,
260+
fromBlock: weekAgoBlock,
250261
address: res.address,
251262
},
252263
this.logger,
253264
'TokenRebased',
254265
);
255266

256-
this.logger.log('TokenRebase event logs for last 48 hours', {
267+
this.logger.log('TokenRebase event logs for last week', {
257268
service: RewardsService.SERVICE_LOG_NAME,
258269
logsCount: logs.length,
259270
});
260271

261272
if (logs.length === 0) {
262-
this.logger.warn('TokenRebase events are not found for last 48 hours.', {
273+
this.logger.warn('TokenRebase events are not found for last week.', {
263274
service: RewardsService.SERVICE_LOG_NAME,
264275
});
265276

266277
return null;
267278
}
268279

269-
const lastLog = logs[logs.length - 1];
270-
const parser = new Interface([LIDO_TOKEN_REBASED_EVENT]);
271-
const parsedData = parser.parseLog(lastLog);
280+
const rewardsBlocks = logs.map((log) => {
281+
const parser = new Interface([LIDO_TOKEN_REBASED_EVENT]);
282+
const parsedData = parser.parseLog(log);
272283

273-
this.logger.log('last TokenRebase event for last 48 hours', {
284+
return {
285+
blockNumber: log.blockNumber,
286+
frames: BigNumber.from(parsedData.args.getValue('timeElapsed')).div(
287+
SECONDS_PER_SLOT * SLOTS_PER_EPOCH * this.contractConfig.getEpochsPerFrame(),
288+
),
289+
};
290+
});
291+
292+
this.logger.log('last TokenRebase events for last week', {
274293
service: RewardsService.SERVICE_LOG_NAME,
275-
args: parsedData.args,
276-
timeElapsed: parsedData.args.getValue('timeElapsed'),
277-
blockNumber: lastLog.blockNumber,
294+
rewardsBlocks,
278295
});
279296

280-
return {
281-
blockNumber: lastLog.blockNumber,
282-
frames: BigNumber.from(parsedData.args.getValue('timeElapsed')).div(
283-
SECONDS_PER_SLOT * SLOTS_PER_EPOCH * this.contractConfig.getEpochsPerFrame(),
284-
),
285-
};
297+
return rewardsBlocks;
286298
}
287299

288300
// it includes WithdrawalVault balance and diff between rewards and cached rewards from previous report
289-
async getVaultsBalance() {
301+
async getVaultsBalance(blockNumber: number) {
290302
const chainId = this.configService.get('CHAIN_ID');
291-
const withdrawalVaultAddress = await this.lidoLocator.withdrawalVault();
292-
const withdrawalVaultBalance = await this.provider.getBalance(withdrawalVaultAddress);
293-
const rewardsVaultBalance = await this.provider.getBalance(EXECUTION_REWARDS_VAULT_CONTRACT_ADDRESSES[chainId]);
303+
const withdrawalVaultAddress = await this.lidoLocator.withdrawalVault({ blockTag: blockNumber });
304+
const withdrawalVaultBalance = await this.provider.getBalance(withdrawalVaultAddress, blockNumber);
305+
const rewardsVaultBalance = await this.provider.getBalance(
306+
EXECUTION_REWARDS_VAULT_CONTRACT_ADDRESSES[chainId],
307+
blockNumber,
308+
);
294309
const elRewards = this.rewardsStorage.getElRewardsPerFrame();
295310
const clRewards = this.rewardsStorage.getClRewardsPerFrame();
296311

src/http/validators/validators.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export class ValidatorsService {
2323
return acc;
2424
}, {} as Record<string, string>);
2525

26+
if (!lastUpdatedAt) {
27+
return null;
28+
}
29+
2630
return {
2731
lastUpdatedAt,
2832
maxExitEpoch,

src/jobs/validators/validators.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ export class ValidatorsService {
8888
this.validatorsStorageService.setActiveValidatorsCount(activeValidatorCount);
8989
this.validatorsStorageService.setTotalValidatorsCount(data.length);
9090
this.validatorsStorageService.setMaxExitEpoch(latestEpoch);
91-
this.validatorsStorageService.setLastUpdate(Math.floor(Date.now() / 1000));
9291

9392
const frameBalances = await this.getLidoValidatorsWithdrawableBalances(data);
9493
this.validatorsStorageService.setFrameBalances(frameBalances);
@@ -111,6 +110,8 @@ export class ValidatorsService {
111110
})
112111
.inc();
113112
});
113+
114+
this.validatorsStorageService.setLastUpdate(Math.floor(Date.now() / 1000));
114115
},
115116
);
116117
}

src/storage/validators/validators-cache.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ export class ValidatorsCacheService {
5252

5353
this.validatorsStorage.setActiveValidatorsCount(Number(data[0]));
5454
this.validatorsStorage.setMaxExitEpoch(data[1]);
55-
this.validatorsStorage.setLastUpdate(Number(data[2]));
5655
this.validatorsStorage.setFrameBalances(this.parseFrameBalances(data[3]));
56+
this.validatorsStorage.setLastUpdate(Number(data[2]));
5757

5858
this.logger.log(`success initialize from cache file ${cacheFileName}`, {
5959
service: ValidatorsCacheService.SERVICE_LOG_NAME,

src/waiting-time/waiting-time.service.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { RewardsService } from 'events/rewards/rewards.service';
1414
import { SECONDS_PER_SLOT, SLOTS_PER_EPOCH } from 'common/genesis-time';
1515

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

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

@@ -24,6 +25,7 @@ describe('WaitingTimeService', () => {
2425
let contractConfig: ContractConfigStorageService;
2526
let genesisTimeService: GenesisTimeService;
2627
let validatorsStorage: ValidatorsStorageService;
28+
let rpcBatchProvider: SimpleFallbackJsonRpcBatchProvider;
2729

2830
// constants
2931
const genesisTime = 1606824023;
@@ -108,6 +110,12 @@ describe('WaitingTimeService', () => {
108110
getFrameBalances: jest.fn(),
109111
},
110112
},
113+
{
114+
provide: SimpleFallbackJsonRpcBatchProvider,
115+
useValue: {
116+
getBlock: jest.fn(),
117+
},
118+
},
111119
{
112120
provide: GenesisTimeService,
113121
useValue: {
@@ -130,6 +138,7 @@ describe('WaitingTimeService', () => {
130138
contractConfig = moduleRef.get<ContractConfigStorageService>(ContractConfigStorageService);
131139
genesisTimeService = moduleRef.get<GenesisTimeService>(GenesisTimeService);
132140
validatorsStorage = moduleRef.get<ValidatorsStorageService>(ValidatorsStorageService);
141+
rpcBatchProvider = moduleRef.get<SimpleFallbackJsonRpcBatchProvider>(SimpleFallbackJsonRpcBatchProvider);
133142

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

150161
afterEach(async () => {

src/waiting-time/waiting-time.service.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
GetWaitingTimeInfoV2Args,
3939
GetWaitingTimeInfoV2Result,
4040
} from './waiting-time.types';
41+
import { SimpleFallbackJsonRpcBatchProvider } from '@lido-nestjs/execution';
4142

4243
@Injectable()
4344
export class WaitingTimeService {
@@ -51,6 +52,7 @@ export class WaitingTimeService {
5152
protected readonly genesisTimeService: GenesisTimeService,
5253
protected readonly rewardsService: RewardsService,
5354
protected readonly queueInfo: QueueInfoStorageService,
55+
protected readonly provider: SimpleFallbackJsonRpcBatchProvider,
5456
) {}
5557

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

6468
const [unfinalized, buffer, vaultsBalance] = !cached
6569
? await Promise.all([
66-
this.contractWithdrawal.unfinalizedStETH(),
67-
this.contractLido.getBufferedEther(),
68-
this.rewardsService.getVaultsBalance(),
70+
this.contractWithdrawal.unfinalizedStETH({ blockTag: blockNumber }),
71+
this.contractLido.getBufferedEther({ blockTag: blockNumber }),
72+
this.rewardsService.getVaultsBalance(blockNumber),
6973
])
7074
: [cached.unfinalized, cached.buffer, cached.vaultsBalance];
7175

@@ -296,10 +300,13 @@ export class WaitingTimeService {
296300
}
297301

298302
public async calculateRequestsTime(ids: string[]) {
303+
const block = await this.provider.getBlock('safe');
304+
const blockNumber = block.number;
305+
299306
const [unfinalized, buffer, vaultsBalance] = await Promise.all([
300-
this.contractWithdrawal.unfinalizedStETH(),
301-
this.contractLido.getBufferedEther(),
302-
this.rewardsService.getVaultsBalance(),
307+
this.contractWithdrawal.unfinalizedStETH({ blockTag: blockNumber }),
308+
this.contractLido.getBufferedEther({ blockTag: blockNumber }),
309+
this.rewardsService.getVaultsBalance(blockNumber),
303310
]);
304311

305312
return Promise.all(

yarn.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2950,9 +2950,9 @@ [email protected]:
29502950
luxon "^3.2.1"
29512951

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

0 commit comments

Comments
 (0)