Skip to content

Commit 0f71ca0

Browse files
authored
Merge pull request #860 from lidofinance/develop
Develop
2 parents 692d696 + 919c66e commit 0f71ca0

File tree

4 files changed

+112
-43
lines changed

4 files changed

+112
-43
lines changed

src/modules/accounting/accounting.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,11 @@ def _get_slots_elapsed_from_last_report(self, blockstamp: ReferenceBlockStamp) -
332332
slots_elapsed = blockstamp.ref_slot - last_ref_slot
333333
else:
334334
# https://github.com/lidofinance/core/blob/master/contracts/0.8.9/oracle/HashConsensus.sol#L667
335-
slots_elapsed = blockstamp.ref_slot - (frame_config.initial_epoch * chain_conf.slots_per_epoch - 1)
335+
prev_slot = max(
336+
(frame_config.initial_epoch - frame_config.epochs_per_frame) * chain_conf.slots_per_epoch - 1,
337+
0,
338+
)
339+
slots_elapsed = blockstamp.ref_slot - prev_slot
336340

337341
return slots_elapsed
338342

src/services/staking_vaults.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -803,39 +803,38 @@ def get_vaults_fees(
803803
current_frame=current_frame,
804804
)
805805

806-
# Get the last processing ref slot
807-
last_processing_ref_slot: SlotNumber = accounting_oracle.get_last_processing_ref_slot(blockstamp.block_hash)
808-
if last_processing_ref_slot:
809-
prev_ref_slot = SlotNumber(int(last_processing_ref_slot))
810-
else:
811-
# Fresh devnet: no previous Oracle report, derive a starting ref slot
812-
# This approximates the accounting contract's _get_slots_elapsed_from_last_report
813-
initial_ref_slot = frame_config.initial_epoch * chain_config.slots_per_epoch
814-
prev_ref_slot = SlotNumber(initial_ref_slot - 1) if initial_ref_slot > 0 else SlotNumber(0)
815-
816-
slots_per_frame = frame_config.epochs_per_frame * chain_config.slots_per_epoch
817-
prev_report_blockstamp = get_blockstamp(
806+
# Calculate from block param
807+
from_ref_slot: SlotNumber = accounting_oracle.get_last_processing_ref_slot(blockstamp.block_hash)
808+
809+
# If this is first report
810+
if not from_ref_slot:
811+
# Time range should include all events from FrameIndex 0 upto current ref slot
812+
# Calculate FrameIndex 0 ref slot
813+
potential_prev_ref_slot = (frame_config.initial_epoch - frame_config.epochs_per_frame) * chain_config.slots_per_epoch - 1
814+
from_ref_slot = SlotNumber(max(potential_prev_ref_slot, 0))
815+
816+
prev_report_block_number = get_blockstamp(
818817
cc=self.w3.cc,
819-
slot=prev_ref_slot,
820-
last_finalized_slot_number=SlotNumber(int(prev_ref_slot) + slots_per_frame),
821-
)
818+
slot=from_ref_slot,
819+
last_finalized_slot_number=blockstamp.slot_number,
820+
).block_number
822821

823822
# Events are fetched forward over (event_start_block, current_block] and applied backward by timestamp.
824823
events, connected_vaults_set = self._get_vault_events_for_fees(
825824
vault_hub=vault_hub,
826825
# Do not include events from last block of last frame
827-
from_block=prev_report_blockstamp.block_number + 1,
826+
from_block=prev_report_block_number + 1,
828827
to_block=blockstamp.block_number,
829828
)
830829

831830
# Missed CL slots produce no EL blocks, so elapsed time must be derived from ref slots.
832831
current_ref_slot_timestamp = self._get_report_timestamp(blockstamp.ref_slot, chain_config)
833-
prev_ref_slot_timestamp = self._get_report_timestamp(prev_ref_slot, chain_config)
832+
prev_ref_slot_timestamp = self._get_report_timestamp(from_ref_slot, chain_config)
834833
report_interval_seconds = current_ref_slot_timestamp - prev_ref_slot_timestamp
835834
if report_interval_seconds < 0:
836835
raise ValueError(
837836
"Negative report interval."
838-
f" {current_ref_slot_timestamp=} {prev_ref_slot_timestamp=} {blockstamp.ref_slot=} {prev_ref_slot=}"
837+
f" {current_ref_slot_timestamp=} {prev_ref_slot_timestamp=} {blockstamp.ref_slot=} {from_ref_slot=}"
839838
)
840839

