Skip to content

Commit 4602db1

Browse files
authored
Merge pull request #520 from lidofinance/fix-csm-tests
- fix incorrect queue ics
2 parents 20df523 + 692033e commit 4602db1

File tree

3 files changed

+92
-80
lines changed

3 files changed

+92
-80
lines changed

tests/regression/test_csm.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
)
1414
from utils.dsm import UnvetArgs, to_bytes, set_single_guardian
1515
from utils.staking_module import calc_module_reward_shares
16-
from utils.test.csm_helpers import csm_add_node_operator, csm_upload_keys, get_ics_members, csm_add_ics_node_operator
16+
from utils.test.csm_helpers import (
17+
csm_add_node_operator, csm_upload_keys, csm_add_ics_node_operator,
18+
csm_set_ics_tree_members,
19+
)
1720
from utils.test.deposits_helpers import fill_deposit_buffer
1821
from utils.test.easy_track_helpers import _encode_calldata
1922
from utils.test.helpers import ETH
@@ -90,7 +93,7 @@ def strikes():
9093
def depositable_node_operator(csm, accounting, permissionless_gate, stranger):
9194
increase_staking_module_share(module_id=CSM_MODULE_ID, share_multiplier=2)
9295
csm.cleanDepositQueue(2 * csm.getNonce(), {"from": stranger.address})
93-
for queue_priority in range(1, 6):
96+
for queue_priority in range(0, 6):
9497
deposit_batch = csm.depositQueueItem(queue_priority, csm.depositQueuePointers(queue_priority)["head"])
9598
if deposit_batch:
9699
node_operator_id = (deposit_batch >> 192) & ((1 << 64) - 1)
@@ -167,14 +170,17 @@ def get_sys_fee_to_eject():
167170
return int.from_bytes(val, "big")
168171

169172

170-
@pytest.mark.parametrize("address, proof", get_ics_members())
171-
def test_add_node_operator_ics(csm, vetted_gate, accounting, address, proof):
172-
no_id = csm_add_ics_node_operator(csm, vetted_gate, accounting, address, proof)
173-
no = csm.getNodeOperator(no_id)
173+
def test_add_node_operators_ics(csm, vetted_gate, accounting, accounts):
174+
members = [account.address for account in accounts[3:5]]
175+
tree = csm_set_ics_tree_members(members)
176+
for address in members:
177+
proof = list(tree.tree.get_proof(tree.tree.find(tree.tree.leaf([address]))))
178+
no_id = csm_add_ics_node_operator(csm, vetted_gate, accounting, address, proof)
179+
no = csm.getNodeOperator(no_id)
174180

175-
assert no["managerAddress"] == address
176-
assert no["rewardAddress"] == address
177-
assert accounting.getBondCurveId(no_id) == vetted_gate.curveId()
181+
assert no["managerAddress"] == address
182+
assert no["rewardAddress"] == address
183+
assert accounting.getBondCurveId(no_id) == vetted_gate.curveId()
178184

179185

180186
def test_add_node_operator_permissionless(csm, permissionless_gate, accounting, accounts):
@@ -424,18 +430,19 @@ def test_csm_remove_key(csm, parameters_registry, accounting, node_operator):
424430
assert "KeyRemovalChargeApplied" in tx.events
425431
assert "BondCharged" in tx.events
426432

433+
curve_id = accounting.getBondCurveId(node_operator)
427434
expected_charge_amount = contracts.lido.getPooledEthByShares(
428-
contracts.lido.getSharesByPooledEth(parameters_registry.getKeyRemovalCharge(accounting.DEFAULT_BOND_CURVE_ID()))
435+
contracts.lido.getSharesByPooledEth(parameters_registry.getKeyRemovalCharge(curve_id))
429436
)
430437
assert tx.events["BondCharged"]["toChargeAmount"] == expected_charge_amount
431438
no = csm.getNodeOperator(node_operator)
432439
assert no["totalAddedKeys"] == keys_before - 1
433440

434441