841840
prev_fee_map, prev_liability_shares_map = self._build_prev_report_maps(prev_ipfs_report)

tests/modules/accounting/staking_vault/test_fee_get_vaults.py

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from src.services.staking_vaults import StakingVaultsService
1111
from src.types import FrameNumber, ReferenceBlockStamp, SlotNumber
1212
from src.utils.apr import get_steth_by_shares
13+
from tests.factory.blockstamp import BlockStampFactory
1314
from tests.modules.accounting.staking_vault.conftest import (
1415
ExtraValueFactory,
1516
FeeTestConstants,
@@ -41,7 +42,6 @@ def service(self, web3, monkeypatch):
4142

4243
fake_blockstamp = MagicMock()
4344
fake_blockstamp.block_number = 0
44-
4545
monkeypatch.setattr("src.services.staking_vaults.get_blockstamp", MagicMock(return_value=fake_blockstamp))
4646

4747
return svc
@@ -111,42 +111,100 @@ def test_zero_time_elapsed_allowed(self, service):
111111

112112
assert fees[vault_adr].prev_fee == 0
113113

114-
def test_initial_epoch_zero_uses_non_negative_prev_ref_slot(self, service):
115-
# Setup
114+
def test_negative_ref_slot_on_first_report(self, service, monkeypatch):
115+
# initial_epoch - frame_epoches <= 0
116116
vault_adr = VaultAddresses.VAULT_0
117117
vault = VaultInfoFactory.build(vault=vault_adr, liability_shares=0, max_liability_shares=0)
118-
blockstamp = self.make_blockstamp(block=10, slot=100)
119118

120-
service.w3.lido_contracts.accounting_oracle.get_last_processing_ref_slot.return_value = 0
119+
service.w3.lido_contracts.accounting_oracle.get_last_processing_ref_slot.return_value = None
121120
service._get_prev_vault_ipfs_report = MagicMock(return_value=None)
122121
service._get_vault_events_for_fees = MagicMock(return_value=({}, set()))
123122
service._calculate_vault_fee_components = MagicMock(return_value=(Decimal(0), Decimal(0), Decimal(0), 0))
124123

125-
with patch("src.services.staking_vaults.get_blockstamp", return_value=MagicMock(block_number=0)) as get_bs_mock:
126-
# Act
127-
service.get_vaults_fees(
128-
blockstamp=blockstamp,
129-
vaults={vault_adr: vault},
130-
vaults_total_values={},
131-
latest_onchain_ipfs_report_data=OnChainIpfsVaultReportDataFactory.build(report_cid=""),
132-
core_apr_ratio=Decimal("0"),
133-
pre_total_pooled_ether=Wei(0),
134-
pre_total_shares=0,
135-
frame_config=FrameConfig(initial_epoch=0, epochs_per_frame=1, fast_lane_length_slots=0),
136-
chain_config=ChainConfig(slots_per_epoch=32, seconds_per_slot=12, genesis_time=0),
137-
current_frame=FrameNumber(0),
138-
)
124+
# Negative prev_slot -> events should be fetched from 0 block
125+
blockstamp = self.make_blockstamp(block=3 * 32 - 1, slot=3 * 32 - 1)
126+
monkeypatch.setattr(
127+
"src.services.staking_vaults.get_blockstamp",
128+
lambda cc, slot, last_finalized_slot_number: BlockStampFactory.build(block_number=slot),
129+
)
130+
131+
service.get_vaults_fees(
132+
blockstamp=blockstamp,
133+
vaults={vault_adr: vault},
134+
vaults_total_values={},
135+
latest_onchain_ipfs_report_data=OnChainIpfsVaultReportDataFactory.build(report_cid=""),
136+
core_apr_ratio=Decimal("0"),
137+
pre_total_pooled_ether=Wei(0),
138+
pre_total_shares=0,
139+
frame_config=FrameConfig(initial_epoch=3, epochs_per_frame=5, fast_lane_length_slots=0),
140+
chain_config=ChainConfig(slots_per_epoch=32, seconds_per_slot=12, genesis_time=0),
141+
current_frame=FrameNumber(0),
142+
)
143+
144+
service._get_vault_events_for_fees.assert_called_once_with(
145+
vault_hub=service.w3.lido_contracts.vault_hub,
146+
from_block=1,
147+
to_block=3 * 32 - 1,
148+
)
139149

140-
# Assert
141-
assert get_bs_mock.call_args.kwargs["slot"] == SlotNumber(0)
142150
service._calculate_vault_fee_components.assert_called_once_with(
143151
vault_address=vault_adr,
144152
vault_info=vault,
145153
vault_total_value=0,
146154
vault_events=[],
147-
report_interval_seconds=100 * 12,
155+
report_interval_seconds=(3 * 32 - 1) * 12,
148156
prev_ref_slot_timestamp=0,
149-
current_ref_slot_timestamp=100 * 12,
157+
current_ref_slot_timestamp=(3 * 32 - 1) * 12,
158+
core_apr_ratio=Decimal(0),
159+
pre_total_pooled_ether=0,
160+
pre_total_shares=0,
161+
block_timestamps={},
162+
)
163+
164+
def test_first_ref_slot_calculate_on_first_report(self, service, monkeypatch):
165+
# initial_epoch - frame_epoches > 0
166+
vault_adr = VaultAddresses.VAULT_0
167+
vault = VaultInfoFactory.build(vault=vault_adr, liability_shares=0, max_liability_shares=0)
168+
169+
service.w3.lido_contracts.accounting_oracle.get_last_processing_ref_slot.return_value = None
170+
service._get_prev_vault_ipfs_report = MagicMock(return_value=None)
171+
service._get_vault_events_for_fees = MagicMock(return_value=({}, set()))
172+
service._calculate_vault_fee_components = MagicMock(return_value=(Decimal(0), Decimal(0), Decimal(0), 0))
173+
174+
monkeypatch.setattr(
175+
"src.services.staking_vaults.get_blockstamp",
176+
lambda cc, slot, last_finalized_slot_number: BlockStampFactory.build(block_number=slot),
177+
)
178+
179+
# First report -> events should be fetched from initial_epoch - frame_epoches
180+
blockstamp = self.make_blockstamp(block=10 * 32 - 1, slot=10 * 32 - 1)
181+
service.get_vaults_fees(
182+
blockstamp=blockstamp,
183+
vaults={vault_adr: vault},
184+
vaults_total_values={},
185+
latest_onchain_ipfs_report_data=OnChainIpfsVaultReportDataFactory.build(report_cid=""),
186+
core_apr_ratio=Decimal("0"),
187+
pre_total_pooled_ether=Wei(0),
188+
pre_total_shares=0,
189+
frame_config=FrameConfig(initial_epoch=10, epochs_per_frame=5, fast_lane_length_slots=0),
190+
chain_config=ChainConfig(slots_per_epoch=32, seconds_per_slot=12, genesis_time=0),
191+
current_frame=FrameNumber(0),
192+
)
193+
194+
service._get_vault_events_for_fees.assert_called_once_with(
195+
vault_hub=service.w3.lido_contracts.vault_hub,
196+
from_block=(10 - 5) * 32,
197+
to_block=10 * 32 - 1,
198+
)
199+
200+
service._calculate_vault_fee_components.assert_called_once_with(
201+
vault_address=vault_adr,
202+
vault_info=vault,
203+
vault_total_value=0,
204+
vault_events=[],
205+
report_interval_seconds=5 * 32 * 12,
206+
prev_ref_slot_timestamp=(5 * 32 - 1) * 12,
207+
current_ref_slot_timestamp=(10 * 32 - 1) * 12,
150208
core_apr_ratio=Decimal(0),
151209
pre_total_pooled_ether=0,
152210
pre_total_shares=0,

tests/modules/accounting/test_accounting_module.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,15 @@ def test_get_slots_elapsed_from_initialize(accounting: Accounting):
206206
bs = ReferenceBlockStampFactory.build(ref_slot=100)
207207
slots_elapsed = accounting._get_slots_elapsed_from_last_report(bs)
208208

209-
assert slots_elapsed == 100 - 32 * 2 + 1
209+
# Ref slot of Frame -1.
210+
# (initial_epoch - epochs_per_frame) * slots_per_epoch - 1
211+
prev_slot = (2 - 1) * 32 - 1 # 31
212+
assert slots_elapsed == bs.ref_slot - prev_slot
213+
214+
accounting.get_frame_config = Mock(return_value=FrameConfigFactory.build(initial_epoch=2, epochs_per_frame=4))
215+
216+
slots_elapsed = accounting._get_slots_elapsed_from_last_report(bs)
217+
assert slots_elapsed == bs.ref_slot
210218

211219

212220
@pytest.mark.unit

0 commit comments

Comments
 (0)