435-
def test_eject_bad_performer(csm, ejector, strikes, node_operator, stranger):
442+
def test_eject_bad_performer(csm, accounting, ejector, strikes, node_operator, stranger):
436443
index_to_eject = 0
437444
pubkey_to_eject = csm.getSigningKeys(node_operator, index_to_eject, 1)
438-
leaf_to_eject = (node_operator, pubkey_to_eject, [1, 1, 1, 0, 0, 0])
445+
leaf_to_eject = (node_operator, pubkey_to_eject, [1, 1, 1, 1, 1, 1])
439446
another_pubkey = csm.getSigningKeys(node_operator, index_to_eject + 1, 1)
440447
strikes_tree = StrikesTree.new(
441448
[leaf_to_eject, (node_operator, another_pubkey, [1, 1, 0, 0, 0, 0])]
@@ -450,7 +457,7 @@ def test_eject_bad_performer(csm, ejector, strikes, node_operator, stranger):
450457

451458
eject_payment_value = get_sys_fee_to_eject()
452459

453-
bad_performer = (node_operator, index_to_eject, [1, 1, 1, 0, 0, 0])
460+
bad_performer = (node_operator, index_to_eject, [1, 1, 1, 1, 1, 1])
454461
tx = strikes.processBadPerformanceProof(
455462
[bad_performer],
456463
proof,
@@ -461,7 +468,9 @@ def test_eject_bad_performer(csm, ejector, strikes, node_operator, stranger):
461468
assert "StrikesPenaltyProcessed" in tx.events
462469
assert tx.events["StrikesPenaltyProcessed"]["nodeOperatorId"] == node_operator
463470
assert tx.events["StrikesPenaltyProcessed"]["pubkey"] == pubkey_to_eject
464-
penalty = contracts.cs_parameters_registry.defaultBadPerformancePenalty()
471+
472+
curve_id = accounting.getBondCurveId(node_operator)
473+
penalty = contracts.cs_parameters_registry.getBadPerformancePenalty(curve_id)
465474
assert tx.events["StrikesPenaltyProcessed"]["strikesPenalty"] == penalty
466475

467476
assert "TriggeredExitFeeRecorded" in tx.events
@@ -482,15 +491,17 @@ def test_voluntary_eject(csm, ejector, node_operator):
482491
assert "TriggeredExitFeeRecorded" not in tx.events
483492

484493

485-
def test_report_validator_exit_delay(csm, node_operator):
494+
def test_report_validator_exit_delay(csm, accounting, parameters_registry, node_operator):
486495
pubkey = csm.getSigningKeys(node_operator, 0, 1)
487496
day_in_seconds = 60 * 60 * 24
488497

489498
tx = csm.reportValidatorExitDelay(node_operator, 0, pubkey, 7 * day_in_seconds, {"from": contracts.staking_router})
490499
assert "ValidatorExitDelayProcessed" in tx.events
491500
assert tx.events["ValidatorExitDelayProcessed"]["nodeOperatorId"] == node_operator
492501
assert tx.events["ValidatorExitDelayProcessed"]["pubkey"] == pubkey
493-
penalty = contracts.cs_parameters_registry.defaultExitDelayPenalty()
502+
503+
curve_id = accounting.getBondCurveId(node_operator)
504+
penalty = parameters_registry.getExitDelayPenalty(curve_id)
494505
assert tx.events["ValidatorExitDelayProcessed"]["delayPenalty"] == penalty
495506

496507

utils/test/csm_helpers.py

Lines changed: 22 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,29 @@
1-
from brownie import ZERO_ADDRESS
1+
from brownie import ZERO_ADDRESS, chain
22

33
from utils.balance import set_balance_in_wei
4+
from utils.test.easy_track_helpers import _encode_calldata
45
from utils.test.helpers import ETH
56
from utils.test.keys_helpers import random_pubkeys_batch, random_signatures_batch
6-
from utils.config import contracts
7-
8-
def get_ics_members():
9-
"""Few ICS members from mainnet list"""
10-
return ((
11-
"0xf4c1b69a65f3fdbafcae0786c5fb02dad3e0f353",
12-
[
13-
"0x5f1fbf1cda5a3dd4d863cd96f6513c24f4f5973e9de0d4524ba5c65bdbff09c6",
14-
"0x459f8832de04e639e148bfdf73d008f9496bc5473b76c76fcb2681dd2806b34f",
15-
"0x38b9c0886c7f5a0115688d538e14318a22856470d265c03778b63e775bfce55a",
16-
"0x007c11b18270c5b5ec10e821b3b2763fd7b28f889b11ec0ae297ab3c55ab0dba",
17-
"0x5cb1b7f8a3505a0d36cb64886bfd5d48e28e62d2ff0fd52aa12d3f5933fe1916",
18-
"0x429e5ae3d28582cb7efcebca05bae5430e9915f8f5bef58c79563938479fa791",
19-
"0x31003203efa870c41458f35927908e17c10f24b615b0e6af863d01c0d8ea3065",
20-
"0xbe5b3c65dec940f408d96c86777263868e45fa74b9848871c1f508bde4d731e1"
21-
]
22-
), ("0x9b4064c9d9801f062f377512e61bd19484e7f365",
23-
[
24-
"0x3dca0828b389d4a4f72eb381ac3edd6f96f51a04b0446db2e7a77892b61ba7c7",
25-
"0x8f5af01242e34310a430415a1bda6ad42ea09b7ed487de5b38c2626d7acb08b1",
26-
"0x2ae5c4b9c69f91edb90caf2594e595e01758c58ae9719311cd3d5d41a32f0b66",
27-
"0x1f7e8e488cf08018ea5796a648b39f2218f1aad23223dcd6c3733cf775950dff",
28-
"0xdbc36608e64ffee165ab914565b18e83b526b81771c46fc4689e7579c426a567",
29-
"0xf676e62b1ca138f69927c196ec23e6f43e4e430e2865637fa5db83cc26670372",
30-
"0x31003203efa870c41458f35927908e17c10f24b615b0e6af863d01c0d8ea3065",
31-
"0xbe5b3c65dec940f408d96c86777263868e45fa74b9848871c1f508bde4d731e1"
32-
]
33-
), ("0xb8f8e5751b2c5bad790ff0d2f5574ab38246272e",
34-
[
35-
"0x96441bf0ea1e2d069560e692ff9b5baec46e83a364ffe3203c5b16d91bc0321f",
36-
"0x5e7c84656af7b03dc12353898f2cafda93166fc9c85aec6d797e7410a38390b2",
37-
"0x90efa708c8677e4589f86f98d788a2b6096b1202358cabc31558ccc716bab269",
38-
"0x1392ec14e24fb65c06527743cafc6c408653a312511fbeb4f7a385ee62cbb201",
39-
"0xc656eee8bb516c8ef59fa3c73f68276c87aa2e707b1394cfe0d0d2151a60ecb8",
40-
"0x994eb932575dc2f3b0a55262fcdfd0108cdf5573228a2fdac5c82bc689cbb90d",
41-
"0xc878b4f250b6b2616245fe079be412dac4605b48aede5ac8f695fe219931051d",
42-
"0xf0e2720fdd888df5369e9aa081347d97603fadbd22f5a1978b08fbaa7c13e6f7"
43-
]
44-
), (
45-
"0x88792bee0d8a4c46acb7a2d8bfbef7e3a678639e",
46-
[
47-
"0x955c8105dfeed6dd47fccc580e67b00090043c2e0fe00dd9269adaf1864ac073",
48-
"0xfb254edbb3d5dc888dd77e1779146b94268fb5964d0dc9f71815fad8da58612c",
49-
"0x90efa708c8677e4589f86f98d788a2b6096b1202358cabc31558ccc716bab269",
50-
"0x1392ec14e24fb65c06527743cafc6c408653a312511fbeb4f7a385ee62cbb201",
51-
"0xc656eee8bb516c8ef59fa3c73f68276c87aa2e707b1394cfe0d0d2151a60ecb8",
52-
"0x994eb932575dc2f3b0a55262fcdfd0108cdf5573228a2fdac5c82bc689cbb90d",
53-
"0xc878b4f250b6b2616245fe079be412dac4605b48aede5ac8f695fe219931051d",
54-
"0xf0e2720fdd888df5369e9aa081347d97603fadbd22f5a1978b08fbaa7c13e6f7"
55-
]
56-
), (
57-
"0x1ea60eaa2883173e2f8d6b7b025fce33ebc08738",
58-
[
59-
"0xd5d55039d3530cc365d80d5bdb249c6a21024ecd2d0f3629ab256da3030e5f4a",
60-
"0xad535120c80dea856727178e9ffc03a549ae69c71fa8fd69e7da87500d94ed5e",
61-
"0x18c929c35a4632f9aea2684bba2fb918a1a32035c2c2aa1c50d7d48ab6f84960",
62-
"0x1eae092568547d900432cc755ab381fedac95d4847e6de94bbdec79ca7706b47",
63-
"0xe8c3fe7bc103e7c4da1d5a7d2bafbc6e9d1b1d42cbdb8b75bc9136701e18dd4d",
64-
"0xd456f32b3f050d9204e43c123bb5a81c01c482ea56c9a0249ad60e36c64468af",
65-
"0xbf3f96fd42d14dc9db9126b57923d8845d8434c7e64dcf9a1a23f17afec30aad",
66-
"0xf0e2720fdd888df5369e9aa081347d97603fadbd22f5a1978b08fbaa7c13e6f7"
67-
]
68-
))
7+
from utils.config import contracts, CSM_COMMITTEE_MS, EASYTRACK_CS_SET_VETTED_GATE_TREE_FACTORY
8+
from utils.test.merkle_tree import ICSTree
9+
10+
11+
12+
def csm_set_ics_tree_members(members):
13+
tree = ICSTree.new(members)
14+
calldata = _encode_calldata(["bytes32", "string"], [tree.root, "0xabc"])
15+
tx = contracts.easy_track.createMotion(EASYTRACK_CS_SET_VETTED_GATE_TREE_FACTORY, calldata, {"from": CSM_COMMITTEE_MS})
16+
chain.sleep(60 * 60 * 24 * 3)
17+
chain.mine()
18+
motions = contracts.easy_track.getMotions()
19+
contracts.easy_track.enactMotion(
20+
motions[-1][0],
21+
tx.events["MotionCreated"]["_evmScriptCallData"],
22+
{"from": CSM_COMMITTEE_MS},
23+
)
24+
25+
return tree
26+
6927

7028

7129
def csm_add_node_operator(csm, permissionless_gate, accounting, node_operator, keys_count=5, curve_id=0):

utils/test/merkle_tree.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Strikes: TypeAlias = list[int]
1717
RewardTreeLeaf: TypeAlias = tuple[NodeOperatorId, Shares]
1818
StrikesTreeLeaf: TypeAlias = tuple[NodeOperatorId, Pubkey, Strikes]
19+
ICSTreeLeaf: TypeAlias = str
1920

2021

2122
class TreeJSONEncoder(json.JSONEncoder):
@@ -316,3 +317,45 @@ def dump(self) -> Dump[StrikesTreeLeaf]:
316317
def new(cls, values: Sequence[StrikesTreeLeaf]):
317318
"""Create new instance around the wrapped tree out of the given values"""
318319
return cls(StandardMerkleTree(values, ("uint256", "bytes", "uint256[]")))
320+
321+
322+
@dataclass
323+
class ICSTree:
324+
"""A wrapper around StandardMerkleTree to cover use cases of the CSM ICS Vetted Gate"""
325+
326+
tree: StandardMerkleTree[ICSTreeLeaf]
327+
328+
@property
329+
def root(self) -> HexBytes:
330+
return HexBytes(self.tree.root)
331+
332+
@classmethod
333+
def decode(cls, content: bytes):
334+
"""Restore a tree from a supported binary representation"""
335+
336+
try:
337+
return cls(StandardMerkleTree.load(json.loads(content)))
338+
except json.JSONDecodeError as e:
339+
raise ValueError("Unsupported tree format") from e
340+
341+
def encode(self) -> bytes:
342+
"""Convert the underlying StandardMerkleTree to a binary representation"""
343+
344+
return (
345+
TreeJSONEncoder(
346+
indent=None,
347+
separators=(',', ':'),
348+
sort_keys=True,
349+
)
350+
.encode(self.dump())
351+
.encode()
352+
)
353+
354+
def dump(self) -> Dump[ICSTreeLeaf]:
355+
return self.tree.dump()
356+
357+
@classmethod
358+
def new(cls, values: Sequence[ICSTreeLeaf]):
359+
"""Create new instance around the wrapped tree out of the given values"""
360+
values = [[value] for value in values]
361+
return cls(StandardMerkleTree(values, ("address",)))

0 commit comments

Comments
 (0)