From 7374db1e64c0fe97cdc825575206991c99e9f7be Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 26 Mar 2025 14:18:27 +0000 Subject: [PATCH 01/43] validator snapshots --- l1-contracts/src/core/Rollup.sol | 6 +- l1-contracts/src/core/interfaces/IStaking.sol | 6 +- .../core/interfaces/IValidatorSelection.sol | 13 ++ l1-contracts/src/core/libraries/TimeLib.sol | 12 +- .../libraries/staking/AddressSnapshotLib.sol | 142 ++++++++++++++++++ .../src/core/libraries/staking/StakingLib.sol | 5 +- .../ValidatorSelectionLib.sol | 18 ++- l1-contracts/test/staking/StakingCheater.sol | 4 +- .../ValidatorSnapshots.t.sol | 87 +++++++++++ .../test/validator-selection/snapshots.tree | 4 + 10 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol create mode 100644 l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol create mode 100644 l1-contracts/test/validator-selection/snapshots.tree diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 19ac02f22c52..a3e8164c238d 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -19,8 +19,7 @@ import { IStaking, ValidatorInfo, Exit, - OperatorInfo, - EnumerableSet + OperatorInfo } from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; import { @@ -28,6 +27,7 @@ import { } from "@aztec/core/libraries/rollup/FeeLib.sol"; import {HeaderLib} from "@aztec/core/libraries/rollup/HeaderLib.sol"; import {EpochProofLib} from "./libraries/rollup/EpochProofLib.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {ProposeLib, ValidateHeaderArgs} from "./libraries/rollup/ProposeLib.sol"; import {ValidatorSelectionLib} from "./libraries/validator-selection/ValidatorSelectionLib.sol"; import { @@ -59,7 +59,7 @@ import { * about the state of the rollup and test it. */ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { - using EnumerableSet for EnumerableSet.AddressSet; + using AddressSnapshotLib for SnapshottedAddressSet; using TimeLib for Timestamp; using TimeLib for Slot; diff --git a/l1-contracts/src/core/interfaces/IStaking.sol b/l1-contracts/src/core/interfaces/IStaking.sol index 3460af47db1a..2ad88095657c 100644 --- a/l1-contracts/src/core/interfaces/IStaking.sol +++ b/l1-contracts/src/core/interfaces/IStaking.sol @@ -4,7 +4,8 @@ pragma solidity >=0.8.27; import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; + // None -> Does not exist in our setup // Validating -> Participating as validator @@ -35,12 +36,13 @@ struct Exit { address recipient; } + struct StakingStorage { IERC20 stakingAsset; address slasher; uint256 minimumStake; Timestamp exitDelay; - EnumerableSet.AddressSet attesters; + SnapshottedAddressSet attesters; mapping(address attester => ValidatorInfo) info; mapping(address attester => Exit) exits; } diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index 1962dfd899d7..12dcd229c905 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -16,9 +16,22 @@ struct EpochData { uint256 nextSeed; } + +// TODO(md) +struct ValidatorSetSizeSnapshot { + uint128 size; + uint96 epochNumber; +} + struct ValidatorSelectionStorage { // A mapping to snapshots of the validator set mapping(Epoch => EpochData) epochs; + // A mapping to snapshots of the validator set size + + // TODO(md): this can just be inside the epoch data???? + // Only keeping this here so it looks the same as the issue for now + ValidatorSetSizeSnapshot[] epochSizeSnapshots; + // The last stored randao value, same value as `seed` in the last inserted epoch uint256 lastSeed; uint256 targetCommitteeSize; diff --git a/l1-contracts/src/core/libraries/TimeLib.sol b/l1-contracts/src/core/libraries/TimeLib.sol index ef8edbe7485a..04dc8bfa7780 100644 --- a/l1-contracts/src/core/libraries/TimeLib.sol +++ b/l1-contracts/src/core/libraries/TimeLib.sol @@ -6,9 +6,9 @@ pragma solidity >=0.8.27; import {Timestamp, Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol"; struct TimeStorage { - uint256 genesisTime; - uint256 slotDuration; // Number of seconds in a slot - uint256 epochDuration; // Number of slots in an epoch + uint128 genesisTime; + uint32 slotDuration; // Number of seconds in a slot + uint32 epochDuration; // Number of slots in an epoch } library TimeLib { @@ -16,9 +16,9 @@ library TimeLib { function initialize(uint256 _genesisTime, uint256 _slotDuration, uint256 _epochDuration) internal { TimeStorage storage store = getStorage(); - store.genesisTime = _genesisTime; - store.slotDuration = _slotDuration; - store.epochDuration = _epochDuration; + store.genesisTime = uint128(_genesisTime); + store.slotDuration = uint32(_slotDuration); + store.epochDuration = uint32(_epochDuration); } function toTimestamp(Slot _a) internal view returns (Timestamp) { diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol new file mode 100644 index 000000000000..7920ee30364a --- /dev/null +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {IStaking} from "@aztec/core/interfaces/IStaking.sol"; + +// The validator index -> the validator at that point in time +struct SnapshottedAddressSet { + uint256 size; + mapping(uint256 index => AddressSnapshot[]) checkpoints; + + // Store up to date position for each validator + mapping(address validator => uint256 index) validatorToIndex; +} + +struct AddressSnapshot { + address addr; + uint96 epochNumber; +} + + +// TODO(md): think about this in the context of the enumerable set a bit more +// - + +// @note +// Do we want to be able to remove somebody from the validator set by their index? +// Or do we want to be able to remove them via their address, like the enumerable set? +// What is the most efficient way to implement such a thing + +library AddressSnapshotLib { + function add(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { + // Insert into the end of the array + uint256 index = _self.size; + + // TODO: tidy up + uint256 _epochNumber = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp))); + + // TODO: check double insertion?? + _self.validatorToIndex[_validator] = index; + _self.checkpoints[index].push(AddressSnapshot({addr: _validator, epochNumber: uint96(_epochNumber)})); + _self.size += 1; + return true; + } + + function remove(SnapshottedAddressSet storage _self, uint256 _index) internal returns (bool) { + // To remove from the list, we push the last item into the index and reduce the size + uint256 lastIndex = _self.size - 1; + uint256 lastSnapshotLength = _self.checkpoints[lastIndex].length; + + // TODO: tidy up + uint256 epochNow = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp))); + + AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; + + address lastValidator = lastSnapshot.addr; + _self.validatorToIndex[lastValidator] = 0; // Remove the last validator from the index + + // If we are removing the last item, we cannot swap it with anything + // so we append a new address of zero for this epoch + if (lastIndex == _index) { + lastSnapshot.addr = address(0); + } + + require(lastSnapshot.epochNumber != epochNow, "Cannot edit an item twice within the same epoch"); + + lastSnapshot.epochNumber = uint96(epochNow); + _self.checkpoints[_index].push(lastSnapshot); + _self.size -= 1; + return true; + } + + function remove(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { + uint256 index = _self.validatorToIndex[_validator]; + require(index != 0, "Validator not found"); + return remove(_self, index); + } + + function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { + return getValidatorInIndexNow(_self, _index); + } + + function getValidatorInIndexNow(SnapshottedAddressSet storage _self, uint256 index) internal view returns (address) { + uint256 numCheckpoints = _self.checkpoints[index].length; + + if (index >= _self.size) { + return address(0); + } + + if (numCheckpoints == 0) { + return address(0); + } + + AddressSnapshot memory lastSnapshot = _self.checkpoints[index][numCheckpoints - 1]; + return lastSnapshot.addr; + } + + function getAddresssIndexAt(SnapshottedAddressSet storage _self, uint256 index, uint256 _epoch) internal view returns (address) { + uint256 numCheckpoints = _self.checkpoints[index].length; + uint96 epoch = uint96(_epoch); + + if (numCheckpoints == 0) { + return address(0); + } + + // Check the most recent checkpoint + AddressSnapshot memory lastSnapshot = _self.checkpoints[index][numCheckpoints - 1]; + if (lastSnapshot.epochNumber <= epoch) { + return lastSnapshot.addr; + } + + // Binary search + uint256 lower = 0; + uint256 upper = numCheckpoints - 1; + while (upper > lower) { + uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow + AddressSnapshot memory cp = _self.checkpoints[index][center]; + if (cp.epochNumber == epoch) { + return cp.addr; + } else if (cp.epochNumber < epoch) { + lower = center; + } else { + upper = center - 1; + } + } + + return _self.checkpoints[index][lower].addr; + } + + function length(SnapshottedAddressSet storage _self) internal view returns (uint256) { + return _self.size; + } + + // TODO: ideally we would not have this - created to match with the enumerable set + function values(SnapshottedAddressSet storage _self) internal view returns (address[] memory) { + address[] memory vals = new address[](_self.size); + for (uint256 i = 0; i < _self.size; i++) { + vals[i] = getValidatorInIndexNow(_self, i); + } + return vals; + } +} diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 87ecfda67dfe..651a621966e5 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -13,11 +13,11 @@ import { import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; -import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; library StakingLib { using SafeERC20 for IERC20; - using EnumerableSet for EnumerableSet.AddressSet; + using AddressSnapshotLib for SnapshottedAddressSet; bytes32 private constant STAKING_SLOT = keccak256("aztec.core.staking.storage"); @@ -126,6 +126,7 @@ library StakingLib { Errors.Staking__NothingToExit(_attester) ); if (validator.status == Status.VALIDATING) { + // Need to remove them based on the index, not the address!!!!! require(store.attesters.remove(_attester), Errors.Staking__FailedToRemove(_attester)); } diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 0d1398e431b3..8afd4d050263 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -5,12 +5,13 @@ pragma solidity >=0.8.27; import {BlockHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol"; import {StakingStorage} from "@aztec/core/interfaces/IStaking.sol"; import { - EpochData, ValidatorSelectionStorage + EpochData, ValidatorSelectionStorage, ValidatorSetSizeSnapshot } from "@aztec/core/interfaces/IValidatorSelection.sol"; import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; @@ -19,6 +20,7 @@ library ValidatorSelectionLib { using MessageHashUtils for bytes32; using SignatureLib for Signature; using TimeLib for Timestamp; + using AddressSnapshotLib for SnapshottedAddressSet; bytes32 private constant VALIDATOR_SELECTION_STORAGE_POSITION = keccak256("aztec.validator_selection.storage"); @@ -44,6 +46,9 @@ library ValidatorSelectionLib { EpochData storage epoch = store.epochs[epochNumber]; if (epoch.sampleSeed == 0) { + // TODO: can be combined into the current epoch store + checkpointValidatorSetSize(_stakingStore, epochNumber); + epoch.sampleSeed = getSampleSeed(epochNumber); epoch.nextSeed = store.lastSeed = computeNextSeed(epochNumber); epoch.committee = sampleValidators(_stakingStore, epoch.sampleSeed); @@ -146,6 +151,17 @@ library ValidatorSelectionLib { return _stakingStore.info[attester].proposer; } + function checkpointValidatorSetSize(StakingStorage storage _stakingStore, Epoch _epochNumber) internal { + ValidatorSelectionStorage storage store = getStorage(); + uint256 setSize = _stakingStore.attesters.length(); + + // TODO: safe cast + store.epochSizeSnapshots.push(ValidatorSetSizeSnapshot({ + size: uint128(setSize), + epochNumber: uint96(Epoch.unwrap(_epochNumber)) + })); + } + /** * @notice Samples a validator set for a specific epoch * diff --git a/l1-contracts/test/staking/StakingCheater.sol b/l1-contracts/test/staking/StakingCheater.sol index 15fd16795eca..e54c8d534c88 100644 --- a/l1-contracts/test/staking/StakingCheater.sol +++ b/l1-contracts/test/staking/StakingCheater.sol @@ -3,7 +3,6 @@ pragma solidity >=0.8.27; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; import { IStaking, ValidatorInfo, Exit, OperatorInfo, Status } from "@aztec/core/interfaces/IStaking.sol"; @@ -11,9 +10,10 @@ import { import {Timestamp} from "@aztec/core/libraries/TimeLib.sol"; import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; import {Slasher} from "@aztec/core/staking/Slasher.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; contract StakingCheater is IStaking { - using EnumerableSet for EnumerableSet.AddressSet; + using AddressSnapshotLib for SnapshottedAddressSet; constructor( IERC20 _stakingAsset, diff --git a/l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol b/l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol new file mode 100644 index 000000000000..bd0d8359649b --- /dev/null +++ b/l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; + +contract LibTest { + SnapshottedAddressSet validatorSet; + + function add(address _validator) public { + AddressSnapshotLib.add(validatorSet, _validator); + } + + function remove(uint256 _index) public { + AddressSnapshotLib.remove(validatorSet, _index); + } + + function getValidatorInIndexNow(uint256 _index) public view returns (address) { + return AddressSnapshotLib.getValidatorInIndexNow(validatorSet, _index); + } + + function getAddresssIndexAt(uint256 _index, uint256 _epoch) public view returns (address) { + return AddressSnapshotLib.getAddresssIndexAt(validatorSet, _index, _epoch); + } +} + +contract AddressSnapshotsTest is Test { + LibTest libTest; + function setUp() public { + libTest = new LibTest(); + } + + function cheat__setEpochNow(uint256 _epoch) public { + // TODO: set the storage directly in the TimeLib + vm.warp(block.timestamp + _epoch); + } + + function test_getValidatorInIndexNow() public { + // Empty should return 0 + assertEq(libTest.getValidatorInIndexNow(0), address(0)); + + libTest.add(address(1)); + libTest.add(address(2)); + libTest.add(address(3)); + + assertEq(libTest.getValidatorInIndexNow(0), address(1)); + assertEq(libTest.getValidatorInIndexNow(1), address(2)); + assertEq(libTest.getValidatorInIndexNow(2), address(3)); + } + + function test_getAddresssIndexAt() public { + // Empty should return 0 + assertEq(libTest.getAddresssIndexAt(0, 1), address(0)); + + // Adds validator 1 to the first index in the set, at epoch 1 + libTest.add(address(1)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 1), address(1)); + + // Remove index 0 from the set, in the new epoch + libTest.remove(/* index */ 0); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 2), address(0)); + + // Add validator 2 to the first index in the set + libTest.add(address(2)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 3), address(2)); + + // Setup and remove the last item in the set alot of times + libTest.remove(/* index */ 0); + libTest.add(address(3)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 5), address(3)); + + libTest.remove(/* index */ 0); + libTest.add(address(4)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 7), address(4)); + + // Expect past values to be maintained + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 1), address(1)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 2), address(0)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 3), address(2)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 4), address(0)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 5), address(3)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 6), address(0)); + assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 7), address(4)); + } + +} diff --git a/l1-contracts/test/validator-selection/snapshots.tree b/l1-contracts/test/validator-selection/snapshots.tree new file mode 100644 index 000000000000..80d64c725bba --- /dev/null +++ b/l1-contracts/test/validator-selection/snapshots.tree @@ -0,0 +1,4 @@ +ValidatorSnapshotTest +└── When the snapshots are empty + ├── It should allow additions and removals + └── It should not allow removals of non-existent validators From e74a472568e4bf7aa4bf14185d61dfe4f6ef7b15 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 26 Mar 2025 17:09:01 +0000 Subject: [PATCH 02/43] test: add cheating time --- l1-contracts/src/core/Rollup.sol | 12 +- l1-contracts/src/core/interfaces/IStaking.sol | 4 +- .../core/interfaces/IValidatorSelection.sol | 2 - .../libraries/staking/AddressSnapshotLib.sol | 35 +++-- .../src/core/libraries/staking/StakingLib.sol | 2 +- .../ValidatorSelectionLib.sol | 20 ++- l1-contracts/src/mock/FeeAssetHandler.sol | 4 +- l1-contracts/test/staking/StakingCheater.sol | 2 +- .../AddressSnapshots.t.sol | 136 ++++++++++++++++++ .../ValidatorSnapshots.t.sol | 87 ----------- 10 files changed, 180 insertions(+), 124 deletions(-) create mode 100644 l1-contracts/test/validator-selection/AddressSnapshots.t.sol delete mode 100644 l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index a3e8164c238d..32d847c1a305 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -15,19 +15,17 @@ import { FeeHeader, RollupConfigInput } from "@aztec/core/interfaces/IRollup.sol"; -import { - IStaking, - ValidatorInfo, - Exit, - OperatorInfo -} from "@aztec/core/interfaces/IStaking.sol"; +import {IStaking, ValidatorInfo, Exit, OperatorInfo} from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; import { FeeLib, FeeHeaderLib, FeeAssetValue, PriceLib } from "@aztec/core/libraries/rollup/FeeLib.sol"; import {HeaderLib} from "@aztec/core/libraries/rollup/HeaderLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {EpochProofLib} from "./libraries/rollup/EpochProofLib.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {ProposeLib, ValidateHeaderArgs} from "./libraries/rollup/ProposeLib.sol"; import {ValidatorSelectionLib} from "./libraries/validator-selection/ValidatorSelectionLib.sol"; import { diff --git a/l1-contracts/src/core/interfaces/IStaking.sol b/l1-contracts/src/core/interfaces/IStaking.sol index 2ad88095657c..2da8a46ffcab 100644 --- a/l1-contracts/src/core/interfaces/IStaking.sol +++ b/l1-contracts/src/core/interfaces/IStaking.sol @@ -2,10 +2,9 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; +import {SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; - // None -> Does not exist in our setup // Validating -> Participating as validator @@ -36,7 +35,6 @@ struct Exit { address recipient; } - struct StakingStorage { IERC20 stakingAsset; address slasher; diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index 12dcd229c905..dc54ec0955e1 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -16,7 +16,6 @@ struct EpochData { uint256 nextSeed; } - // TODO(md) struct ValidatorSetSizeSnapshot { uint128 size; @@ -31,7 +30,6 @@ struct ValidatorSelectionStorage { // TODO(md): this can just be inside the epoch data???? // Only keeping this here so it looks the same as the issue for now ValidatorSetSizeSnapshot[] epochSizeSnapshots; - // The last stored randao value, same value as `seed` in the last inserted epoch uint256 lastSeed; uint256 targetCommitteeSize; diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 7920ee30364a..8a66ceeb9153 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -2,14 +2,12 @@ // Copyright 2025 Aztec Labs. pragma solidity >=0.8.27; -import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; -import {IStaking} from "@aztec/core/interfaces/IStaking.sol"; +import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; // The validator index -> the validator at that point in time struct SnapshottedAddressSet { uint256 size; mapping(uint256 index => AddressSnapshot[]) checkpoints; - // Store up to date position for each validator mapping(address validator => uint256 index) validatorToIndex; } @@ -19,7 +17,6 @@ struct AddressSnapshot { uint96 epochNumber; } - // TODO(md): think about this in the context of the enumerable set a bit more // - @@ -38,7 +35,9 @@ library AddressSnapshotLib { // TODO: check double insertion?? _self.validatorToIndex[_validator] = index; - _self.checkpoints[index].push(AddressSnapshot({addr: _validator, epochNumber: uint96(_epochNumber)})); + _self.checkpoints[index].push( + AddressSnapshot({addr: _validator, epochNumber: uint96(_epochNumber)}) + ); _self.size += 1; return true; } @@ -80,10 +79,14 @@ library AddressSnapshotLib { return getValidatorInIndexNow(_self, _index); } - function getValidatorInIndexNow(SnapshottedAddressSet storage _self, uint256 index) internal view returns (address) { - uint256 numCheckpoints = _self.checkpoints[index].length; + function getValidatorInIndexNow(SnapshottedAddressSet storage _self, uint256 _index) + internal + view + returns (address) + { + uint256 numCheckpoints = _self.checkpoints[_index].length; - if (index >= _self.size) { + if (_index >= _self.size) { return address(0); } @@ -91,12 +94,16 @@ library AddressSnapshotLib { return address(0); } - AddressSnapshot memory lastSnapshot = _self.checkpoints[index][numCheckpoints - 1]; + AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1]; return lastSnapshot.addr; } - function getAddresssIndexAt(SnapshottedAddressSet storage _self, uint256 index, uint256 _epoch) internal view returns (address) { - uint256 numCheckpoints = _self.checkpoints[index].length; + function getAddresssIndexAt(SnapshottedAddressSet storage _self, uint256 _index, uint256 _epoch) + internal + view + returns (address) + { + uint256 numCheckpoints = _self.checkpoints[_index].length; uint96 epoch = uint96(_epoch); if (numCheckpoints == 0) { @@ -104,7 +111,7 @@ library AddressSnapshotLib { } // Check the most recent checkpoint - AddressSnapshot memory lastSnapshot = _self.checkpoints[index][numCheckpoints - 1]; + AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1]; if (lastSnapshot.epochNumber <= epoch) { return lastSnapshot.addr; } @@ -114,7 +121,7 @@ library AddressSnapshotLib { uint256 upper = numCheckpoints - 1; while (upper > lower) { uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow - AddressSnapshot memory cp = _self.checkpoints[index][center]; + AddressSnapshot memory cp = _self.checkpoints[_index][center]; if (cp.epochNumber == epoch) { return cp.addr; } else if (cp.epochNumber < epoch) { @@ -124,7 +131,7 @@ library AddressSnapshotLib { } } - return _self.checkpoints[index][lower].addr; + return _self.checkpoints[_index][lower].addr; } function length(SnapshottedAddressSet storage _self) internal view returns (uint256) { diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 651a621966e5..9717f36612ef 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -10,10 +10,10 @@ import { StakingStorage, IStakingCore } from "@aztec/core/interfaces/IStaking.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; library StakingLib { using SafeERC20 for IERC20; diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 8afd4d050263..9968670b6d98 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -4,14 +4,16 @@ pragma solidity >=0.8.27; import {BlockHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol"; import {StakingStorage} from "@aztec/core/interfaces/IStaking.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import { - EpochData, ValidatorSelectionStorage, ValidatorSetSizeSnapshot + EpochData, + ValidatorSelectionStorage, + ValidatorSetSizeSnapshot } from "@aztec/core/interfaces/IValidatorSelection.sol"; import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; @@ -151,15 +153,19 @@ library ValidatorSelectionLib { return _stakingStore.info[attester].proposer; } - function checkpointValidatorSetSize(StakingStorage storage _stakingStore, Epoch _epochNumber) internal { + function checkpointValidatorSetSize(StakingStorage storage _stakingStore, Epoch _epochNumber) + internal + { ValidatorSelectionStorage storage store = getStorage(); uint256 setSize = _stakingStore.attesters.length(); // TODO: safe cast - store.epochSizeSnapshots.push(ValidatorSetSizeSnapshot({ - size: uint128(setSize), - epochNumber: uint96(Epoch.unwrap(_epochNumber)) - })); + store.epochSizeSnapshots.push( + ValidatorSetSizeSnapshot({ + size: uint128(setSize), + epochNumber: uint96(Epoch.unwrap(_epochNumber)) + }) + ); } /** diff --git a/l1-contracts/src/mock/FeeAssetHandler.sol b/l1-contracts/src/mock/FeeAssetHandler.sol index 2b62ce552ba2..0e536fb24d1d 100644 --- a/l1-contracts/src/mock/FeeAssetHandler.sol +++ b/l1-contracts/src/mock/FeeAssetHandler.sol @@ -20,11 +20,11 @@ contract FeeAssetHandler is IFeeAssetHandler, Ownable { mintAmount = _mintAmount; } - function mint(address _recipient) external override { + function mint(address _recipient) external override(IFeeAssetHandler) { FEE_ASSET.mint(_recipient, mintAmount); } - function setMintAmount(uint256 _amount) external override onlyOwner { + function setMintAmount(uint256 _amount) external override(IFeeAssetHandler) onlyOwner { mintAmount = _amount; emit MintAmountSet(_amount); } diff --git a/l1-contracts/test/staking/StakingCheater.sol b/l1-contracts/test/staking/StakingCheater.sol index e54c8d534c88..af2759890149 100644 --- a/l1-contracts/test/staking/StakingCheater.sol +++ b/l1-contracts/test/staking/StakingCheater.sol @@ -10,7 +10,7 @@ import { import {Timestamp} from "@aztec/core/libraries/TimeLib.sol"; import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; import {Slasher} from "@aztec/core/staking/Slasher.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; contract StakingCheater is IStaking { using AddressSnapshotLib for SnapshottedAddressSet; diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol new file mode 100644 index 000000000000..dd948e744c78 --- /dev/null +++ b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {TimeLib, TimeStorage} from "@aztec/core/libraries/TimeLib.sol"; + +contract LibTest { + SnapshottedAddressSet validatorSet; + + function add(address _validator) public { + AddressSnapshotLib.add(validatorSet, _validator); + } + + function remove(uint256 _index) public { + AddressSnapshotLib.remove(validatorSet, _index); + } + + function getValidatorInIndexNow(uint256 _index) public view returns (address) { + return AddressSnapshotLib.getValidatorInIndexNow(validatorSet, _index); + } + + function getAddresssIndexAt(uint256 _index, uint256 _epoch) public view returns (address) { + return AddressSnapshotLib.getAddresssIndexAt(validatorSet, _index, _epoch); + } +} + +contract AddressSnapshotsTest is Test { + bytes32 private constant TIME_STORAGE_POSITION = keccak256("aztec.time.storage"); + uint256 private constant SLOT_DURATION = 36; + uint256 private constant EPOCH_DURATION = 48; + + uint256 private immutable GENESIS_TIME = block.timestamp; + + LibTest libTest; + + function setUp() public { + libTest = new LibTest(); + + // Set the time storage + setTimeStorage( + TimeStorage({ + genesisTime: uint128(GENESIS_TIME), + slotDuration: uint32(SLOT_DURATION), + epochDuration: uint32(EPOCH_DURATION) + }) + ); + } + + function getTimeStorage() public view returns (TimeStorage memory) { + return abi.decode(bytes.concat(vm.load(address(libTest), TIME_STORAGE_POSITION)), (TimeStorage)); + } + + function setTimeStorage(TimeStorage memory _timeStorage) public { + vm.store( + address(libTest), + TIME_STORAGE_POSITION, + bytes32( + abi.encodePacked( + // Encoding order is a fun one + bytes8(0), + _timeStorage.epochDuration, + _timeStorage.slotDuration, + _timeStorage.genesisTime + ) + ) + ); + } + + function cheat__setEpochNow(uint256 _epoch) public { + vm.warp(GENESIS_TIME + 1 + _epoch * SLOT_DURATION * EPOCH_DURATION); + } + + function test_getValidatorInIndexNow() public { + // Empty should return 0 + assertEq(libTest.getValidatorInIndexNow(0), address(0)); + + libTest.add(address(1)); + libTest.add(address(2)); + libTest.add(address(3)); + + assertEq(libTest.getValidatorInIndexNow(0), address(1)); + assertEq(libTest.getValidatorInIndexNow(1), address(2)); + assertEq(libTest.getValidatorInIndexNow(2), address(3)); + } + + function test_getAddresssIndexAt() public { + // Empty should return 0 + cheat__setEpochNow(0); + assertEq(libTest.getAddresssIndexAt(0, 0), address(0)); + + // Adds validator 1 to the first index in the set, at epoch 1 + cheat__setEpochNow(1); + + libTest.add(address(1)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 1), address(1)); + + // Remove index 0 from the set, in the new epoch + cheat__setEpochNow(2); + + libTest.remove( /* index */ 0); + + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 2), address(0)); + + // Add validator 2 to the first index in the set + cheat__setEpochNow(3); + + libTest.add(address(2)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 3), address(2)); + + // Setup and remove the last item in the set alot of times + cheat__setEpochNow(4); + libTest.remove( /* index */ 0); + + cheat__setEpochNow(5); + libTest.add(address(3)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 5), address(3)); + + cheat__setEpochNow(6); + libTest.remove( /* index */ 0); + + cheat__setEpochNow(7); + libTest.add(address(4)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 7), address(4)); + + // Expect past values to be maintained + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 1), address(1)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 2), address(0)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 3), address(2)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 4), address(0)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 5), address(3)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 6), address(0)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 7), address(4)); + } +} diff --git a/l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol b/l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol deleted file mode 100644 index bd0d8359649b..000000000000 --- a/l1-contracts/test/validator-selection/ValidatorSnapshots.t.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2025 Aztec Labs. -pragma solidity >=0.8.27; - -import {Test} from "forge-std/Test.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/interfaces/IStaking.sol"; - -contract LibTest { - SnapshottedAddressSet validatorSet; - - function add(address _validator) public { - AddressSnapshotLib.add(validatorSet, _validator); - } - - function remove(uint256 _index) public { - AddressSnapshotLib.remove(validatorSet, _index); - } - - function getValidatorInIndexNow(uint256 _index) public view returns (address) { - return AddressSnapshotLib.getValidatorInIndexNow(validatorSet, _index); - } - - function getAddresssIndexAt(uint256 _index, uint256 _epoch) public view returns (address) { - return AddressSnapshotLib.getAddresssIndexAt(validatorSet, _index, _epoch); - } -} - -contract AddressSnapshotsTest is Test { - LibTest libTest; - function setUp() public { - libTest = new LibTest(); - } - - function cheat__setEpochNow(uint256 _epoch) public { - // TODO: set the storage directly in the TimeLib - vm.warp(block.timestamp + _epoch); - } - - function test_getValidatorInIndexNow() public { - // Empty should return 0 - assertEq(libTest.getValidatorInIndexNow(0), address(0)); - - libTest.add(address(1)); - libTest.add(address(2)); - libTest.add(address(3)); - - assertEq(libTest.getValidatorInIndexNow(0), address(1)); - assertEq(libTest.getValidatorInIndexNow(1), address(2)); - assertEq(libTest.getValidatorInIndexNow(2), address(3)); - } - - function test_getAddresssIndexAt() public { - // Empty should return 0 - assertEq(libTest.getAddresssIndexAt(0, 1), address(0)); - - // Adds validator 1 to the first index in the set, at epoch 1 - libTest.add(address(1)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 1), address(1)); - - // Remove index 0 from the set, in the new epoch - libTest.remove(/* index */ 0); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 2), address(0)); - - // Add validator 2 to the first index in the set - libTest.add(address(2)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 3), address(2)); - - // Setup and remove the last item in the set alot of times - libTest.remove(/* index */ 0); - libTest.add(address(3)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 5), address(3)); - - libTest.remove(/* index */ 0); - libTest.add(address(4)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 7), address(4)); - - // Expect past values to be maintained - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 1), address(1)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 2), address(0)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 3), address(2)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 4), address(0)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 5), address(3)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 6), address(0)); - assertEq(libTest.getAddresssIndexAt(/* index */ 0, /* epoch */ 7), address(4)); - } - -} From dfbddb2432db21e72d10288822de577a80321e1f Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:47:28 +0000 Subject: [PATCH 03/43] checkpoint --- .../AddressSnapshots.t.sol | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol index dd948e744c78..4e9eab2a2529 100644 --- a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol +++ b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol @@ -17,6 +17,14 @@ contract LibTest { AddressSnapshotLib.remove(validatorSet, _index); } + function remove(address _validator) public { + AddressSnapshotLib.remove(validatorSet, _validator); + } + + function at(uint256 _index) public view returns (address) { + return AddressSnapshotLib.at(validatorSet, _index); + } + function getValidatorInIndexNow(uint256 _index) public view returns (address) { return AddressSnapshotLib.getValidatorInIndexNow(validatorSet, _index); } @@ -24,6 +32,14 @@ contract LibTest { function getAddresssIndexAt(uint256 _index, uint256 _epoch) public view returns (address) { return AddressSnapshotLib.getAddresssIndexAt(validatorSet, _index, _epoch); } + + function length() public view returns (uint256) { + return AddressSnapshotLib.length(validatorSet); + } + + function values() public view returns (address[] memory) { + return AddressSnapshotLib.values(validatorSet); + } } contract AddressSnapshotsTest is Test { @@ -133,4 +149,71 @@ contract AddressSnapshotsTest is Test { assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 6), address(0)); assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 7), address(4)); } + + function test_length() public { + assertEq(libTest.length(), 0); + + libTest.add(address(1)); + assertEq(libTest.length(), 1); + + cheat__setEpochNow(1); + libTest.remove( /* index */ 0); + assertEq(libTest.length(), 0); + + cheat__setEpochNow(2); + libTest.add(address(1)); + libTest.add(address(2)); + assertEq(libTest.length(), 2); + + cheat__setEpochNow(3); + libTest.remove( /* index */ 0); + assertEq(libTest.length(), 1); + } + + function test_values() public { + libTest.add(address(1)); + libTest.add(address(2)); + libTest.add(address(3)); + + address[] memory values = libTest.values(); + assertEq(values.length, 3); + assertEq(values[0], address(1)); + assertEq(values[1], address(2)); + assertEq(values[2], address(3)); + } + + function test_remove_by_name() public { + libTest.add(address(1)); + libTest.add(address(2)); + libTest.add(address(3)); + + cheat__setEpochNow(1); + libTest.remove(address(2)); + + address[] memory values = libTest.values(); + assertEq(values.length, 2); + assertEq(values[0], address(1)); + assertEq(values[1], address(3)); + } + + function test_at() public { + libTest.add(address(1)); + libTest.add(address(2)); + libTest.add(address(3)); + + assertEq(libTest.at(0), address(1)); + assertEq(libTest.at(1), address(2)); + assertEq(libTest.at(2), address(3)); + + cheat__setEpochNow(1); + libTest.remove( /* index */ 0); + + // When removing and item, the index has become shuffled + assertEq(libTest.at(0), address(3)); + assertEq(libTest.at(1), address(2)); + + // Expect past values to be maintained + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 0), address(1)); + assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 1), address(3)); + } } From 0b4038a47f47ea25a30ba3d3ba7eb3b16d342faa Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 14:31:06 +0000 Subject: [PATCH 04/43] chore: shift sampling --- .../libraries/staking/AddressSnapshotLib.sol | 33 ++-- .../src/core/libraries/staking/StakingLib.sol | 5 +- .../ValidatorSelectionLib.sol | 5 +- l1-contracts/test/staking/StakingCheater.sol | 5 +- l1-contracts/test/staking/TimeCheater.sol | 64 +++++++ l1-contracts/test/staking/base.t.sol | 10 ++ l1-contracts/test/staking/slash.t.sol | 3 + .../AddressSnapshots.t.sol | 164 ++++++++++-------- 8 files changed, 202 insertions(+), 87 deletions(-) create mode 100644 l1-contracts/test/staking/TimeCheater.sol diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 8a66ceeb9153..d6e1dc11de15 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -4,6 +4,8 @@ pragma solidity >=0.8.27; import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import "forge-std/console.sol"; + // The validator index -> the validator at that point in time struct SnapshottedAddressSet { uint256 size; @@ -34,7 +36,14 @@ library AddressSnapshotLib { uint256 _epochNumber = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp))); // TODO: check double insertion?? - _self.validatorToIndex[_validator] = index; + + // Include max bit to indicate that the validator is in the set - means 0 index is not 0 + uint256 indexWithBit = index | (1 << 255); + console.log(" inserting validator", _validator); + console.log(" at index"); + console.logBytes32(bytes32(indexWithBit)); + + _self.validatorToIndex[_validator] = indexWithBit; _self.checkpoints[index].push( AddressSnapshot({addr: _validator, epochNumber: uint96(_epochNumber)}) ); @@ -53,7 +62,8 @@ library AddressSnapshotLib { AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; address lastValidator = lastSnapshot.addr; - _self.validatorToIndex[lastValidator] = 0; // Remove the last validator from the index + uint256 newLocationWithMask = _index | (1 << 255); + _self.validatorToIndex[lastValidator] = newLocationWithMask; // Remove the last validator from the index // If we are removing the last item, we cannot swap it with anything // so we append a new address of zero for this epoch @@ -72,14 +82,17 @@ library AddressSnapshotLib { function remove(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { uint256 index = _self.validatorToIndex[_validator]; require(index != 0, "Validator not found"); + + // Remove top most bit + index = index & ~uint256(1 << 255); return remove(_self, index); } function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { - return getValidatorInIndexNow(_self, _index); + return getAddressFromIndexNow(_self, _index); } - function getValidatorInIndexNow(SnapshottedAddressSet storage _self, uint256 _index) + function getAddressFromIndexNow(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) @@ -98,11 +111,11 @@ library AddressSnapshotLib { return lastSnapshot.addr; } - function getAddresssIndexAt(SnapshottedAddressSet storage _self, uint256 _index, uint256 _epoch) - internal - view - returns (address) - { + function getAddressFromIndexAtEpoch( + SnapshottedAddressSet storage _self, + uint256 _index, + uint256 _epoch + ) internal view returns (address) { uint256 numCheckpoints = _self.checkpoints[_index].length; uint96 epoch = uint96(_epoch); @@ -142,7 +155,7 @@ library AddressSnapshotLib { function values(SnapshottedAddressSet storage _self) internal view returns (address[] memory) { address[] memory vals = new address[](_self.size); for (uint256 i = 0; i < _self.size; i++) { - vals[i] = getValidatorInIndexNow(_self, i); + vals[i] = getAddressFromIndexNow(_self, i); } return vals; } diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 9717f36612ef..5b7d10c9c1d0 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -10,7 +10,10 @@ import { StakingStorage, IStakingCore } from "@aztec/core/interfaces/IStaking.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 9968670b6d98..e5994af4405c 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -4,7 +4,10 @@ pragma solidity >=0.8.27; import {BlockHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol"; import {StakingStorage} from "@aztec/core/interfaces/IStaking.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import { EpochData, ValidatorSelectionStorage, diff --git a/l1-contracts/test/staking/StakingCheater.sol b/l1-contracts/test/staking/StakingCheater.sol index af2759890149..503a5cd2c03f 100644 --- a/l1-contracts/test/staking/StakingCheater.sol +++ b/l1-contracts/test/staking/StakingCheater.sol @@ -10,7 +10,10 @@ import { import {Timestamp} from "@aztec/core/libraries/TimeLib.sol"; import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; import {Slasher} from "@aztec/core/staking/Slasher.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; contract StakingCheater is IStaking { using AddressSnapshotLib for SnapshottedAddressSet; diff --git a/l1-contracts/test/staking/TimeCheater.sol b/l1-contracts/test/staking/TimeCheater.sol new file mode 100644 index 000000000000..f8b2aebc02c8 --- /dev/null +++ b/l1-contracts/test/staking/TimeCheater.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {TimeStorage, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {Vm} from "forge-std/Vm.sol"; + +contract TimeCheater { + Vm public constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + bytes32 public constant TIME_STORAGE_POSITION = keccak256("aztec.time.storage"); + + address public immutable target; + uint256 public genesisTime; + uint256 public slotDuration; + uint256 public epochDuration; + + uint256 public currentEpoch; + + constructor( + address _target, + uint256 _genesisTime, + uint256 _slotDuration, + uint256 _epochDuration + ) { + target = _target; + + genesisTime = _genesisTime; + slotDuration = _slotDuration; + epochDuration = _epochDuration; + cheat__setTimeStorage( + TimeStorage({ + genesisTime: uint128(_genesisTime), + slotDuration: uint32(_slotDuration), + epochDuration: uint32(_epochDuration) + }) + ); + } + + function cheat__setTimeStorage(TimeStorage memory _timeStorage) public { + vm.store( + target, + TIME_STORAGE_POSITION, + bytes32( + abi.encodePacked( + // Encoding order is a fun thing. + bytes8(0), + _timeStorage.epochDuration, + _timeStorage.slotDuration, + _timeStorage.genesisTime + ) + ) + ); + } + + function cheat__setEpochNow(uint256 _epoch) public { + vm.warp(genesisTime + 1 + _epoch * slotDuration * epochDuration); + currentEpoch = _epoch; + } + + function cheat__progressEpoch() public { + currentEpoch++; + vm.warp(genesisTime + 1 + currentEpoch * slotDuration * epochDuration); + } +} diff --git a/l1-contracts/test/staking/base.t.sol b/l1-contracts/test/staking/base.t.sol index 18fc8cddb105..c12ef7b568fa 100644 --- a/l1-contracts/test/staking/base.t.sol +++ b/l1-contracts/test/staking/base.t.sol @@ -4,10 +4,13 @@ pragma solidity >=0.8.27; import {TestBase} from "@test/base/Base.sol"; import {StakingCheater} from "./StakingCheater.sol"; +import {TimeCheater} from "./TimeCheater.sol"; +import {TestConstants} from "../harnesses/TestConstants.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; contract StakingBase is TestBase { StakingCheater internal staking; + TimeCheater internal timeCheater; TestERC20 internal stakingAsset; uint256 internal constant MINIMUM_STAKE = 100e18; @@ -23,6 +26,13 @@ contract StakingBase is TestBase { stakingAsset = new TestERC20("test", "TEST", address(this)); staking = new StakingCheater(stakingAsset, MINIMUM_STAKE, 1, 1); + timeCheater = new TimeCheater( + address(staking), + block.timestamp, + TestConstants.AZTEC_SLOT_DURATION, + TestConstants.AZTEC_EPOCH_DURATION + ); + SLASHER = staking.getSlasher(); } } diff --git a/l1-contracts/test/staking/slash.t.sol b/l1-contracts/test/staking/slash.t.sol index 1d3279ac7a8b..b8571ce78182 100644 --- a/l1-contracts/test/staking/slash.t.sol +++ b/l1-contracts/test/staking/slash.t.sol @@ -41,6 +41,9 @@ contract SlashTest is StakingBase { _withdrawer: WITHDRAWER, _amount: DEPOSIT_AMOUNT }); + + // Progress into the next epoch + timeCheater.cheat__progressEpoch(); _; } diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol index 4e9eab2a2529..04f39ffd19a7 100644 --- a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol +++ b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol @@ -3,8 +3,14 @@ pragma solidity >=0.8.27; import {Test} from "forge-std/Test.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {TimeLib, TimeStorage} from "@aztec/core/libraries/TimeLib.sol"; +import {TimeCheater} from "../staking/TimeCheater.sol"; + +import "forge-std/console.sol"; contract LibTest { SnapshottedAddressSet validatorSet; @@ -25,12 +31,12 @@ contract LibTest { return AddressSnapshotLib.at(validatorSet, _index); } - function getValidatorInIndexNow(uint256 _index) public view returns (address) { - return AddressSnapshotLib.getValidatorInIndexNow(validatorSet, _index); + function getAddressFromIndexNow(uint256 _index) public view returns (address) { + return AddressSnapshotLib.getAddressFromIndexNow(validatorSet, _index); } - function getAddresssIndexAt(uint256 _index, uint256 _epoch) public view returns (address) { - return AddressSnapshotLib.getAddresssIndexAt(validatorSet, _index, _epoch); + function getAddressFromIndexAtEpoch(uint256 _index, uint256 _epoch) public view returns (address) { + return AddressSnapshotLib.getAddressFromIndexAtEpoch(validatorSet, _index, _epoch); } function length() public view returns (uint256) { @@ -43,111 +49,81 @@ contract LibTest { } contract AddressSnapshotsTest is Test { - bytes32 private constant TIME_STORAGE_POSITION = keccak256("aztec.time.storage"); uint256 private constant SLOT_DURATION = 36; uint256 private constant EPOCH_DURATION = 48; uint256 private immutable GENESIS_TIME = block.timestamp; LibTest libTest; + TimeCheater timeCheater; function setUp() public { libTest = new LibTest(); - // Set the time storage - setTimeStorage( - TimeStorage({ - genesisTime: uint128(GENESIS_TIME), - slotDuration: uint32(SLOT_DURATION), - epochDuration: uint32(EPOCH_DURATION) - }) - ); - } - - function getTimeStorage() public view returns (TimeStorage memory) { - return abi.decode(bytes.concat(vm.load(address(libTest), TIME_STORAGE_POSITION)), (TimeStorage)); - } - - function setTimeStorage(TimeStorage memory _timeStorage) public { - vm.store( - address(libTest), - TIME_STORAGE_POSITION, - bytes32( - abi.encodePacked( - // Encoding order is a fun one - bytes8(0), - _timeStorage.epochDuration, - _timeStorage.slotDuration, - _timeStorage.genesisTime - ) - ) - ); - } - - function cheat__setEpochNow(uint256 _epoch) public { - vm.warp(GENESIS_TIME + 1 + _epoch * SLOT_DURATION * EPOCH_DURATION); + // Enable us to modify TimeLib functions for libTest + timeCheater = new TimeCheater(address(libTest), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); } - function test_getValidatorInIndexNow() public { + function test_getAddressFromIndexNow() public { // Empty should return 0 - assertEq(libTest.getValidatorInIndexNow(0), address(0)); + assertEq(libTest.getAddressFromIndexNow(0), address(0)); libTest.add(address(1)); libTest.add(address(2)); libTest.add(address(3)); - assertEq(libTest.getValidatorInIndexNow(0), address(1)); - assertEq(libTest.getValidatorInIndexNow(1), address(2)); - assertEq(libTest.getValidatorInIndexNow(2), address(3)); + assertEq(libTest.getAddressFromIndexNow(0), address(1)); + assertEq(libTest.getAddressFromIndexNow(1), address(2)); + assertEq(libTest.getAddressFromIndexNow(2), address(3)); } function test_getAddresssIndexAt() public { // Empty should return 0 - cheat__setEpochNow(0); - assertEq(libTest.getAddresssIndexAt(0, 0), address(0)); + timeCheater.cheat__setEpochNow(0); + assertEq(libTest.getAddressFromIndexAtEpoch(0, 0), address(0)); // Adds validator 1 to the first index in the set, at epoch 1 - cheat__setEpochNow(1); + timeCheater.cheat__setEpochNow(1); libTest.add(address(1)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 1), address(1)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(1)); // Remove index 0 from the set, in the new epoch - cheat__setEpochNow(2); + timeCheater.cheat__setEpochNow(2); libTest.remove( /* index */ 0); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 2), address(0)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 2), address(0)); // Add validator 2 to the first index in the set - cheat__setEpochNow(3); + timeCheater.cheat__setEpochNow(3); libTest.add(address(2)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 3), address(2)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 3), address(2)); // Setup and remove the last item in the set alot of times - cheat__setEpochNow(4); + timeCheater.cheat__setEpochNow(4); libTest.remove( /* index */ 0); - cheat__setEpochNow(5); + timeCheater.cheat__setEpochNow(5); libTest.add(address(3)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 5), address(3)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 5), address(3)); - cheat__setEpochNow(6); + timeCheater.cheat__setEpochNow(6); libTest.remove( /* index */ 0); - cheat__setEpochNow(7); + timeCheater.cheat__setEpochNow(7); libTest.add(address(4)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 7), address(4)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 7), address(4)); // Expect past values to be maintained - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 1), address(1)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 2), address(0)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 3), address(2)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 4), address(0)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 5), address(3)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 6), address(0)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 7), address(4)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(1)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 2), address(0)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 3), address(2)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 4), address(0)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 5), address(3)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 6), address(0)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 7), address(4)); } function test_length() public { @@ -156,16 +132,16 @@ contract AddressSnapshotsTest is Test { libTest.add(address(1)); assertEq(libTest.length(), 1); - cheat__setEpochNow(1); + timeCheater.cheat__setEpochNow(1); libTest.remove( /* index */ 0); assertEq(libTest.length(), 0); - cheat__setEpochNow(2); + timeCheater.cheat__setEpochNow(2); libTest.add(address(1)); libTest.add(address(2)); assertEq(libTest.length(), 2); - cheat__setEpochNow(3); + timeCheater.cheat__setEpochNow(3); libTest.remove( /* index */ 0); assertEq(libTest.length(), 1); } @@ -187,13 +163,46 @@ contract AddressSnapshotsTest is Test { libTest.add(address(2)); libTest.add(address(3)); - cheat__setEpochNow(1); + timeCheater.cheat__setEpochNow(1); libTest.remove(address(2)); + console.log("first remove"); + + // Order flips address[] memory values = libTest.values(); assertEq(values.length, 2); assertEq(values[0], address(1)); assertEq(values[1], address(3)); + + console.log("second remove"); + + timeCheater.cheat__setEpochNow(2); + libTest.remove(address(3)); + + console.log("third remove"); + + values = libTest.values(); + assertEq(values.length, 1); + assertEq(values[0], address(1)); + + timeCheater.cheat__setEpochNow(3); + libTest.add(address(4)); + + console.log("fourth add"); + + values = libTest.values(); + assertEq(values.length, 2); + assertEq(values[0], address(1)); + assertEq(values[1], address(4)); + + console.log("fifth remove"); + + timeCheater.cheat__setEpochNow(4); + libTest.remove(address(1)); + + values = libTest.values(); + assertEq(values.length, 1); + assertEq(values[0], address(4)); } function test_at() public { @@ -201,19 +210,26 @@ contract AddressSnapshotsTest is Test { libTest.add(address(2)); libTest.add(address(3)); + console.log("first assertiong"); + + // Index 1 is the first item in the set assertEq(libTest.at(0), address(1)); assertEq(libTest.at(1), address(2)); assertEq(libTest.at(2), address(3)); - cheat__setEpochNow(1); - libTest.remove( /* index */ 0); + console.log("removing index 1"); + + timeCheater.cheat__setEpochNow(1); + libTest.remove( /* index */ 1); + + console.log("second assertion"); // When removing and item, the index has become shuffled - assertEq(libTest.at(0), address(3)); - assertEq(libTest.at(1), address(2)); + assertEq(libTest.at(0), address(1)); + assertEq(libTest.at(1), address(3)); // Expect past values to be maintained - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 0), address(1)); - assertEq(libTest.getAddresssIndexAt( /* index */ 0, /* epoch */ 1), address(3)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(1)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(1)); } } From 50d96e31244e31b43603bf4fd3d0181d1b4547f3 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:11:12 +0000 Subject: [PATCH 05/43] fix: other tests --- l1-contracts/src/core/Rollup.sol | 5 ++- l1-contracts/src/core/libraries/TimeLib.sol | 1 + .../libraries/staking/AddressSnapshotLib.sol | 39 ++++++++++++++----- .../src/core/libraries/staking/StakingLib.sol | 3 +- .../ValidatorSelectionLib.sol | 19 +++++---- l1-contracts/test/staking/TimeCheater.sol | 10 +++++ .../test/staking/finaliseWithdraw.t.sol | 3 ++ l1-contracts/test/staking/getters.t.sol | 3 ++ .../test/staking/initiateWithdraw.t.sol | 2 + .../AddressSnapshots.t.sol | 5 +-- .../ValidatorSelection.t.sol | 11 +++++- 11 files changed, 77 insertions(+), 24 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 32d847c1a305..2579a1e43958 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -25,6 +25,7 @@ import { AddressSnapshotLib, SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; + import {EpochProofLib} from "./libraries/rollup/EpochProofLib.sol"; import {ProposeLib, ValidateHeaderArgs} from "./libraries/rollup/ProposeLib.sol"; import {ValidatorSelectionLib} from "./libraries/validator-selection/ValidatorSelectionLib.sol"; @@ -709,6 +710,8 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { * @return The current epoch number */ function getCurrentEpoch() public view override(IValidatorSelection) returns (Epoch) { - return Timestamp.wrap(block.timestamp).epochFromTimestamp(); + Epoch epoch = Timestamp.wrap(block.timestamp).epochFromTimestamp(); + + return epoch; } } diff --git a/l1-contracts/src/core/libraries/TimeLib.sol b/l1-contracts/src/core/libraries/TimeLib.sol index 04dc8bfa7780..91eb92309c0c 100644 --- a/l1-contracts/src/core/libraries/TimeLib.sol +++ b/l1-contracts/src/core/libraries/TimeLib.sol @@ -45,6 +45,7 @@ library TimeLib { function epochFromTimestamp(Timestamp _a) internal view returns (Epoch) { TimeStorage storage store = getStorage(); + return Epoch.wrap( (Timestamp.unwrap(_a) - store.genesisTime) / (store.epochDuration * store.slotDuration) ); diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index d6e1dc11de15..6e9c7269cfd6 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -4,8 +4,6 @@ pragma solidity >=0.8.27; import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; -import "forge-std/console.sol"; - // The validator index -> the validator at that point in time struct SnapshottedAddressSet { uint256 size; @@ -29,6 +27,10 @@ struct AddressSnapshot { library AddressSnapshotLib { function add(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { + if (_self.validatorToIndex[_validator] != 0) { + return false; + } + // Insert into the end of the array uint256 index = _self.size; @@ -39,9 +41,6 @@ library AddressSnapshotLib { // Include max bit to indicate that the validator is in the set - means 0 index is not 0 uint256 indexWithBit = index | (1 << 255); - console.log(" inserting validator", _validator); - console.log(" at index"); - console.logBytes32(bytes32(indexWithBit)); _self.validatorToIndex[_validator] = indexWithBit; _self.checkpoints[index].push( @@ -63,28 +62,47 @@ library AddressSnapshotLib { address lastValidator = lastSnapshot.addr; uint256 newLocationWithMask = _index | (1 << 255); + _self.validatorToIndex[lastValidator] = newLocationWithMask; // Remove the last validator from the index // If we are removing the last item, we cannot swap it with anything // so we append a new address of zero for this epoch if (lastIndex == _index) { lastSnapshot.addr = address(0); - } - require(lastSnapshot.epochNumber != epochNow, "Cannot edit an item twice within the same epoch"); + // TODO: reuse value above, only insert once + _self.validatorToIndex[lastValidator] = 0; + } lastSnapshot.epochNumber = uint96(epochNow); - _self.checkpoints[_index].push(lastSnapshot); + // Check if there's already a checkpoint for this index in the current epoch + uint256 checkpointCount = _self.checkpoints[_index].length; + if ( + checkpointCount > 0 && _self.checkpoints[_index][checkpointCount - 1].epochNumber == epochNow + ) { + // If there's already a checkpoint for this epoch, update it instead of adding a new one + _self.checkpoints[_index][checkpointCount - 1] = lastSnapshot; + } else { + // Otherwise add a new checkpoint + _self.checkpoints[_index].push(lastSnapshot); + } + + // _self.checkpoints[_index].push(lastSnapshot); + _self.size -= 1; return true; } function remove(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { uint256 index = _self.validatorToIndex[_validator]; - require(index != 0, "Validator not found"); + if (index == 0) { + return false; + } // Remove top most bit + index = index & ~uint256(1 << 255); + return remove(_self, index); } @@ -100,7 +118,7 @@ library AddressSnapshotLib { uint256 numCheckpoints = _self.checkpoints[_index].length; if (_index >= _self.size) { - return address(0); + revert("update me"); } if (numCheckpoints == 0) { @@ -114,6 +132,7 @@ library AddressSnapshotLib { function getAddressFromIndexAtEpoch( SnapshottedAddressSet storage _self, uint256 _index, + // TODO: make Epoch type - waiting to not have to change all tests uint256 _epoch ) internal view returns (address) { uint256 numCheckpoints = _self.checkpoints[_index].length; diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 5b7d10c9c1d0..11bb9cdba1d2 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -10,11 +10,11 @@ import { StakingStorage, IStakingCore } from "@aztec/core/interfaces/IStaking.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; import { AddressSnapshotLib, SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; -import {Errors} from "@aztec/core/libraries/Errors.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; @@ -81,6 +81,7 @@ library StakingLib { // gas and cost edge cases around recipient, so lets just avoid that. if (validator.status == Status.VALIDATING && validator.stake < store.minimumStake) { require(store.attesters.remove(_attester), Errors.Staking__FailedToRemove(_attester)); + validator.status = Status.LIVING; } diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index e5994af4405c..347f70a2e746 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -4,10 +4,6 @@ pragma solidity >=0.8.27; import {BlockHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol"; import {StakingStorage} from "@aztec/core/interfaces/IStaking.sol"; -import { - AddressSnapshotLib, - SnapshottedAddressSet -} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import { EpochData, ValidatorSelectionStorage, @@ -16,6 +12,10 @@ import { import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; @@ -56,7 +56,7 @@ library ValidatorSelectionLib { epoch.sampleSeed = getSampleSeed(epochNumber); epoch.nextSeed = store.lastSeed = computeNextSeed(epochNumber); - epoch.committee = sampleValidators(_stakingStore, epoch.sampleSeed); + epoch.committee = sampleValidators(_stakingStore, epochNumber, epoch.sampleSeed); } } @@ -179,11 +179,12 @@ library ValidatorSelectionLib { * * @return The validators for the given epoch */ - function sampleValidators(StakingStorage storage _stakingStore, uint256 _seed) + function sampleValidators(StakingStorage storage _stakingStore, Epoch _epoch, uint256 _seed) internal returns (address[] memory) { uint256 validatorSetSize = _stakingStore.attesters.length(); + if (validatorSetSize == 0) { return new address[](0); } @@ -201,7 +202,8 @@ library ValidatorSelectionLib { address[] memory committee = new address[](targetCommitteeSize); for (uint256 i = 0; i < targetCommitteeSize; i++) { - committee[i] = _stakingStore.attesters.at(indices[i]); + committee[i] = + _stakingStore.attesters.getAddressFromIndexAtEpoch(indices[i], Epoch.unwrap(_epoch)); } return committee; } @@ -228,7 +230,8 @@ library ValidatorSelectionLib { // Emulate a sampling of the validators uint256 sampleSeed = getSampleSeed(_epochNumber); - return sampleValidators(_stakingStore, sampleSeed); + + return sampleValidators(_stakingStore, _epochNumber, sampleSeed); } /** diff --git a/l1-contracts/test/staking/TimeCheater.sol b/l1-contracts/test/staking/TimeCheater.sol index f8b2aebc02c8..0445edd747a2 100644 --- a/l1-contracts/test/staking/TimeCheater.sol +++ b/l1-contracts/test/staking/TimeCheater.sol @@ -37,6 +37,12 @@ contract TimeCheater { } function cheat__setTimeStorage(TimeStorage memory _timeStorage) public { + console.log("cheat__setTimeStorage"); + console.log("target", target); + console.log("abi.encodePacked"); + console.log("epochDuration", _timeStorage.epochDuration); + console.log("slotDuration", _timeStorage.slotDuration); + console.log("genesisTime", _timeStorage.genesisTime); vm.store( target, TIME_STORAGE_POSITION, @@ -59,6 +65,10 @@ contract TimeCheater { function cheat__progressEpoch() public { currentEpoch++; + console.log("cheat__progressEpoch", currentEpoch); + console.log("timestamp", block.timestamp); + vm.warp(genesisTime + 1 + currentEpoch * slotDuration * epochDuration); + console.log("timestamp after", block.timestamp); } } diff --git a/l1-contracts/test/staking/finaliseWithdraw.t.sol b/l1-contracts/test/staking/finaliseWithdraw.t.sol index 4dc11259cae1..7a86cea497bc 100644 --- a/l1-contracts/test/staking/finaliseWithdraw.t.sol +++ b/l1-contracts/test/staking/finaliseWithdraw.t.sol @@ -32,6 +32,9 @@ contract FinaliseWithdrawTest is StakingBase { _amount: MINIMUM_STAKE }); + // Progress into the next epoch + timeCheater.cheat__progressEpoch(); + vm.prank(WITHDRAWER); staking.initiateWithdraw(ATTESTER, RECIPIENT); diff --git a/l1-contracts/test/staking/getters.t.sol b/l1-contracts/test/staking/getters.t.sol index ceb09a1d4b54..2bc5f7302625 100644 --- a/l1-contracts/test/staking/getters.t.sol +++ b/l1-contracts/test/staking/getters.t.sol @@ -16,6 +16,9 @@ contract GettersTest is StakingBase { _withdrawer: WITHDRAWER, _amount: MINIMUM_STAKE }); + + // Progress into the next epoch + timeCheater.cheat__progressEpoch(); } function test_getAttesterAtIndex() external view { diff --git a/l1-contracts/test/staking/initiateWithdraw.t.sol b/l1-contracts/test/staking/initiateWithdraw.t.sol index cb93ad8ed3f1..3aae513618ee 100644 --- a/l1-contracts/test/staking/initiateWithdraw.t.sol +++ b/l1-contracts/test/staking/initiateWithdraw.t.sol @@ -27,6 +27,8 @@ contract InitiateWithdrawTest is StakingBase { _amount: MINIMUM_STAKE }); + // Progress into the next epoch + timeCheater.cheat__progressEpoch(); _; } diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol index 04f39ffd19a7..1a103e9b142b 100644 --- a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol +++ b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol @@ -10,8 +10,6 @@ import { import {TimeLib, TimeStorage} from "@aztec/core/libraries/TimeLib.sol"; import {TimeCheater} from "../staking/TimeCheater.sol"; -import "forge-std/console.sol"; - contract LibTest { SnapshottedAddressSet validatorSet; @@ -66,7 +64,8 @@ contract AddressSnapshotsTest is Test { function test_getAddressFromIndexNow() public { // Empty should return 0 - assertEq(libTest.getAddressFromIndexNow(0), address(0)); + vm.expectRevert(); + libTest.getAddressFromIndexNow(0); libTest.add(address(1)); libTest.add(address(2)); diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index 0b8d77545d3f..f5a2f31d4df4 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -24,11 +24,12 @@ import {CheatDepositArgs} from "@aztec/core/interfaces/IRollup.sol"; import {Slot, Epoch, EpochLib} from "@aztec/core/libraries/TimeLib.sol"; import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; - import {SlashFactory} from "@aztec/periphery/SlashFactory.sol"; import {Slasher, IPayload} from "@aztec/core/staking/Slasher.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; import {Status, ValidatorInfo} from "@aztec/core/interfaces/IStaking.sol"; + +import {TimeCheater} from "../staking/TimeCheater.sol"; // solhint-disable comprehensive-interface /** @@ -54,6 +55,7 @@ contract ValidatorSelectionTest is DecoderBase { TestERC20 internal testERC20; RewardDistributor internal rewardDistributor; Signature internal emptySignature; + TimeCheater internal timeCheater; mapping(address attester => uint256 privateKey) internal attesterPrivateKeys; mapping(address proposer => uint256 privateKey) internal proposerPrivateKeys; mapping(address proposer => address attester) internal proposerToAttester; @@ -70,6 +72,13 @@ contract ValidatorSelectionTest is DecoderBase { uint256 slotNumber = full.block.decodedHeader.globalVariables.slotNumber; uint256 initialTime = full.block.decodedHeader.globalVariables.timestamp - slotNumber * TestConstants.AZTEC_SLOT_DURATION; + + timeCheater = new TimeCheater( + address(rollup), + initialTime, + TestConstants.AZTEC_SLOT_DURATION, + TestConstants.AZTEC_EPOCH_DURATION + ); vm.warp(initialTime); } From 642572dca8fb77d2f7b36e8b5b2aa34a918c8ca8 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:42:24 +0000 Subject: [PATCH 06/43] clean --- l1-contracts/src/core/libraries/Errors.sol | 3 + .../libraries/staking/AddressSnapshotLib.sol | 142 ++++++++++++------ .../ValidatorSelectionLib.sol | 3 +- l1-contracts/test/staking/TimeCheater.sol | 10 -- .../AddressSnapshots.t.sol | 34 +---- 5 files changed, 107 insertions(+), 85 deletions(-) diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index e4f693ba2d4b..d7f3531a8b00 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -133,4 +133,7 @@ library Errors { // FeeLib error FeeLib__InvalidFeeAssetPriceModifier(); // 0xf2fb32ad + + // AddressSnapshotLib + error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // 0xd789b71a } diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 6e9c7269cfd6..e79479228912 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -3,8 +3,14 @@ pragma solidity >=0.8.27; import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; - -// The validator index -> the validator at that point in time +import {Errors} from "@aztec/core/libraries/Errors.sol"; + +/** + * @notice Structure to store a set of addresses with their historical snapshots + * @param size The current number of addresses in the set + * @param checkpoints Mapping of index to array of address snapshots + * @param validatorToIndex Mapping of validator address to its current index in the set + */ struct SnapshottedAddressSet { uint256 size; mapping(uint256 index => AddressSnapshot[]) checkpoints; @@ -12,56 +18,73 @@ struct SnapshottedAddressSet { mapping(address validator => uint256 index) validatorToIndex; } +/** + * @notice Structure to store a single address snapshot + * @param addr The address being snapshotted + * @param epochNumber The epoch number when this snapshot was taken + */ struct AddressSnapshot { address addr; uint96 epochNumber; } -// TODO(md): think about this in the context of the enumerable set a bit more -// - - -// @note -// Do we want to be able to remove somebody from the validator set by their index? -// Or do we want to be able to remove them via their address, like the enumerable set? -// What is the most efficient way to implement such a thing +/** + * @title AddressSnapshotLib + * @notice A library for managing a set of addresses with historical snapshots + * @dev This library provides functionality similar to EnumerableSet but can track addresses across different epochs + * and allows querying the state of addresses at any point in time + */ library AddressSnapshotLib { + /** + * @notice Bit mask used to indicate presence of a validator in the set + */ + uint256 internal constant PRESENCE_BIT = 1 << 255; + + /** + * @notice Adds a validator to the set + * @param _self The storage reference to the set + * @param _validator The address of the validator to add + * @return bool True if the validator was added, false if it was already present + */ function add(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { + // Prevent against double insertion if (_self.validatorToIndex[_validator] != 0) { return false; } // Insert into the end of the array uint256 index = _self.size; - - // TODO: tidy up - uint256 _epochNumber = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp))); - - // TODO: check double insertion?? + Epoch _epochNumber = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); // Include max bit to indicate that the validator is in the set - means 0 index is not 0 - uint256 indexWithBit = index | (1 << 255); + uint256 indexWithBit = index | PRESENCE_BIT; _self.validatorToIndex[_validator] = indexWithBit; _self.checkpoints[index].push( - AddressSnapshot({addr: _validator, epochNumber: uint96(_epochNumber)}) + AddressSnapshot({addr: _validator, epochNumber: uint96(Epoch.unwrap(_epochNumber))}) ); _self.size += 1; return true; } + /** + * @notice Removes a validator from the set by index + * @param _self The storage reference to the set + * @param _index The index of the validator to remove + * @return bool True if the validator was removed, false otherwise + */ function remove(SnapshottedAddressSet storage _self, uint256 _index) internal returns (bool) { // To remove from the list, we push the last item into the index and reduce the size uint256 lastIndex = _self.size - 1; uint256 lastSnapshotLength = _self.checkpoints[lastIndex].length; - // TODO: tidy up uint256 epochNow = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp))); AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; address lastValidator = lastSnapshot.addr; - uint256 newLocationWithMask = _index | (1 << 255); + uint256 newLocationWithMask = _index | PRESENCE_BIT; _self.validatorToIndex[lastValidator] = newLocationWithMask; // Remove the last validator from the index @@ -87,12 +110,16 @@ library AddressSnapshotLib { _self.checkpoints[_index].push(lastSnapshot); } - // _self.checkpoints[_index].push(lastSnapshot); - _self.size -= 1; return true; } + /** + * @notice Removes a validator from the set by address + * @param _self The storage reference to the set + * @param _validator The address of the validator to remove + * @return bool True if the validator was removed, false if it wasn't found + */ function remove(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { uint256 index = _self.validatorToIndex[_validator]; if (index == 0) { @@ -100,17 +127,17 @@ library AddressSnapshotLib { } // Remove top most bit - - index = index & ~uint256(1 << 255); - + index = index & ~PRESENCE_BIT; return remove(_self, index); } - function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { - return getAddressFromIndexNow(_self, _index); - } - - function getAddressFromIndexNow(SnapshottedAddressSet storage _self, uint256 _index) + /** + * @notice Gets the current address at a specific index + * @param _self The storage reference to the set + * @param _index The index to query + * @return address The current address at the given index + */ + function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) @@ -118,7 +145,7 @@ library AddressSnapshotLib { uint256 numCheckpoints = _self.checkpoints[_index].length; if (_index >= _self.size) { - revert("update me"); + revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, _self.size); } if (numCheckpoints == 0) { @@ -129,14 +156,20 @@ library AddressSnapshotLib { return lastSnapshot.addr; } + /** + * @notice Gets the address at a specific index and epoch + * @param _self The storage reference to the set + * @param _index The index to query + * @param _epoch The epoch number to query + * @return address The address at the given index and epoch + */ function getAddressFromIndexAtEpoch( SnapshottedAddressSet storage _self, uint256 _index, - // TODO: make Epoch type - waiting to not have to change all tests - uint256 _epoch + Epoch _epoch ) internal view returns (address) { uint256 numCheckpoints = _self.checkpoints[_index].length; - uint96 epoch = uint96(_epoch); + uint96 epoch = uint96(Epoch.unwrap(_epoch)); if (numCheckpoints == 0) { return address(0); @@ -150,31 +183,48 @@ library AddressSnapshotLib { // Binary search uint256 lower = 0; - uint256 upper = numCheckpoints - 1; - while (upper > lower) { - uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow - AddressSnapshot memory cp = _self.checkpoints[_index][center]; - if (cp.epochNumber == epoch) { - return cp.addr; - } else if (cp.epochNumber < epoch) { - lower = center; - } else { - upper = center - 1; + + unchecked { + uint256 upper = numCheckpoints - 1; + while (upper > lower) { + uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow + AddressSnapshot memory cp = _self.checkpoints[_index][center]; + if (cp.epochNumber == epoch) { + return cp.addr; + } else if (cp.epochNumber < epoch) { + lower = center; + } else { + upper = center - 1; + } } } return _self.checkpoints[_index][lower].addr; } + /** + * @notice Gets the current size of the set + * @param _self The storage reference to the set + * @return uint256 The number of addresses in the set + */ function length(SnapshottedAddressSet storage _self) internal view returns (uint256) { return _self.size; } - // TODO: ideally we would not have this - created to match with the enumerable set + /** + * @notice Gets all current addresses in the set + * @param _self The storage reference to the set + * @return address[] Array of all current addresses in the set + */ function values(SnapshottedAddressSet storage _self) internal view returns (address[] memory) { - address[] memory vals = new address[](_self.size); - for (uint256 i = 0; i < _self.size; i++) { - vals[i] = getAddressFromIndexNow(_self, i); + uint256 size = _self.size; + address[] memory vals = new address[](size); + for (uint256 i; i < size;) { + vals[i] = at(_self, i); + + unchecked { + i++; + } } return vals; } diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 347f70a2e746..4abc98fe3a8f 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -162,7 +162,6 @@ library ValidatorSelectionLib { ValidatorSelectionStorage storage store = getStorage(); uint256 setSize = _stakingStore.attesters.length(); - // TODO: safe cast store.epochSizeSnapshots.push( ValidatorSetSizeSnapshot({ size: uint128(setSize), @@ -203,7 +202,7 @@ library ValidatorSelectionLib { address[] memory committee = new address[](targetCommitteeSize); for (uint256 i = 0; i < targetCommitteeSize; i++) { committee[i] = - _stakingStore.attesters.getAddressFromIndexAtEpoch(indices[i], Epoch.unwrap(_epoch)); + _stakingStore.attesters.getAddressFromIndexAtEpoch(indices[i], _epoch); } return committee; } diff --git a/l1-contracts/test/staking/TimeCheater.sol b/l1-contracts/test/staking/TimeCheater.sol index 0445edd747a2..f8b2aebc02c8 100644 --- a/l1-contracts/test/staking/TimeCheater.sol +++ b/l1-contracts/test/staking/TimeCheater.sol @@ -37,12 +37,6 @@ contract TimeCheater { } function cheat__setTimeStorage(TimeStorage memory _timeStorage) public { - console.log("cheat__setTimeStorage"); - console.log("target", target); - console.log("abi.encodePacked"); - console.log("epochDuration", _timeStorage.epochDuration); - console.log("slotDuration", _timeStorage.slotDuration); - console.log("genesisTime", _timeStorage.genesisTime); vm.store( target, TIME_STORAGE_POSITION, @@ -65,10 +59,6 @@ contract TimeCheater { function cheat__progressEpoch() public { currentEpoch++; - console.log("cheat__progressEpoch", currentEpoch); - console.log("timestamp", block.timestamp); - vm.warp(genesisTime + 1 + currentEpoch * slotDuration * epochDuration); - console.log("timestamp after", block.timestamp); } } diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol index 1a103e9b142b..19b50d3213be 100644 --- a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol +++ b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol @@ -7,7 +7,7 @@ import { AddressSnapshotLib, SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; -import {TimeLib, TimeStorage} from "@aztec/core/libraries/TimeLib.sol"; +import {TimeLib, TimeStorage, Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {TimeCheater} from "../staking/TimeCheater.sol"; contract LibTest { @@ -29,12 +29,8 @@ contract LibTest { return AddressSnapshotLib.at(validatorSet, _index); } - function getAddressFromIndexNow(uint256 _index) public view returns (address) { - return AddressSnapshotLib.getAddressFromIndexNow(validatorSet, _index); - } - function getAddressFromIndexAtEpoch(uint256 _index, uint256 _epoch) public view returns (address) { - return AddressSnapshotLib.getAddressFromIndexAtEpoch(validatorSet, _index, _epoch); + return AddressSnapshotLib.getAddressFromIndexAtEpoch(validatorSet, _index, Epoch.wrap(_epoch)); } function length() public view returns (uint256) { @@ -62,18 +58,18 @@ contract AddressSnapshotsTest is Test { timeCheater = new TimeCheater(address(libTest), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); } - function test_getAddressFromIndexNow() public { + function test_at_() public { // Empty should return 0 vm.expectRevert(); - libTest.getAddressFromIndexNow(0); + libTest.at(0); libTest.add(address(1)); libTest.add(address(2)); libTest.add(address(3)); - assertEq(libTest.getAddressFromIndexNow(0), address(1)); - assertEq(libTest.getAddressFromIndexNow(1), address(2)); - assertEq(libTest.getAddressFromIndexNow(2), address(3)); + assertEq(libTest.at(0), address(1)); + assertEq(libTest.at(1), address(2)); + assertEq(libTest.at(2), address(3)); } function test_getAddresssIndexAt() public { @@ -165,21 +161,15 @@ contract AddressSnapshotsTest is Test { timeCheater.cheat__setEpochNow(1); libTest.remove(address(2)); - console.log("first remove"); - // Order flips address[] memory values = libTest.values(); assertEq(values.length, 2); assertEq(values[0], address(1)); assertEq(values[1], address(3)); - console.log("second remove"); - timeCheater.cheat__setEpochNow(2); libTest.remove(address(3)); - console.log("third remove"); - values = libTest.values(); assertEq(values.length, 1); assertEq(values[0], address(1)); @@ -187,15 +177,11 @@ contract AddressSnapshotsTest is Test { timeCheater.cheat__setEpochNow(3); libTest.add(address(4)); - console.log("fourth add"); - values = libTest.values(); assertEq(values.length, 2); assertEq(values[0], address(1)); assertEq(values[1], address(4)); - console.log("fifth remove"); - timeCheater.cheat__setEpochNow(4); libTest.remove(address(1)); @@ -209,20 +195,14 @@ contract AddressSnapshotsTest is Test { libTest.add(address(2)); libTest.add(address(3)); - console.log("first assertiong"); - // Index 1 is the first item in the set assertEq(libTest.at(0), address(1)); assertEq(libTest.at(1), address(2)); assertEq(libTest.at(2), address(3)); - console.log("removing index 1"); - timeCheater.cheat__setEpochNow(1); libTest.remove( /* index */ 1); - console.log("second assertion"); - // When removing and item, the index has become shuffled assertEq(libTest.at(0), address(1)); assertEq(libTest.at(1), address(3)); From 082995990e35b095e7edfb4f08c64c9f41a96cd3 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:04:37 +0000 Subject: [PATCH 07/43] include tree --- .../libraries/staking/AddressSnapshotLib.sol | 7 +- .../ValidatorSelectionLib.sol | 3 +- .../AddressSnapshots.t.sol | 78 ++++++++++++++++--- .../address_snapshots.tree | 23 ++++++ .../test/validator-selection/snapshots.tree | 4 - 5 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 l1-contracts/test/validator-selection/address_snapshots.tree delete mode 100644 l1-contracts/test/validator-selection/snapshots.tree diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index e79479228912..03f4b4a40f93 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -34,7 +34,6 @@ struct AddressSnapshot { * @dev This library provides functionality similar to EnumerableSet but can track addresses across different epochs * and allows querying the state of addresses at any point in time */ - library AddressSnapshotLib { /** * @notice Bit mask used to indicate presence of a validator in the set @@ -137,11 +136,7 @@ library AddressSnapshotLib { * @param _index The index to query * @return address The current address at the given index */ - function at(SnapshottedAddressSet storage _self, uint256 _index) - internal - view - returns (address) - { + function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { uint256 numCheckpoints = _self.checkpoints[_index].length; if (_index >= _self.size) { diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 4abc98fe3a8f..e3286d1904b3 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -201,8 +201,7 @@ library ValidatorSelectionLib { address[] memory committee = new address[](targetCommitteeSize); for (uint256 i = 0; i < targetCommitteeSize; i++) { - committee[i] = - _stakingStore.attesters.getAddressFromIndexAtEpoch(indices[i], _epoch); + committee[i] = _stakingStore.attesters.getAddressFromIndexAtEpoch(indices[i], _epoch); } return committee; } diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol index 19b50d3213be..baed610dda71 100644 --- a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol +++ b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol @@ -13,16 +13,16 @@ import {TimeCheater} from "../staking/TimeCheater.sol"; contract LibTest { SnapshottedAddressSet validatorSet; - function add(address _validator) public { - AddressSnapshotLib.add(validatorSet, _validator); + function add(address _validator) public returns (bool) { + return AddressSnapshotLib.add(validatorSet, _validator); } - function remove(uint256 _index) public { - AddressSnapshotLib.remove(validatorSet, _index); + function remove(uint256 _index) public returns (bool) { + return AddressSnapshotLib.remove(validatorSet, _index); } - function remove(address _validator) public { - AddressSnapshotLib.remove(validatorSet, _validator); + function remove(address _validator) public returns (bool) { + return AddressSnapshotLib.remove(validatorSet, _validator); } function at(uint256 _index) public view returns (address) { @@ -73,10 +73,6 @@ contract AddressSnapshotsTest is Test { } function test_getAddresssIndexAt() public { - // Empty should return 0 - timeCheater.cheat__setEpochNow(0); - assertEq(libTest.getAddressFromIndexAtEpoch(0, 0), address(0)); - // Adds validator 1 to the first index in the set, at epoch 1 timeCheater.cheat__setEpochNow(1); @@ -211,4 +207,66 @@ contract AddressSnapshotsTest is Test { assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(1)); assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(1)); } + + function test_WhenNoValidatorsAreRegistered() external { + vm.expectRevert(); + libTest.at(0); + + // it causes getAddressFromIndexAtEpoch to return revert + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(0)); + } + + modifier whenAddingAValidator() { + libTest.add(address(1)); + _; + } + + function test_WhenAlreadyAdded() external whenAddingAValidator { + // it returns false + assertFalse(libTest.add(address(1))); + } + + function test_WhenNotPreviouslyAdded() external whenAddingAValidator { + // it returns true + assertTrue(libTest.add(address(2))); + // it increases the length + assertEq(libTest.length(), 2); + + // it creates a checkpoint + timeCheater.cheat__setEpochNow(1); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(1)); + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 1, /* epoch */ 0), address(2)); + } + + function test_cannotRemoveAddressNotInTheSet() external { + // it returns false + assertFalse(libTest.remove(address(1))); + } + + function test_WhenValidatorIsInTheSet() external { + // it returns true + libTest.add(address(1)); + timeCheater.cheat__setEpochNow(1); + + assertTrue(libTest.remove(address(1))); + // it decreases the length + assertEq(libTest.length(), 0); + // it updates the snapshot for that index + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(0)); + + // it maintains historical values correctly + assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(1)); + } + + function test_WhenIndexIsOutOfBounds() external { + // it reverts with IndexOutOfBounds + vm.expectRevert(); + libTest.at(1); + } + + function test_WhenIndexIsValid() external { + // it returns the current validator at that index + libTest.add(address(1)); + assertEq(libTest.at(0), address(1)); + } } diff --git a/l1-contracts/test/validator-selection/address_snapshots.tree b/l1-contracts/test/validator-selection/address_snapshots.tree new file mode 100644 index 000000000000..92b59580a089 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots.tree @@ -0,0 +1,23 @@ +AddressSnapshotLibTest +├── when no validators are registered +│ ├── it causes at to revert +│ └── it causes getAddressFromIndexAtEpoch to return revert +├── when adding a validator +│ ├── when already added +│ │ └── it returns false +│ └── when not previously added +│ ├── it returns true +│ ├── it increases the length +│ └── it creates a checkpoint +├── when removing a validator +│ ├── when address not in the set +│ │ └── it returns false +│ └── when validator is in the set +│ ├── it returns true +│ ├── it decreases the length +│ ├── it updates the snapshot for that index +│ └── it maintains historical values correctly +├── when index is out of bounds +│ └── it reverts with IndexOutOfBounds +└── when index is valid + └── it returns the current validator at that index diff --git a/l1-contracts/test/validator-selection/snapshots.tree b/l1-contracts/test/validator-selection/snapshots.tree deleted file mode 100644 index 80d64c725bba..000000000000 --- a/l1-contracts/test/validator-selection/snapshots.tree +++ /dev/null @@ -1,4 +0,0 @@ -ValidatorSnapshotTest -└── When the snapshots are empty - ├── It should allow additions and removals - └── It should not allow removals of non-existent validators From 6f72dd276222967f1718664316c6d2ffee90daa6 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:05:14 +0000 Subject: [PATCH 08/43] chore: remove redundant test --- .../validator-selection/AddressSnapshots.t.sol | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol index baed610dda71..e91692823806 100644 --- a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol +++ b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol @@ -58,20 +58,6 @@ contract AddressSnapshotsTest is Test { timeCheater = new TimeCheater(address(libTest), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); } - function test_at_() public { - // Empty should return 0 - vm.expectRevert(); - libTest.at(0); - - libTest.add(address(1)); - libTest.add(address(2)); - libTest.add(address(3)); - - assertEq(libTest.at(0), address(1)); - assertEq(libTest.at(1), address(2)); - assertEq(libTest.at(2), address(3)); - } - function test_getAddresssIndexAt() public { // Adds validator 1 to the first index in the set, at epoch 1 timeCheater.cheat__setEpochNow(1); From 30bbf8371005388a640e1af863a2917471a73c9c Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:26:44 +0000 Subject: [PATCH 09/43] clean --- l1-contracts/src/core/Rollup.sol | 4 +--- .../core/interfaces/IValidatorSelection.sol | 12 ++--------- .../src/core/libraries/staking/StakingLib.sol | 1 - .../ValidatorSelectionLib.sol | 20 ++----------------- 4 files changed, 5 insertions(+), 32 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 2579a1e43958..9db3ab481372 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -710,8 +710,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { * @return The current epoch number */ function getCurrentEpoch() public view override(IValidatorSelection) returns (Epoch) { - Epoch epoch = Timestamp.wrap(block.timestamp).epochFromTimestamp(); - - return epoch; + return Timestamp.wrap(block.timestamp).epochFromTimestamp(); } } diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index dc54ec0955e1..38a23b7d415a 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -11,25 +11,17 @@ import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; * @param nextSeed - The seed used to influence the NEXT epoch */ struct EpochData { + uint256 validatorSetSize; + // TODO: remove in favor of commitment to comittee address[] committee; uint256 sampleSeed; uint256 nextSeed; } -// TODO(md) -struct ValidatorSetSizeSnapshot { - uint128 size; - uint96 epochNumber; -} struct ValidatorSelectionStorage { // A mapping to snapshots of the validator set mapping(Epoch => EpochData) epochs; - // A mapping to snapshots of the validator set size - - // TODO(md): this can just be inside the epoch data???? - // Only keeping this here so it looks the same as the issue for now - ValidatorSetSizeSnapshot[] epochSizeSnapshots; // The last stored randao value, same value as `seed` in the last inserted epoch uint256 lastSeed; uint256 targetCommitteeSize; diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 11bb9cdba1d2..efa517fc789f 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -130,7 +130,6 @@ library StakingLib { Errors.Staking__NothingToExit(_attester) ); if (validator.status == Status.VALIDATING) { - // Need to remove them based on the index, not the address!!!!! require(store.attesters.remove(_attester), Errors.Staking__FailedToRemove(_attester)); } diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index e3286d1904b3..847052632962 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -6,8 +6,7 @@ import {BlockHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol"; import {StakingStorage} from "@aztec/core/interfaces/IStaking.sol"; import { EpochData, - ValidatorSelectionStorage, - ValidatorSetSizeSnapshot + ValidatorSelectionStorage } from "@aztec/core/interfaces/IValidatorSelection.sol"; import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; @@ -51,8 +50,7 @@ library ValidatorSelectionLib { EpochData storage epoch = store.epochs[epochNumber]; if (epoch.sampleSeed == 0) { - // TODO: can be combined into the current epoch store - checkpointValidatorSetSize(_stakingStore, epochNumber); + epoch.validatorSetSize = _stakingStore.attesters.length(); epoch.sampleSeed = getSampleSeed(epochNumber); epoch.nextSeed = store.lastSeed = computeNextSeed(epochNumber); @@ -156,20 +154,6 @@ library ValidatorSelectionLib { return _stakingStore.info[attester].proposer; } - function checkpointValidatorSetSize(StakingStorage storage _stakingStore, Epoch _epochNumber) - internal - { - ValidatorSelectionStorage storage store = getStorage(); - uint256 setSize = _stakingStore.attesters.length(); - - store.epochSizeSnapshots.push( - ValidatorSetSizeSnapshot({ - size: uint128(setSize), - epochNumber: uint96(Epoch.unwrap(_epochNumber)) - }) - ); - } - /** * @notice Samples a validator set for a specific epoch * From a9c4263561b65b8d2bbfcc361c2e48b238d0879c Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:33:31 +0000 Subject: [PATCH 10/43] fix: post rebase --- l1-contracts/test/staking_asset_handler/base.t.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/l1-contracts/test/staking_asset_handler/base.t.sol b/l1-contracts/test/staking_asset_handler/base.t.sol index 80d8328c3277..b31996cd2693 100644 --- a/l1-contracts/test/staking_asset_handler/base.t.sol +++ b/l1-contracts/test/staking_asset_handler/base.t.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.27; import {TestBase} from "@test/base/Base.sol"; import {StakingCheater} from "./../staking/StakingCheater.sol"; +import {TimeCheater} from "./../staking/TimeCheater.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {StakingAssetHandler} from "@aztec/mock/StakingAssetHandler.sol"; @@ -12,6 +13,7 @@ import {StakingAssetHandler} from "@aztec/mock/StakingAssetHandler.sol"; contract StakingAssetHandlerBase is TestBase { StakingCheater internal staking; TestERC20 internal stakingAsset; + TimeCheater internal timeCheater; StakingAssetHandler internal stakingAssetHandler; uint256 internal constant MINIMUM_STAKE = 100e18; @@ -24,5 +26,6 @@ contract StakingAssetHandlerBase is TestBase { function setUp() public virtual { stakingAsset = new TestERC20("test", "TEST", address(this)); staking = new StakingCheater(stakingAsset, MINIMUM_STAKE, 1, 1); + timeCheater = new TimeCheater(address(staking), block.timestamp, 1, 1); } } From 93c6939946d172f6f93e48e0a4184d566e722a5e Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:39:36 +0000 Subject: [PATCH 11/43] chore: embed time cheater into staking cheater + prev linting rules --- .../core/interfaces/IValidatorSelection.sol | 1 - .../libraries/staking/AddressSnapshotLib.sol | 2 +- .../ValidatorSelectionLib.sol | 3 +- l1-contracts/src/mock/StakingAssetHandler.sol | 32 ++++++++++++++----- l1-contracts/test/staking/StakingCheater.sol | 15 +++++++++ l1-contracts/test/staking/base.t.sol | 9 ------ .../test/staking/finaliseWithdraw.t.sol | 2 +- l1-contracts/test/staking/getters.t.sol | 2 +- .../test/staking/initiateWithdraw.t.sol | 2 +- l1-contracts/test/staking/slash.t.sol | 2 +- .../test/staking_asset_handler/base.t.sol | 3 -- .../staking_asset_handler/setRollup.t.sol | 1 + 12 files changed, 46 insertions(+), 28 deletions(-) diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index 38a23b7d415a..960975531314 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -18,7 +18,6 @@ struct EpochData { uint256 nextSeed; } - struct ValidatorSelectionStorage { // A mapping to snapshots of the validator set mapping(Epoch => EpochData) epochs; diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 03f4b4a40f93..b203acf2ba7b 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -2,8 +2,8 @@ // Copyright 2025 Aztec Labs. pragma solidity >=0.8.27; -import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; /** * @notice Structure to store a set of addresses with their historical snapshots diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 847052632962..d14586f4ad54 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -5,8 +5,7 @@ pragma solidity >=0.8.27; import {BlockHeaderValidationFlags} from "@aztec/core/interfaces/IRollup.sol"; import {StakingStorage} from "@aztec/core/interfaces/IStaking.sol"; import { - EpochData, - ValidatorSelectionStorage + EpochData, ValidatorSelectionStorage } from "@aztec/core/interfaces/IValidatorSelection.sol"; import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; diff --git a/l1-contracts/src/mock/StakingAssetHandler.sol b/l1-contracts/src/mock/StakingAssetHandler.sol index 70b1919882d4..764c24465fab 100644 --- a/l1-contracts/src/mock/StakingAssetHandler.sol +++ b/l1-contracts/src/mock/StakingAssetHandler.sol @@ -108,7 +108,11 @@ contract StakingAssetHandler is IStakingAssetHandler, Ownable { emit AddValidatorPermissionGranted(_owner); } - function addValidator(address _attester, address _proposer) external override onlyCanAddValidator { + function addValidator(address _attester, address _proposer) + external + override(IStakingAssetHandler) + onlyCanAddValidator + { bool needsToMint = STAKING_ASSET.balanceOf(address(this)) < depositAmount; bool canMint = block.timestamp - lastMintTimestamp >= mintInterval; @@ -124,38 +128,50 @@ contract StakingAssetHandler is IStakingAssetHandler, Ownable { emit ValidatorAdded(_attester, _proposer, withdrawer); } - function setRollup(address _rollup) external override onlyOwner { + function setRollup(address _rollup) external override(IStakingAssetHandler) onlyOwner { rollup = IStaking(_rollup); emit RollupUpdated(_rollup); } - function setDepositAmount(uint256 _amount) external override onlyOwner { + function setDepositAmount(uint256 _amount) external override(IStakingAssetHandler) onlyOwner { depositAmount = _amount; emit DepositAmountUpdated(_amount); } - function setMintInterval(uint256 _interval) external override onlyOwner { + function setMintInterval(uint256 _interval) external override(IStakingAssetHandler) onlyOwner { mintInterval = _interval; emit IntervalUpdated(_interval); } - function setDepositsPerMint(uint256 _depositsPerMint) external override onlyOwner { + function setDepositsPerMint(uint256 _depositsPerMint) + external + override(IStakingAssetHandler) + onlyOwner + { require(_depositsPerMint > 0, CannotMintZeroAmount()); depositsPerMint = _depositsPerMint; emit DepositsPerMintUpdated(_depositsPerMint); } - function setWithdrawer(address _withdrawer) external override onlyOwner { + function setWithdrawer(address _withdrawer) external override(IStakingAssetHandler) onlyOwner { withdrawer = _withdrawer; emit WithdrawerUpdated(_withdrawer); } - function grantAddValidatorPermission(address _address) external override onlyOwner { + function grantAddValidatorPermission(address _address) + external + override(IStakingAssetHandler) + onlyOwner + { canAddValidator[_address] = true; emit AddValidatorPermissionGranted(_address); } - function revokeAddValidatorPermission(address _address) external override onlyOwner { + function revokeAddValidatorPermission(address _address) + external + override(IStakingAssetHandler) + onlyOwner + { canAddValidator[_address] = false; emit AddValidatorPermissionRevoked(_address); } diff --git a/l1-contracts/test/staking/StakingCheater.sol b/l1-contracts/test/staking/StakingCheater.sol index 503a5cd2c03f..36d779a96b7a 100644 --- a/l1-contracts/test/staking/StakingCheater.sol +++ b/l1-contracts/test/staking/StakingCheater.sol @@ -6,6 +6,7 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import { IStaking, ValidatorInfo, Exit, OperatorInfo, Status } from "@aztec/core/interfaces/IStaking.sol"; +import {TimeCheater} from "./TimeCheater.sol"; import {Timestamp} from "@aztec/core/libraries/TimeLib.sol"; import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; @@ -15,15 +16,25 @@ import { SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {TestConstants} from "@test/harnesses/TestConstants.sol"; + contract StakingCheater is IStaking { using AddressSnapshotLib for SnapshottedAddressSet; + TimeCheater internal timeCheater; + constructor( IERC20 _stakingAsset, uint256 _minimumStake, uint256 _slashingQuorum, uint256 _roundSize ) { + timeCheater = new TimeCheater( + address(this), + block.timestamp, + TestConstants.AZTEC_SLOT_DURATION, + TestConstants.AZTEC_EPOCH_DURATION + ); Timestamp exitDelay = Timestamp.wrap(60 * 60 * 24); Slasher slasher = new Slasher(_slashingQuorum, _roundSize); StakingLib.initialize(_stakingAsset, _minimumStake, exitDelay, address(slasher)); @@ -108,4 +119,8 @@ contract StakingCheater is IStaking { function cheat__RemoveAttester(address _attester) external { StakingLib.getStorage().attesters.remove(_attester); } + + function cheat__progressEpoch() external { + timeCheater.cheat__progressEpoch(); + } } diff --git a/l1-contracts/test/staking/base.t.sol b/l1-contracts/test/staking/base.t.sol index c12ef7b568fa..ebfcdd11198e 100644 --- a/l1-contracts/test/staking/base.t.sol +++ b/l1-contracts/test/staking/base.t.sol @@ -4,13 +4,11 @@ pragma solidity >=0.8.27; import {TestBase} from "@test/base/Base.sol"; import {StakingCheater} from "./StakingCheater.sol"; -import {TimeCheater} from "./TimeCheater.sol"; import {TestConstants} from "../harnesses/TestConstants.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; contract StakingBase is TestBase { StakingCheater internal staking; - TimeCheater internal timeCheater; TestERC20 internal stakingAsset; uint256 internal constant MINIMUM_STAKE = 100e18; @@ -26,13 +24,6 @@ contract StakingBase is TestBase { stakingAsset = new TestERC20("test", "TEST", address(this)); staking = new StakingCheater(stakingAsset, MINIMUM_STAKE, 1, 1); - timeCheater = new TimeCheater( - address(staking), - block.timestamp, - TestConstants.AZTEC_SLOT_DURATION, - TestConstants.AZTEC_EPOCH_DURATION - ); - SLASHER = staking.getSlasher(); } } diff --git a/l1-contracts/test/staking/finaliseWithdraw.t.sol b/l1-contracts/test/staking/finaliseWithdraw.t.sol index 7a86cea497bc..b96b7c5a2924 100644 --- a/l1-contracts/test/staking/finaliseWithdraw.t.sol +++ b/l1-contracts/test/staking/finaliseWithdraw.t.sol @@ -33,7 +33,7 @@ contract FinaliseWithdrawTest is StakingBase { }); // Progress into the next epoch - timeCheater.cheat__progressEpoch(); + staking.cheat__progressEpoch(); vm.prank(WITHDRAWER); staking.initiateWithdraw(ATTESTER, RECIPIENT); diff --git a/l1-contracts/test/staking/getters.t.sol b/l1-contracts/test/staking/getters.t.sol index 2bc5f7302625..10787e6051c4 100644 --- a/l1-contracts/test/staking/getters.t.sol +++ b/l1-contracts/test/staking/getters.t.sol @@ -18,7 +18,7 @@ contract GettersTest is StakingBase { }); // Progress into the next epoch - timeCheater.cheat__progressEpoch(); + staking.cheat__progressEpoch(); } function test_getAttesterAtIndex() external view { diff --git a/l1-contracts/test/staking/initiateWithdraw.t.sol b/l1-contracts/test/staking/initiateWithdraw.t.sol index 3aae513618ee..4c0e342b27b5 100644 --- a/l1-contracts/test/staking/initiateWithdraw.t.sol +++ b/l1-contracts/test/staking/initiateWithdraw.t.sol @@ -28,7 +28,7 @@ contract InitiateWithdrawTest is StakingBase { }); // Progress into the next epoch - timeCheater.cheat__progressEpoch(); + staking.cheat__progressEpoch(); _; } diff --git a/l1-contracts/test/staking/slash.t.sol b/l1-contracts/test/staking/slash.t.sol index b8571ce78182..d35ac672b96a 100644 --- a/l1-contracts/test/staking/slash.t.sol +++ b/l1-contracts/test/staking/slash.t.sol @@ -43,7 +43,7 @@ contract SlashTest is StakingBase { }); // Progress into the next epoch - timeCheater.cheat__progressEpoch(); + staking.cheat__progressEpoch(); _; } diff --git a/l1-contracts/test/staking_asset_handler/base.t.sol b/l1-contracts/test/staking_asset_handler/base.t.sol index b31996cd2693..80d8328c3277 100644 --- a/l1-contracts/test/staking_asset_handler/base.t.sol +++ b/l1-contracts/test/staking_asset_handler/base.t.sol @@ -4,7 +4,6 @@ pragma solidity >=0.8.27; import {TestBase} from "@test/base/Base.sol"; import {StakingCheater} from "./../staking/StakingCheater.sol"; -import {TimeCheater} from "./../staking/TimeCheater.sol"; import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {StakingAssetHandler} from "@aztec/mock/StakingAssetHandler.sol"; @@ -13,7 +12,6 @@ import {StakingAssetHandler} from "@aztec/mock/StakingAssetHandler.sol"; contract StakingAssetHandlerBase is TestBase { StakingCheater internal staking; TestERC20 internal stakingAsset; - TimeCheater internal timeCheater; StakingAssetHandler internal stakingAssetHandler; uint256 internal constant MINIMUM_STAKE = 100e18; @@ -26,6 +24,5 @@ contract StakingAssetHandlerBase is TestBase { function setUp() public virtual { stakingAsset = new TestERC20("test", "TEST", address(this)); staking = new StakingCheater(stakingAsset, MINIMUM_STAKE, 1, 1); - timeCheater = new TimeCheater(address(staking), block.timestamp, 1, 1); } } diff --git a/l1-contracts/test/staking_asset_handler/setRollup.t.sol b/l1-contracts/test/staking_asset_handler/setRollup.t.sol index deefc226624e..94af0786c62f 100644 --- a/l1-contracts/test/staking_asset_handler/setRollup.t.sol +++ b/l1-contracts/test/staking_asset_handler/setRollup.t.sol @@ -51,6 +51,7 @@ contract SetRollupTest is StakingAssetHandlerBase { function test_WhenValidatorIsAddedAfterSettingTheRollup() external { // it deposits into the new rollup StakingCheater newStaking = new StakingCheater(stakingAsset, MINIMUM_STAKE, 1, 1); + assertNotEq(address(newStaking), address(staking)); stakingAssetHandler.setRollup(address(newStaking)); From 76b0454ae71827566e8aa0811727f9ca5bba57a8 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:42:51 +0000 Subject: [PATCH 12/43] include: benchmarks, it gets worse before it gets better --- l1-contracts/gas_benchmark.md | 22 ++-- l1-contracts/gas_report.md | 115 +++++++++--------- l1-contracts/src/core/libraries/TimeLib.sol | 10 +- .../libraries/staking/AddressSnapshotLib.sol | 20 +-- 4 files changed, 88 insertions(+), 79 deletions(-) diff --git a/l1-contracts/gas_benchmark.md b/l1-contracts/gas_benchmark.md index 0927d04aa497..eed91825a6e8 100644 --- a/l1-contracts/gas_benchmark.md +++ b/l1-contracts/gas_benchmark.md @@ -1,26 +1,26 @@ | src/core/Rollup.sol:Rollup contract | | | | | | |-------------------------------------|-----------------|----------|----------|----------|---------| | Deployment Cost | Deployment Size | | | | | -| 7944840 | 38653 | | | | | +| 8154934 | 39929 | | | | | | Function Name | min | avg | median | max | # calls | -| cheat__InitialiseValidatorSet | 13524835 | 13524835 | 13524835 | 13524835 | 1 | +| cheat__InitialiseValidatorSet | 15884661 | 15884661 | 15884661 | 15884661 | 1 | | getBlock | 1230 | 1230 | 1230 | 1230 | 12 | | getBurnAddress | 369 | 369 | 369 | 369 | 1 | -| getCurrentEpoch | 1017 | 1017 | 1017 | 1017 | 397 | -| getCurrentProposer | 139908 | 143780 | 140137 | 263958 | 200 | -| getCurrentSlot | 823 | 833 | 823 | 4823 | 397 | -| getEpochCommittee | 135768 | 139483 | 135780 | 259358 | 100 | -| getEpochForBlock | 1016 | 1016 | 1016 | 1016 | 196 | +| getCurrentEpoch | 979 | 979 | 979 | 979 | 397 | +| getCurrentProposer | 135981 | 143548 | 136210 | 383219 | 200 | +| getCurrentSlot | 735 | 740 | 735 | 2735 | 397 | +| getEpochCommittee | 135880 | 143291 | 135892 | 382675 | 100 | +| getEpochForBlock | 1065 | 1065 | 1065 | 1065 | 196 | | getFeeHeader | 1457 | 1457 | 1457 | 1457 | 95 | -| getManaBaseFeeAt | 20442 | 26863 | 27182 | 34313 | 195 | +| getManaBaseFeeAt | 20381 | 26792 | 27121 | 32242 | 195 | | getPendingBlockNumber | 507 | 511 | 507 | 2507 | 401 | | getProvenBlockNumber | 490 | 490 | 490 | 490 | 3 | -| getTimestampForSlot | 887 | 887 | 887 | 887 | 195 | +| getTimestampForSlot | 802 | 802 | 802 | 802 | 195 | | setProvingCostPerMana | 25915 | 25942 | 25915 | 28715 | 101 | -| submitEpochRootProof | 771062 | 783900 | 771086 | 809552 | 3 | +| submitEpochRootProof | 767179 | 780017 | 767203 | 805669 | 3 | | src/periphery/Forwarder.sol:Forwarder contract | | | | | | |------------------------------------------------|-----------------|--------|--------|---------|---------| | Deployment Cost | Deployment Size | | | | | | 358690 | 1553 | | | | | | Function Name | min | avg | median | max | # calls | -| forward | 634788 | 685012 | 645781 | 1916159 | 100 | +| forward | 630841 | 685437 | 641834 | 2057987 | 100 | diff --git a/l1-contracts/gas_report.md b/l1-contracts/gas_report.md index 8d5d1a4ef5d4..20d4518de606 100644 --- a/l1-contracts/gas_report.md +++ b/l1-contracts/gas_report.md @@ -4,52 +4,55 @@ | 589795 | 2886 | | | | | | Function Name | min | avg | median | max | # calls | | L2_TOKEN_ADDRESS | 194 | 194 | 194 | 194 | 256 | -| UNDERLYING | 270 | 270 | 270 | 270 | 3080 | -| canonicalRollup | 1016 | 3632 | 5516 | 5516 | 5574 | +| UNDERLYING | 270 | 270 | 270 | 270 | 3056 | +| canonicalRollup | 1016 | 3619 | 5516 | 5516 | 5535 | | depositToAztecPublic | 42812 | 127386 | 128025 | 128025 | 258 | | distributeFees | 27333 | 56798 | 57006 | 57006 | 258 | | initialize | 49029 | 49029 | 49029 | 49029 | 1566 | | src/core/Rollup.sol:Rollup contract | | | | | | |-------------------------------------|-----------------|---------|---------|----------|---------| | Deployment Cost | Deployment Size | | | | | -| 7925568 | 38750 | | | | | +| 8134336 | 39726 | | | | | | Function Name | min | avg | median | max | # calls | | archive | 605 | 605 | 605 | 605 | 2475 | -| cheat__InitialiseValidatorSet | 752109 | 7052330 | 752133 | 13524799 | 519 | -| claimProverRewards | 31794 | 53315 | 34239 | 93914 | 3 | +| cheat__InitialiseValidatorSet | 865916 | 5835159 | 865940 | 15870313 | 776 | +| claimProverRewards | 27755 | 49276 | 30200 | 89875 | 3 | | claimSequencerRewards | 57174 | 57174 | 57174 | 57174 | 1 | -| deposit | 169778 | 329145 | 342652 | 342652 | 256 | -| getAttesters | 1970 | 26343 | 26629 | 26629 | 259 | -| getBlock | 1230 | 1230 | 1230 | 1230 | 910 | +| deposit | 188796 | 376816 | 395377 | 395377 | 256 | +| getAttesters | 5495 | 61487 | 5495 | 118137 | 515 | +| getBlock | 1230 | 1230 | 1230 | 1230 | 883 | +| getBurnAddress | 369 | 369 | 369 | 369 | 1 | | getCollectiveProverRewardsForEpoch | 636 | 1636 | 1636 | 2636 | 4 | -| getCurrentEpoch | 1017 | 1017 | 1017 | 1017 | 1032 | -| getCurrentEpochCommittee | 42004 | 42004 | 42004 | 42004 | 1 | -| getCurrentProposer | 44246 | 117406 | 52217 | 263958 | 795 | -| getCurrentSlot | 823 | 1442 | 823 | 4823 | 142 | -| getEpochCommittee | 2010 | 14068 | 14218 | 14218 | 520 | -| getEpochDuration | 439 | 439 | 439 | 439 | 256 | +| getCurrentEpoch | 979 | 979 | 979 | 979 | 1941 | +| getCurrentEpochCommittee | 38078 | 38078 | 38078 | 38078 | 1 | +| getCurrentProposer | 40319 | 152953 | 59614 | 379571 | 995 | +| getCurrentSlot | 735 | 820 | 735 | 2735 | 539 | +| getEpochCommittee | 35672 | 94395 | 135892 | 379027 | 1130 | +| getEpochDuration | 422 | 422 | 422 | 422 | 512 | +| getEpochForBlock | 1065 | 1065 | 1065 | 1065 | 196 | | getFeeAssetPerEth | 1440 | 1440 | 1440 | 1440 | 1 | +| getFeeHeader | 1457 | 1457 | 1457 | 1457 | 95 | | getHasSubmitted | 942 | 1192 | 942 | 2942 | 8 | -| getInbox | 476 | 581 | 476 | 2476 | 4926 | +| getInbox | 476 | 576 | 476 | 2476 | 5182 | | getInfo | 1527 | 1527 | 1527 | 1527 | 16 | -| getManaBaseFeeAt | 7834 | 15737 | 16504 | 16514 | 2333 | -| getOutbox | 496 | 873 | 496 | 2496 | 5434 | -| getPendingBlockNumber | 507 | 507 | 507 | 507 | 1546 | +| getManaBaseFeeAt | 7834 | 16528 | 16433 | 32242 | 2528 | +| getOutbox | 496 | 856 | 496 | 2496 | 5690 | +| getPendingBlockNumber | 507 | 508 | 507 | 2507 | 1947 | | getProofSubmissionWindow | 404 | 404 | 404 | 404 | 4 | -| getProvenBlockNumber | 490 | 762 | 490 | 2490 | 7553 | +| getProvenBlockNumber | 490 | 762 | 490 | 2490 | 7556 | | getProvingCostPerManaInEth | 429 | 429 | 429 | 429 | 1 | | getProvingCostPerManaInFeeAsset | 4164 | 4164 | 4164 | 4164 | 1 | | getSequencerRewards | 671 | 1071 | 671 | 2671 | 5 | -| getSlasher | 496 | 496 | 496 | 496 | 518 | -| getSlotDuration | 421 | 421 | 421 | 421 | 256 | +| getSlasher | 496 | 496 | 496 | 496 | 774 | +| getSlotDuration | 444 | 444 | 444 | 444 | 512 | | getSpecificProverRewardsForEpoch | 822 | 2130 | 1634 | 3634 | 5 | -| getTargetCommitteeSize | 462 | 462 | 462 | 462 | 768 | -| getTimestampForSlot | 887 | 888 | 887 | 4887 | 2462 | -| propose | 129177 | 362402 | 363530 | 572838 | 2601 | -| prune | 25731 | 36205 | 37466 | 41951 | 6 | -| setProvingCostPerMana | 28691 | 28691 | 28691 | 28691 | 2 | -| setupEpoch | 208152 | 1372793 | 1400090 | 1400090 | 262 | -| submitEpochRootProof | 64866 | 421805 | 420725 | 448480 | 909 | +| getTargetCommitteeSize | 462 | 462 | 462 | 462 | 1024 | +| getTimestampForSlot | 802 | 802 | 802 | 2802 | 2657 | +| propose | 125155 | 359657 | 361644 | 568816 | 2601 | +| prune | 25731 | 32880 | 33476 | 37961 | 6 | +| setProvingCostPerMana | 25915 | 25996 | 25915 | 28715 | 103 | +| setupEpoch | 237850 | 1508229 | 1538004 | 1538004 | 262 | +| submitEpochRootProof | 61003 | 419386 | 416842 | 805669 | 873 | | src/core/messagebridge/Inbox.sol:Inbox contract | | | | | | |-------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -64,7 +67,7 @@ | Deployment Cost | Deployment Size | | | | | | 586673 | 2646 | | | | | | Function Name | min | avg | median | max | # calls | -| consume | 28894 | 72144 | 73138 | 73400 | 4707 | +| consume | 28894 | 71718 | 73117 | 73400 | 3839 | | getRootData | 940 | 1343 | 1149 | 3217 | 2733 | | hasMessageBeenConsumedAtBlockAndIndex | 591 | 2583 | 2591 | 2591 | 259 | | insert | 22188 | 57527 | 68264 | 68264 | 1099 | @@ -74,18 +77,18 @@ | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | | PROPOSER | 182 | 182 | 182 | 182 | 2 | -| slash | 125484 | 125484 | 125484 | 125484 | 1 | +| slash | 192070 | 192070 | 192070 | 192070 | 1 | | src/core/staking/SlashingProposer.sol:SlashingProposer contract | | | | | | |-----------------------------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | -| executeProposal | 140075 | 140075 | 140075 | 140075 | 1 | -| vote | 64017 | 75384 | 64017 | 120852 | 10 | +| executeProposal | 152635 | 152635 | 152635 | 152635 | 1 | +| vote | 60002 | 71369 | 60002 | 116837 | 10 | | src/governance/CoinIssuer.sol:CoinIssuer contract | | | | | | |---------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 326385 | 1465 | | | | | +| 326433 | 1465 | | | | | | Function Name | min | avg | median | max | # calls | | RATE | 239 | 239 | 239 | 239 | 768 | | mint | 23901 | 43850 | 26637 | 81131 | 768 | @@ -96,25 +99,25 @@ | Deployment Cost | Deployment Size | | | | | | 2332350 | 10841 | | | | | | Function Name | min | avg | median | max | # calls | -| deposit | 27965 | 171786 | 186596 | 188519 | 9729 | +| deposit | 27965 | 171787 | 186596 | 188519 | 9729 | | dropProposal | 23739 | 40533 | 33600 | 63600 | 2307 | | execute | 26209 | 71295 | 71327 | 161717 | 3076 | -| finaliseWithdraw | 23757 | 45077 | 48283 | 65383 | 6057 | +| finaliseWithdraw | 23757 | 45135 | 48283 | 65383 | 6135 | | getConfiguration | 1913 | 12163 | 19913 | 19913 | 5396 | | getProposal | 3523 | 8023 | 3523 | 31523 | 10590 | | getProposalState | 469 | 11470 | 13558 | 21242 | 23311 | -| getWithdrawal | 1075 | 1075 | 1075 | 1075 | 10010 | +| getWithdrawal | 1075 | 1075 | 1075 | 1075 | 10229 | | governanceProposer | 424 | 1418 | 424 | 2424 | 515 | -| initiateWithdraw | 30945 | 199027 | 211342 | 228958 | 7499 | +| initiateWithdraw | 30945 | 199391 | 211342 | 228958 | 7628 | | powerAt | 1042 | 1412 | 1042 | 3029 | 4608 | | proposalCount | 338 | 1714 | 2338 | 2338 | 1116 | -| propose | 23763 | 321927 | 320487 | 337587 | 606 | -| proposeWithLock | 26545 | 421003 | 422627 | 422627 | 257 | -| totalPowerAt | 612 | 1569 | 883 | 3568 | 6067 | -| updateConfiguration | 23457 | 32910 | 24180 | 48186 | 6145 | -| updateGovernanceProposer | 21693 | 27184 | 28016 | 28028 | 2048 | +| propose | 23763 | 321925 | 320487 | 337587 | 606 | +| proposeWithLock | 26545 | 421004 | 422627 | 422627 | 257 | +| totalPowerAt | 612 | 1566 | 883 | 3568 | 6093 | +| updateConfiguration | 23457 | 32911 | 24180 | 48186 | 6145 | +| updateGovernanceProposer | 21705 | 27185 | 28016 | 28028 | 2048 | | vote | 30670 | 87818 | 94478 | 94500 | 12289 | -| withdrawalCount | 383 | 391 | 383 | 2383 | 2482 | +| withdrawalCount | 383 | 391 | 383 | 2383 | 2508 | | src/governance/Registry.sol:Registry contract | | | | | | |-----------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | @@ -122,21 +125,21 @@ | Function Name | min | avg | median | max | # calls | | getCurrentSnapshot | 664 | 2664 | 2664 | 4664 | 514 | | getGovernance | 341 | 2159 | 2341 | 2341 | 2829 | -| getRollup | 374 | 2358 | 2374 | 2374 | 869977 | +| getRollup | 374 | 2358 | 2374 | 2374 | 873069 | | getSnapshot | 4740 | 4740 | 4740 | 4740 | 257 | | getVersionFor | 743 | 3527 | 2927 | 4927 | 773 | | isRollupRegistered | 657 | 3805 | 2812 | 4812 | 515 | | numberOfVersions | 350 | 1685 | 2350 | 2350 | 770 | | transferOwnership | 28592 | 28592 | 28592 | 28592 | 106 | -| upgrade | 23672 | 103312 | 106801 | 106801 | 6176 | +| upgrade | 23672 | 103299 | 106801 | 106801 | 6152 | | src/governance/RewardDistributor.sol:RewardDistributor contract | | | | | | |-----------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | | 513664 | 2360 | | | | | | Function Name | min | avg | median | max | # calls | -| BLOCK_REWARD | 238 | 238 | 238 | 238 | 376 | -| canonicalRollup | 1143 | 3143 | 3143 | 5643 | 904 | -| claim | 30122 | 45856 | 35665 | 64090 | 513 | +| BLOCK_REWARD | 238 | 238 | 238 | 238 | 374 | +| canonicalRollup | 1143 | 3143 | 3143 | 5643 | 865 | +| claim | 30122 | 45837 | 35665 | 64090 | 513 | | owner | 2384 | 2384 | 2384 | 2384 | 257 | | registry | 347 | 1347 | 1347 | 2347 | 2 | | updateRegistry | 23757 | 23781 | 23757 | 30119 | 257 | @@ -150,15 +153,15 @@ | N | 260 | 260 | 260 | 260 | 1949 | | REGISTRY | 205 | 205 | 205 | 205 | 256 | | computeRound | 435 | 435 | 435 | 435 | 266 | -| executeProposal | 29491 | 43507 | 37213 | 366485 | 2053 | +| executeProposal | 29491 | 41730 | 35183 | 364397 | 2053 | | getExecutor | 3397 | 3397 | 3397 | 3397 | 256 | | getInstance | 951 | 951 | 951 | 951 | 256 | | rounds | 865 | 865 | 865 | 865 | 522 | -| vote | 29794 | 50139 | 50074 | 126085 | 855787 | +| vote | 29794 | 48109 | 48044 | 122070 | 859035 | | yeaCount | 851 | 851 | 851 | 851 | 16 | -| src/periphery/Forwarder.sol:Forwarder contract | | | | | | -|------------------------------------------------|-----------------|-------|--------|--------|---------| -| Deployment Cost | Deployment Size | | | | | -| 358690 | 1553 | | | | | -| Function Name | min | avg | median | max | # calls | -| forward | 24936 | 27197 | 27012 | 132983 | 514 | +| src/periphery/Forwarder.sol:Forwarder contract | | | | | | +|------------------------------------------------|-----------------|--------|--------|---------|---------| +| Deployment Cost | Deployment Size | | | | | +| 358690 | 1553 | | | | | +| Function Name | min | avg | median | max | # calls | +| forward | 24936 | 134385 | 28924 | 2054339 | 614 | diff --git a/l1-contracts/src/core/libraries/TimeLib.sol b/l1-contracts/src/core/libraries/TimeLib.sol index 91eb92309c0c..3756e79c5486 100644 --- a/l1-contracts/src/core/libraries/TimeLib.sol +++ b/l1-contracts/src/core/libraries/TimeLib.sol @@ -5,6 +5,8 @@ pragma solidity >=0.8.27; // solhint-disable-next-line no-unused-import import {Timestamp, Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol"; +import {SafeCast} from "@oz/utils/math/SafeCast.sol"; + struct TimeStorage { uint128 genesisTime; uint32 slotDuration; // Number of seconds in a slot @@ -12,13 +14,15 @@ struct TimeStorage { } library TimeLib { + using SafeCast for uint256; + bytes32 private constant TIME_STORAGE_POSITION = keccak256("aztec.time.storage"); function initialize(uint256 _genesisTime, uint256 _slotDuration, uint256 _epochDuration) internal { TimeStorage storage store = getStorage(); - store.genesisTime = uint128(_genesisTime); - store.slotDuration = uint32(_slotDuration); - store.epochDuration = uint32(_epochDuration); + store.genesisTime = _genesisTime.toUint128(); + store.slotDuration = _slotDuration.toUint32(); + store.epochDuration = _epochDuration.toUint32(); } function toTimestamp(Slot _a) internal view returns (Timestamp) { diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index b203acf2ba7b..ddc1c6bedfcf 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.27; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; +import {SafeCast} from "@oz/utils/math/SafeCast.sol"; /** * @notice Structure to store a set of addresses with their historical snapshots @@ -35,6 +36,8 @@ struct AddressSnapshot { * and allows querying the state of addresses at any point in time */ library AddressSnapshotLib { + using SafeCast for uint256; + /** * @notice Bit mask used to indicate presence of a validator in the set */ @@ -61,7 +64,7 @@ library AddressSnapshotLib { _self.validatorToIndex[_validator] = indexWithBit; _self.checkpoints[index].push( - AddressSnapshot({addr: _validator, epochNumber: uint96(Epoch.unwrap(_epochNumber))}) + AddressSnapshot({addr: _validator, epochNumber: Epoch.unwrap(_epochNumber).toUint96()}) ); _self.size += 1; return true; @@ -83,20 +86,19 @@ library AddressSnapshotLib { AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; address lastValidator = lastSnapshot.addr; - uint256 newLocationWithMask = _index | PRESENCE_BIT; - - _self.validatorToIndex[lastValidator] = newLocationWithMask; // Remove the last validator from the index + uint256 newLocation = _index | PRESENCE_BIT; // If we are removing the last item, we cannot swap it with anything // so we append a new address of zero for this epoch + // And since we are removing it, we set the location to 0 if (lastIndex == _index) { lastSnapshot.addr = address(0); - - // TODO: reuse value above, only insert once - _self.validatorToIndex[lastValidator] = 0; + newLocation = 0; } - lastSnapshot.epochNumber = uint96(epochNow); + _self.validatorToIndex[lastValidator] = newLocation; + + lastSnapshot.epochNumber = epochNow.toUint96(); // Check if there's already a checkpoint for this index in the current epoch uint256 checkpointCount = _self.checkpoints[_index].length; if ( @@ -164,7 +166,7 @@ library AddressSnapshotLib { Epoch _epoch ) internal view returns (address) { uint256 numCheckpoints = _self.checkpoints[_index].length; - uint96 epoch = uint96(Epoch.unwrap(_epoch)); + uint96 epoch = Epoch.unwrap(_epoch).toUint96(); if (numCheckpoints == 0) { return address(0); From 3c85b8f3595561bf900a3342da9a98bdd1dad929 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 27 Mar 2025 19:08:03 +0000 Subject: [PATCH 13/43] chore: include setupepoch in view function --- .../libraries/validator-selection/ValidatorSelectionLib.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index d14586f4ad54..d02c067c3a1e 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -165,13 +165,13 @@ library ValidatorSelectionLib { internal returns (address[] memory) { - uint256 validatorSetSize = _stakingStore.attesters.length(); + ValidatorSelectionStorage storage store = getStorage(); + uint256 validatorSetSize = store.epochs[_epoch].validatorSetSize; if (validatorSetSize == 0) { return new address[](0); } - ValidatorSelectionStorage storage store = getStorage(); uint256 targetCommitteeSize = store.targetCommitteeSize; // If we have less validators than the target committee size, we just return the full set @@ -193,6 +193,8 @@ library ValidatorSelectionLib { internal returns (address[] memory) { + setupEpoch(_stakingStore); + ValidatorSelectionStorage storage store = getStorage(); EpochData storage epoch = store.epochs[_epochNumber]; From d166a5f78cb7f72ddf8fe46a941e19cb1c268b13 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Mon, 14 Apr 2025 15:47:06 +0000 Subject: [PATCH 14/43] chore: more isolated tree testing --- .../AddressSnapshotsBase.t.sol | 27 +++++++++ .../address_snapshots/add.t.sol | 24 ++++++++ .../address_snapshots/add.tree | 7 +++ .../address_snapshots/at.t.sol | 50 ++++++++++++++++ .../address_snapshots/at.tree | 9 +++ .../getAddressFromIndexAtEpoch.t.sol | 48 +++++++++++++++ .../getAddressFromIndexAtEpoch.tree | 12 ++++ .../address_snapshots/length.t.sol | 39 ++++++++++++ .../address_snapshots/length.tree | 7 +++ .../address_snapshots/removeByName.t.sol | 59 +++++++++++++++++++ .../address_snapshots/removeByName.tree | 11 ++++ .../address_snapshots/values.t.sol | 43 ++++++++++++++ .../address_snapshots/values.tree | 8 +++ 13 files changed, 344 insertions(+) create mode 100644 l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol create mode 100644 l1-contracts/test/validator-selection/address_snapshots/add.t.sol create mode 100644 l1-contracts/test/validator-selection/address_snapshots/add.tree create mode 100644 l1-contracts/test/validator-selection/address_snapshots/at.t.sol create mode 100644 l1-contracts/test/validator-selection/address_snapshots/at.tree create mode 100644 l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol create mode 100644 l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree create mode 100644 l1-contracts/test/validator-selection/address_snapshots/length.t.sol create mode 100644 l1-contracts/test/validator-selection/address_snapshots/length.tree create mode 100644 l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol create mode 100644 l1-contracts/test/validator-selection/address_snapshots/removeByName.tree create mode 100644 l1-contracts/test/validator-selection/address_snapshots/values.t.sol create mode 100644 l1-contracts/test/validator-selection/address_snapshots/values.tree diff --git a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol new file mode 100644 index 000000000000..050ceb889c3a --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; + +import {Test} from "forge-std/Test.sol"; +import {TimeLib, TimeStorage, Epoch} from "@aztec/core/libraries/TimeLib.sol"; +import {TimeCheater} from "../../staking/TimeCheater.sol"; + +contract AddressSnapshotsBase is Test { + using AddressSnapshotLib for SnapshottedAddressSet; + + uint256 private constant SLOT_DURATION = 36; + uint256 private constant EPOCH_DURATION = 48; + uint256 private immutable GENESIS_TIME = block.timestamp; + + SnapshottedAddressSet validatorSet; + TimeCheater timeCheater; + + function setUp() public { + timeCheater = new TimeCheater(address(this), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); + } +} diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol new file mode 100644 index 000000000000..153a262d68aa --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; + +contract AddTest is AddressSnapshotsBase { + using AddressSnapshotLib for SnapshottedAddressSet; + + function test_WhenValidatorIsNotInTheSet() public { + timeCheater.cheat__setEpochNow(1); + assertTrue(validatorSet.add(address(1))); + assertEq(validatorSet.length(), 1); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + } + + function test_WhenValidatorIsAlreadyInTheSet() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + assertFalse(validatorSet.add(address(1))); + } +} diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.tree b/l1-contracts/test/validator-selection/address_snapshots/add.tree new file mode 100644 index 000000000000..7ad0d609ccd5 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/add.tree @@ -0,0 +1,7 @@ +AddTest +├── when validator is not in the set +│ ├── it returns true +│ ├── it increases the length +│ └── it creates a checkpoint +└── when validator is already in the set + └── it returns false diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol new file mode 100644 index 000000000000..eaa9d75de09a --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; + +contract AtTest is AddressSnapshotsBase { + using AddressSnapshotLib for SnapshottedAddressSet; + + function test_WhenNoValidatorsAreRegistered() public { + vm.expectRevert(); + validatorSet.at(0); + } + + function test_WhenIndexIsOutOfBounds() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + + vm.expectRevert(); + validatorSet.at(1); + } + + function test_WhenIndexIsValid() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); + + assertEq(validatorSet.at(0), address(1)); + assertEq(validatorSet.at(1), address(2)); + assertEq(validatorSet.at(2), address(3)); + } + + function test_WhenValidatorsAreRemoved() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); + + timeCheater.cheat__setEpochNow(2); + validatorSet.remove(1); + + assertEq(validatorSet.at(0), address(1)); + assertEq(validatorSet.at(1), address(3)); + } +} diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.tree b/l1-contracts/test/validator-selection/address_snapshots/at.tree new file mode 100644 index 000000000000..e7542ae3f398 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/at.tree @@ -0,0 +1,9 @@ +AtTest +├── when no validators are registered +│ └── it reverts with IndexOutOfBounds +├── when index is out of bounds +│ └── it reverts with IndexOutOfBounds +└── when index is valid + ├── it returns the current validator at that index + └── when validators are removed + └── it returns the correct validator after reordering diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol new file mode 100644 index 000000000000..1deba6641ba1 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; + +contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { + using AddressSnapshotLib for SnapshottedAddressSet; + + function test_WhenNoValidatorsAreRegistered() view public { + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(0)), address(0)); + } + + function test_WhenValidatorsExist_WhenQueryingCurrentEpoch() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + } + + function test_WhenValidatorsExist_WhenQueryingPastEpoch() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + + timeCheater.cheat__setEpochNow(2); + validatorSet.add(address(2)); + + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + } + + function test_WhenValidatorsExist_WhenValidatorWasRemoved() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + + timeCheater.cheat__setEpochNow(2); + validatorSet.remove(0); + + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(0)); + } + + function test_WhenIndexIsOutOfBounds() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + + assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)), address(0)); + } +} diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree new file mode 100644 index 000000000000..d9a073bb7a08 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree @@ -0,0 +1,12 @@ +GetAddressFromIndexAtEpochTest +├── when no validators are registered +│ └── it returns address(0) +├── when validators exist +│ ├── when querying current epoch +│ │ └── it returns the correct validator address +│ ├── when querying past epoch +│ │ └── it maintains historical values correctly +│ └── when validator was removed +│ └── it returns address(0) for that epoch +└── when index is out of bounds + └── it returns address(0) diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol new file mode 100644 index 000000000000..dffcfcaa90b0 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; + +contract LengthTest is AddressSnapshotsBase { + using AddressSnapshotLib for SnapshottedAddressSet; + + function test_WhenNoValidatorsAreRegistered() view public { + assertEq(validatorSet.length(), 0); + } + + function test_WhenAddingValidators() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + assertEq(validatorSet.length(), 1); + + validatorSet.add(address(2)); + assertEq(validatorSet.length(), 2); + } + + function test_WhenRemovingValidators() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + + timeCheater.cheat__setEpochNow(2); + validatorSet.remove(0); + assertEq(validatorSet.length(), 1); + + validatorSet.remove(0); + assertEq(validatorSet.length(), 0); + } +} diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.tree b/l1-contracts/test/validator-selection/address_snapshots/length.tree new file mode 100644 index 000000000000..e3972927aa63 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/length.tree @@ -0,0 +1,7 @@ +LengthTest +├── when no validators are registered +│ └── it returns 0 +├── when adding validators +│ └── it increases the length +└── when removing validators + └── it decreases the length diff --git a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol new file mode 100644 index 000000000000..6f4f1d52abf4 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; +import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; + +contract RemoveByNameTest is AddressSnapshotsBase { + using AddressSnapshotLib for SnapshottedAddressSet; + + function test_WhenAddressNotInTheSet() public { + assertFalse(validatorSet.remove(address(1))); + } + + function test_WhenValidatorIsInTheSet() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + + timeCheater.cheat__setEpochNow(2); + assertTrue(validatorSet.remove(address(1))); + assertEq(validatorSet.length(), 0); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + } + + function test_WhenRemovingMultipleValidators() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); + + timeCheater.cheat__setEpochNow(2); + validatorSet.remove(address(2)); + + address[] memory vals = validatorSet.values(); + assertEq(vals.length, 2); + assertEq(vals[0], address(1)); + assertEq(vals[1], address(3)); + + timeCheater.cheat__setEpochNow(3); + validatorSet.remove(address(1)); + + vals = validatorSet.values(); + assertEq(vals.length, 1); + assertEq(vals[0], address(3)); + + // Verify snapshots + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)), address(2)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(2, Epoch.wrap(1)), address(3)); + + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(2)), address(3)); + } +} diff --git a/l1-contracts/test/validator-selection/address_snapshots/removeByName.tree b/l1-contracts/test/validator-selection/address_snapshots/removeByName.tree new file mode 100644 index 000000000000..4074d04d8024 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/removeByName.tree @@ -0,0 +1,11 @@ +RemoveByNameTest +├── when address not in the set +│ └── it returns false +├── when validator is in the set +│ ├── it returns true +│ ├── it decreases the length +│ ├── it updates the snapshot for that index +│ └── it maintains historical values correctly +└── when removing multiple validators + ├── it maintains correct order of remaining validators + └── it updates snapshots correctly for each removal diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol new file mode 100644 index 000000000000..a9dceea90801 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2025 Aztec Labs. +pragma solidity >=0.8.27; + +import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; + +contract ValuesTest is AddressSnapshotsBase { + using AddressSnapshotLib for SnapshottedAddressSet; + + function test_WhenNoValidatorsAreRegistered() view public { + address[] memory vals = validatorSet.values(); + assertEq(vals.length, 0); + } + + function test_WhenValidatorsExist() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); + + address[] memory vals = validatorSet.values(); + assertEq(vals.length, 3); + assertEq(vals[0], address(1)); + assertEq(vals[1], address(2)); + assertEq(vals[2], address(3)); + } + + function test_WhenValidatorsAreRemoved() public { + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); + + timeCheater.cheat__setEpochNow(2); + validatorSet.remove(address(2)); + + address[] memory vals = validatorSet.values(); + assertEq(vals.length, 2); + assertEq(vals[0], address(1)); + assertEq(vals[1], address(3)); + } +} diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.tree b/l1-contracts/test/validator-selection/address_snapshots/values.tree new file mode 100644 index 000000000000..8330cf3ea2f3 --- /dev/null +++ b/l1-contracts/test/validator-selection/address_snapshots/values.tree @@ -0,0 +1,8 @@ +ValuesTest +├── when no validators are registered +│ └── it returns empty array +├── when validators exist +│ ├── it returns array with correct length +│ └── it returns array with correct addresses in order +└── when validators are removed + └── it returns array with remaining validators From ee20cc56816d71ac70cd5643dd14de6f1bdfb22c Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Mon, 14 Apr 2025 16:09:43 +0000 Subject: [PATCH 15/43] chore: remove old tests --- .../AddressSnapshots.t.sol | 258 ------------------ .../address_snapshots.tree | 23 -- .../address_snapshots/add.t.sol | 11 +- .../address_snapshots/at.t.sol | 11 +- .../getAddressFromIndexAtEpoch.t.sol | 76 +++++- .../getAddressFromIndexAtEpoch.tree | 8 +- .../address_snapshots/length.t.sol | 10 +- .../address_snapshots/length.tree | 3 +- .../address_snapshots/removeByName.t.sol | 9 + .../address_snapshots/values.t.sol | 12 +- 10 files changed, 113 insertions(+), 308 deletions(-) delete mode 100644 l1-contracts/test/validator-selection/AddressSnapshots.t.sol delete mode 100644 l1-contracts/test/validator-selection/address_snapshots.tree diff --git a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol b/l1-contracts/test/validator-selection/AddressSnapshots.t.sol deleted file mode 100644 index e91692823806..000000000000 --- a/l1-contracts/test/validator-selection/AddressSnapshots.t.sol +++ /dev/null @@ -1,258 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2025 Aztec Labs. -pragma solidity >=0.8.27; - -import {Test} from "forge-std/Test.sol"; -import { - AddressSnapshotLib, - SnapshottedAddressSet -} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; -import {TimeLib, TimeStorage, Epoch} from "@aztec/core/libraries/TimeLib.sol"; -import {TimeCheater} from "../staking/TimeCheater.sol"; - -contract LibTest { - SnapshottedAddressSet validatorSet; - - function add(address _validator) public returns (bool) { - return AddressSnapshotLib.add(validatorSet, _validator); - } - - function remove(uint256 _index) public returns (bool) { - return AddressSnapshotLib.remove(validatorSet, _index); - } - - function remove(address _validator) public returns (bool) { - return AddressSnapshotLib.remove(validatorSet, _validator); - } - - function at(uint256 _index) public view returns (address) { - return AddressSnapshotLib.at(validatorSet, _index); - } - - function getAddressFromIndexAtEpoch(uint256 _index, uint256 _epoch) public view returns (address) { - return AddressSnapshotLib.getAddressFromIndexAtEpoch(validatorSet, _index, Epoch.wrap(_epoch)); - } - - function length() public view returns (uint256) { - return AddressSnapshotLib.length(validatorSet); - } - - function values() public view returns (address[] memory) { - return AddressSnapshotLib.values(validatorSet); - } -} - -contract AddressSnapshotsTest is Test { - uint256 private constant SLOT_DURATION = 36; - uint256 private constant EPOCH_DURATION = 48; - - uint256 private immutable GENESIS_TIME = block.timestamp; - - LibTest libTest; - TimeCheater timeCheater; - - function setUp() public { - libTest = new LibTest(); - - // Enable us to modify TimeLib functions for libTest - timeCheater = new TimeCheater(address(libTest), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); - } - - function test_getAddresssIndexAt() public { - // Adds validator 1 to the first index in the set, at epoch 1 - timeCheater.cheat__setEpochNow(1); - - libTest.add(address(1)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(1)); - - // Remove index 0 from the set, in the new epoch - timeCheater.cheat__setEpochNow(2); - - libTest.remove( /* index */ 0); - - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 2), address(0)); - - // Add validator 2 to the first index in the set - timeCheater.cheat__setEpochNow(3); - - libTest.add(address(2)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 3), address(2)); - - // Setup and remove the last item in the set alot of times - timeCheater.cheat__setEpochNow(4); - libTest.remove( /* index */ 0); - - timeCheater.cheat__setEpochNow(5); - libTest.add(address(3)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 5), address(3)); - - timeCheater.cheat__setEpochNow(6); - libTest.remove( /* index */ 0); - - timeCheater.cheat__setEpochNow(7); - libTest.add(address(4)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 7), address(4)); - - // Expect past values to be maintained - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(1)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 2), address(0)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 3), address(2)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 4), address(0)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 5), address(3)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 6), address(0)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 7), address(4)); - } - - function test_length() public { - assertEq(libTest.length(), 0); - - libTest.add(address(1)); - assertEq(libTest.length(), 1); - - timeCheater.cheat__setEpochNow(1); - libTest.remove( /* index */ 0); - assertEq(libTest.length(), 0); - - timeCheater.cheat__setEpochNow(2); - libTest.add(address(1)); - libTest.add(address(2)); - assertEq(libTest.length(), 2); - - timeCheater.cheat__setEpochNow(3); - libTest.remove( /* index */ 0); - assertEq(libTest.length(), 1); - } - - function test_values() public { - libTest.add(address(1)); - libTest.add(address(2)); - libTest.add(address(3)); - - address[] memory values = libTest.values(); - assertEq(values.length, 3); - assertEq(values[0], address(1)); - assertEq(values[1], address(2)); - assertEq(values[2], address(3)); - } - - function test_remove_by_name() public { - libTest.add(address(1)); - libTest.add(address(2)); - libTest.add(address(3)); - - timeCheater.cheat__setEpochNow(1); - libTest.remove(address(2)); - - // Order flips - address[] memory values = libTest.values(); - assertEq(values.length, 2); - assertEq(values[0], address(1)); - assertEq(values[1], address(3)); - - timeCheater.cheat__setEpochNow(2); - libTest.remove(address(3)); - - values = libTest.values(); - assertEq(values.length, 1); - assertEq(values[0], address(1)); - - timeCheater.cheat__setEpochNow(3); - libTest.add(address(4)); - - values = libTest.values(); - assertEq(values.length, 2); - assertEq(values[0], address(1)); - assertEq(values[1], address(4)); - - timeCheater.cheat__setEpochNow(4); - libTest.remove(address(1)); - - values = libTest.values(); - assertEq(values.length, 1); - assertEq(values[0], address(4)); - } - - function test_at() public { - libTest.add(address(1)); - libTest.add(address(2)); - libTest.add(address(3)); - - // Index 1 is the first item in the set - assertEq(libTest.at(0), address(1)); - assertEq(libTest.at(1), address(2)); - assertEq(libTest.at(2), address(3)); - - timeCheater.cheat__setEpochNow(1); - libTest.remove( /* index */ 1); - - // When removing and item, the index has become shuffled - assertEq(libTest.at(0), address(1)); - assertEq(libTest.at(1), address(3)); - - // Expect past values to be maintained - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(1)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(1)); - } - - function test_WhenNoValidatorsAreRegistered() external { - vm.expectRevert(); - libTest.at(0); - - // it causes getAddressFromIndexAtEpoch to return revert - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(0)); - } - - modifier whenAddingAValidator() { - libTest.add(address(1)); - _; - } - - function test_WhenAlreadyAdded() external whenAddingAValidator { - // it returns false - assertFalse(libTest.add(address(1))); - } - - function test_WhenNotPreviouslyAdded() external whenAddingAValidator { - // it returns true - assertTrue(libTest.add(address(2))); - // it increases the length - assertEq(libTest.length(), 2); - - // it creates a checkpoint - timeCheater.cheat__setEpochNow(1); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(1)); - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 1, /* epoch */ 0), address(2)); - } - - function test_cannotRemoveAddressNotInTheSet() external { - // it returns false - assertFalse(libTest.remove(address(1))); - } - - function test_WhenValidatorIsInTheSet() external { - // it returns true - libTest.add(address(1)); - timeCheater.cheat__setEpochNow(1); - - assertTrue(libTest.remove(address(1))); - // it decreases the length - assertEq(libTest.length(), 0); - // it updates the snapshot for that index - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 1), address(0)); - - // it maintains historical values correctly - assertEq(libTest.getAddressFromIndexAtEpoch( /* index */ 0, /* epoch */ 0), address(1)); - } - - function test_WhenIndexIsOutOfBounds() external { - // it reverts with IndexOutOfBounds - vm.expectRevert(); - libTest.at(1); - } - - function test_WhenIndexIsValid() external { - // it returns the current validator at that index - libTest.add(address(1)); - assertEq(libTest.at(0), address(1)); - } -} diff --git a/l1-contracts/test/validator-selection/address_snapshots.tree b/l1-contracts/test/validator-selection/address_snapshots.tree deleted file mode 100644 index 92b59580a089..000000000000 --- a/l1-contracts/test/validator-selection/address_snapshots.tree +++ /dev/null @@ -1,23 +0,0 @@ -AddressSnapshotLibTest -├── when no validators are registered -│ ├── it causes at to revert -│ └── it causes getAddressFromIndexAtEpoch to return revert -├── when adding a validator -│ ├── when already added -│ │ └── it returns false -│ └── when not previously added -│ ├── it returns true -│ ├── it increases the length -│ └── it creates a checkpoint -├── when removing a validator -│ ├── when address not in the set -│ │ └── it returns false -│ └── when validator is in the set -│ ├── it returns true -│ ├── it decreases the length -│ ├── it updates the snapshot for that index -│ └── it maintains historical values correctly -├── when index is out of bounds -│ └── it reverts with IndexOutOfBounds -└── when index is valid - └── it returns the current validator at that index diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index 153a262d68aa..dd1ec052d20d 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -3,13 +3,20 @@ pragma solidity >=0.8.27; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; contract AddTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenValidatorIsNotInTheSet() public { + // It returns true + // It increases the length + // It creates a checkpoint + timeCheater.cheat__setEpochNow(1); assertTrue(validatorSet.add(address(1))); assertEq(validatorSet.length(), 1); @@ -17,6 +24,8 @@ contract AddTest is AddressSnapshotsBase { } function test_WhenValidatorIsAlreadyInTheSet() public { + // It returns false + timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); assertFalse(validatorSet.add(address(1))); diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index eaa9d75de09a..c350b310460c 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -12,11 +12,13 @@ contract AtTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenNoValidatorsAreRegistered() public { + // It reverts vm.expectRevert(); validatorSet.at(0); } function test_WhenIndexIsOutOfBounds() public { + // It reverts timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); @@ -25,6 +27,7 @@ contract AtTest is AddressSnapshotsBase { } function test_WhenIndexIsValid() public { + // it returns the current validator at that index timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); validatorSet.add(address(2)); @@ -33,14 +36,8 @@ contract AtTest is AddressSnapshotsBase { assertEq(validatorSet.at(0), address(1)); assertEq(validatorSet.at(1), address(2)); assertEq(validatorSet.at(2), address(3)); - } - - function test_WhenValidatorsAreRemoved() public { - timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - validatorSet.add(address(2)); - validatorSet.add(address(3)); + // it returns the correct validator after reordering timeCheater.cheat__setEpochNow(2); validatorSet.remove(1); diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol index 1deba6641ba1..4dbaedbb8587 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -3,35 +3,46 @@ pragma solidity >=0.8.27; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; - function test_WhenNoValidatorsAreRegistered() view public { + function test_WhenNoValidatorsAreRegistered() public view { + // It should return address(0) assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(0)), address(0)); } - function test_WhenValidatorsExist_WhenQueryingCurrentEpoch() public { + modifier whenValidatorsExist() { timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); + _; + } + + function test_WhenValidatorsExist_WhenQueryingCurrentEpoch() public whenValidatorsExist { + // It should return the current validator address assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); } - function test_WhenValidatorsExist_WhenQueryingPastEpoch() public { - timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); + function test_WhenValidatorsExist_WhenQueryingFutureEpoch() public whenValidatorsExist { + // It should return the current validator address + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); + } + function test_WhenValidatorsExist_WhenQueryingPastEpoch() public whenValidatorsExist { + // It should return the validator address from the snapshot timeCheater.cheat__setEpochNow(2); validatorSet.add(address(2)); assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); } - function test_WhenValidatorsExist_WhenValidatorWasRemoved() public { - timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); + function test_WhenValidatorsExist_WhenValidatorWasRemoved() public whenValidatorsExist { + // It should return address(0) timeCheater.cheat__setEpochNow(2); validatorSet.remove(0); @@ -39,10 +50,49 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(0)); } - function test_WhenIndexIsOutOfBounds() public { - timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - + function test_WhenIndexIsOutOfBounds() public whenValidatorsExist { + // It should return address(0) assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)), address(0)); } + + function test_WhenValidatorIsRemovedAndNewOneAddedAtSamePosition() public whenValidatorsExist { + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(1)), address(1)); + + // Remove index 0 from the set, in the new epoch + timeCheater.cheat__setEpochNow(2); + + validatorSet.remove( /* index */ 0); + + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(0)); + + // Add validator 2 to the first index in the set + timeCheater.cheat__setEpochNow(3); + + validatorSet.add(address(2)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(2)); + + // Setup and remove the last item in the set alot of times + timeCheater.cheat__setEpochNow(4); + validatorSet.remove( /* index */ 0); + + timeCheater.cheat__setEpochNow(5); + validatorSet.add(address(3)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(3)); + + timeCheater.cheat__setEpochNow(6); + validatorSet.remove( /* index */ 0); + + timeCheater.cheat__setEpochNow(7); + validatorSet.add(address(4)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(4)); + + // Expect past values to be maintained + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(1)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(2)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(3)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(4)); + } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree index d9a073bb7a08..afead58bde41 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree @@ -4,9 +4,13 @@ GetAddressFromIndexAtEpochTest ├── when validators exist │ ├── when querying current epoch │ │ └── it returns the correct validator address +│ ├── when querying future epoch +│ │ └── it returns the correct (current) validator address │ ├── when querying past epoch │ │ └── it maintains historical values correctly -│ └── when validator was removed -│ └── it returns address(0) for that epoch +│ ├── when validator was removed +│ │ └── it returns address(0) for that epoch +│ └── when validator is removed and new one added at same position +│ └── it maintains both current and historical values correctly └── when index is out of bounds └── it returns address(0) diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol index dffcfcaa90b0..58d9f526aabe 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol @@ -7,15 +7,18 @@ import { SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; +import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; contract LengthTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; - function test_WhenNoValidatorsAreRegistered() view public { + function test_WhenNoValidatorsAreRegistered() public view { + // It returns 0 assertEq(validatorSet.length(), 0); } function test_WhenAddingValidators() public { + // It increases the length timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); assertEq(validatorSet.length(), 1); @@ -25,6 +28,8 @@ contract LengthTest is AddressSnapshotsBase { } function test_WhenRemovingValidators() public { + // It decrease the length + // It maintains historical values correctly timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); validatorSet.add(address(2)); @@ -35,5 +40,8 @@ contract LengthTest is AddressSnapshotsBase { validatorSet.remove(0); assertEq(validatorSet.length(), 0); + + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(0)); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.tree b/l1-contracts/test/validator-selection/address_snapshots/length.tree index e3972927aa63..edd05e87888b 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/length.tree @@ -4,4 +4,5 @@ LengthTest ├── when adding validators │ └── it increases the length └── when removing validators - └── it decreases the length + ├── it decreases the length + └── it maintains historical values correctly diff --git a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol index 6f4f1d52abf4..77e788aa06ff 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol @@ -13,10 +13,16 @@ contract RemoveByNameTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenAddressNotInTheSet() public { + // It returns false assertFalse(validatorSet.remove(address(1))); } function test_WhenValidatorIsInTheSet() public { + // It returns true + // It decreases the length + // It updates the snapshot for that index + // It maintains historical values correctly + timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); @@ -28,6 +34,9 @@ contract RemoveByNameTest is AddressSnapshotsBase { } function test_WhenRemovingMultipleValidators() public { + // It maintains correct order of remaining validators + // It updates snapshots correctly for each removal + timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); validatorSet.add(address(2)); diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index a9dceea90801..7f923527e8d6 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -2,18 +2,25 @@ // Copyright 2025 Aztec Labs. pragma solidity >=0.8.27; -import {AddressSnapshotLib, SnapshottedAddressSet} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; +import { + AddressSnapshotLib, + SnapshottedAddressSet +} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; contract ValuesTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; - function test_WhenNoValidatorsAreRegistered() view public { + function test_WhenNoValidatorsAreRegistered() public view { + // It returns empty array address[] memory vals = validatorSet.values(); assertEq(vals.length, 0); } function test_WhenValidatorsExist() public { + // It returns array with correct length + // It returns array with addresses in order + timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); validatorSet.add(address(2)); @@ -27,6 +34,7 @@ contract ValuesTest is AddressSnapshotsBase { } function test_WhenValidatorsAreRemoved() public { + // It returns array of remaining validators timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); validatorSet.add(address(2)); From 633839c3793fea27154f7811350477e8ad511fc9 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 09:28:50 +0000 Subject: [PATCH 16/43] feat: also snapshot size of set --- .../libraries/staking/AddressSnapshotLib.sol | 52 ++++++++++++----- .../address_snapshots/add.t.sol | 13 ++++- .../address_snapshots/add.tree | 3 +- .../address_snapshots/at.t.sol | 17 +++++- .../getAddressFromIndexAtEpoch.t.sol | 56 ++++++++++--------- .../getAddressFromIndexAtEpoch.tree | 3 +- .../address_snapshots/length.t.sol | 21 +++++-- .../address_snapshots/removeByName.t.sol | 30 +++++++--- .../address_snapshots/values.t.sol | 10 +++- 9 files changed, 140 insertions(+), 65 deletions(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index ddc1c6bedfcf..93a700d9ae13 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -5,6 +5,7 @@ pragma solidity >=0.8.27; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {SafeCast} from "@oz/utils/math/SafeCast.sol"; +import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol"; /** * @notice Structure to store a set of addresses with their historical snapshots @@ -13,7 +14,8 @@ import {SafeCast} from "@oz/utils/math/SafeCast.sol"; * @param validatorToIndex Mapping of validator address to its current index in the set */ struct SnapshottedAddressSet { - uint256 size; + // This size must also be snapshotted + Checkpoints.Trace224 size; mapping(uint256 index => AddressSnapshot[]) checkpoints; // Store up to date position for each validator mapping(address validator => uint256 index) validatorToIndex; @@ -37,6 +39,7 @@ struct AddressSnapshot { */ library AddressSnapshotLib { using SafeCast for uint256; + using Checkpoints for Checkpoints.Trace224; /** * @notice Bit mask used to indicate presence of a validator in the set @@ -56,17 +59,19 @@ library AddressSnapshotLib { } // Insert into the end of the array - uint256 index = _self.size; - Epoch _epochNumber = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); + Epoch _nextEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)) + Epoch.wrap(1); + + uint256 index = _self.size.latest(); // Include max bit to indicate that the validator is in the set - means 0 index is not 0 uint256 indexWithBit = index | PRESENCE_BIT; _self.validatorToIndex[_validator] = indexWithBit; _self.checkpoints[index].push( - AddressSnapshot({addr: _validator, epochNumber: Epoch.unwrap(_epochNumber).toUint96()}) + AddressSnapshot({addr: _validator, epochNumber: Epoch.unwrap(_nextEpoch).toUint96()}) ); - _self.size += 1; + + _self.size.push(Epoch.unwrap(_nextEpoch).toUint32(), (index + 1).toUint224()); return true; } @@ -78,10 +83,10 @@ library AddressSnapshotLib { */ function remove(SnapshottedAddressSet storage _self, uint256 _index) internal returns (bool) { // To remove from the list, we push the last item into the index and reduce the size - uint256 lastIndex = _self.size - 1; + uint256 lastIndex = _self.size.latest() - 1; uint256 lastSnapshotLength = _self.checkpoints[lastIndex].length; - uint256 epochNow = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp))); + uint256 nextEpoch = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)) + Epoch.wrap(1)); AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; @@ -98,11 +103,11 @@ library AddressSnapshotLib { _self.validatorToIndex[lastValidator] = newLocation; - lastSnapshot.epochNumber = epochNow.toUint96(); + lastSnapshot.epochNumber = nextEpoch.toUint96(); // Check if there's already a checkpoint for this index in the current epoch uint256 checkpointCount = _self.checkpoints[_index].length; if ( - checkpointCount > 0 && _self.checkpoints[_index][checkpointCount - 1].epochNumber == epochNow + checkpointCount > 0 && _self.checkpoints[_index][checkpointCount - 1].epochNumber == nextEpoch ) { // If there's already a checkpoint for this epoch, update it instead of adding a new one _self.checkpoints[_index][checkpointCount - 1] = lastSnapshot; @@ -111,7 +116,7 @@ library AddressSnapshotLib { _self.checkpoints[_index].push(lastSnapshot); } - _self.size -= 1; + _self.size.push(nextEpoch.toUint32(), (lastIndex).toUint224()); return true; } @@ -140,9 +145,12 @@ library AddressSnapshotLib { */ function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { uint256 numCheckpoints = _self.checkpoints[_index].length; + Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); + + uint256 size = _self.size.lowerLookup(Epoch.unwrap(currentEpoch).toUint32()); - if (_index >= _self.size) { - revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, _self.size); + if (_index >= size) { + revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); } if (numCheckpoints == 0) { @@ -150,7 +158,15 @@ library AddressSnapshotLib { } AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1]; - return lastSnapshot.addr; + + // The staking set is frozen in time until the end of the epoch, so we must check if the last checkpoint is in the current epoch + // If it is, we can return it, otherwise, we must perform a search + if (lastSnapshot.epochNumber == Epoch.unwrap(currentEpoch)) { + return lastSnapshot.addr; + } + + // Otherwise, we must perform a search + return getAddressFromIndexAtEpoch(_self, _index, currentEpoch); } /** @@ -196,6 +212,11 @@ library AddressSnapshotLib { } } + // Guard against the found epoch being greater than the epoch we are querying + if (epoch < _self.checkpoints[_index][lower].epochNumber) { + return address(0); + } + return _self.checkpoints[_index][lower].addr; } @@ -205,7 +226,8 @@ library AddressSnapshotLib { * @return uint256 The number of addresses in the set */ function length(SnapshottedAddressSet storage _self) internal view returns (uint256) { - return _self.size; + Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); + return _self.size.upperLookup(Epoch.unwrap(currentEpoch).toUint32()); } /** @@ -214,7 +236,7 @@ library AddressSnapshotLib { * @return address[] Array of all current addresses in the set */ function values(SnapshottedAddressSet storage _self) internal view returns (address[] memory) { - uint256 size = _self.size; + uint256 size = length(_self); address[] memory vals = new address[](size); for (uint256 i; i < size;) { vals[i] = at(_self, i); diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index dd1ec052d20d..01462238f9c9 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -9,18 +9,25 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; -contract AddTest is AddressSnapshotsBase { +contract AddressSnapshotAddTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenValidatorIsNotInTheSet() public { // It returns true // It increases the length - // It creates a checkpoint + // It creates a checkpoint for the next epoch + // It does not change the current epoch timeCheater.cheat__setEpochNow(1); assertTrue(validatorSet.add(address(1))); + assertEq(validatorSet.length(), 0); + + timeCheater.cheat__setEpochNow(2); assertEq(validatorSet.length(), 1); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + + // Epoch remains frozen, so this number should not update + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); } function test_WhenValidatorIsAlreadyInTheSet() public { diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.tree b/l1-contracts/test/validator-selection/address_snapshots/add.tree index 7ad0d609ccd5..76c8294e25f3 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/add.tree @@ -2,6 +2,7 @@ AddTest ├── when validator is not in the set │ ├── it returns true │ ├── it increases the length -│ └── it creates a checkpoint +│ ├── it creates a checkpoint for the next epoch +│ └── it does not change the current epoch └── when validator is already in the set └── it returns false diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index c350b310460c..536fc5624333 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -8,7 +8,9 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; -contract AtTest is AddressSnapshotsBase { +import "forge-std/console.sol"; + +contract AddressSnapshotAtTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenNoValidatorsAreRegistered() public { @@ -33,14 +35,23 @@ contract AtTest is AddressSnapshotsBase { validatorSet.add(address(2)); validatorSet.add(address(3)); + assertEq(validatorSet.at(0), address(0)); + assertEq(validatorSet.at(1), address(0)); + assertEq(validatorSet.at(2), address(0)); + + // it returns the correct validator after reordering + timeCheater.cheat__setEpochNow(2); assertEq(validatorSet.at(0), address(1)); assertEq(validatorSet.at(1), address(2)); assertEq(validatorSet.at(2), address(3)); - // it returns the correct validator after reordering - timeCheater.cheat__setEpochNow(2); validatorSet.remove(1); + assertEq(validatorSet.at(0), address(1)); + assertEq(validatorSet.at(1), address(2)); + assertEq(validatorSet.at(2), address(3)); + + timeCheater.cheat__setEpochNow(3); assertEq(validatorSet.at(0), address(1)); assertEq(validatorSet.at(1), address(3)); } diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol index 4dbaedbb8587..431e65f93877 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -8,7 +8,6 @@ import { SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; - contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; @@ -20,12 +19,13 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { modifier whenValidatorsExist() { timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); + timeCheater.cheat__setEpochNow(2); _; } function test_WhenValidatorsExist_WhenQueryingCurrentEpoch() public whenValidatorsExist { // It should return the current validator address - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); } function test_WhenValidatorsExist_WhenQueryingFutureEpoch() public whenValidatorsExist { @@ -35,19 +35,21 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { function test_WhenValidatorsExist_WhenQueryingPastEpoch() public whenValidatorsExist { // It should return the validator address from the snapshot - timeCheater.cheat__setEpochNow(2); validatorSet.add(address(2)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + // It should return the validator address from the snapshot + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)), address(0)); } function test_WhenValidatorsExist_WhenValidatorWasRemoved() public whenValidatorsExist { - // It should return address(0) - - timeCheater.cheat__setEpochNow(2); + // It should not remove until the next epoch validatorSet.remove(0); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(0)); + // It should return address(0) in the next epoch + timeCheater.cheat__setEpochNow(3); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)), address(0)); } function test_WhenIndexIsOutOfBounds() public whenValidatorsExist { @@ -56,43 +58,45 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { } function test_WhenValidatorIsRemovedAndNewOneAddedAtSamePosition() public whenValidatorsExist { - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(1)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(1)); // Remove index 0 from the set, in the new epoch - timeCheater.cheat__setEpochNow(2); + timeCheater.cheat__setEpochNow(3); validatorSet.remove( /* index */ 0); - - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)), address(0)); // Add validator 2 to the first index in the set - timeCheater.cheat__setEpochNow(3); - + timeCheater.cheat__setEpochNow(4); validatorSet.add(address(2)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(2)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(2)); // Setup and remove the last item in the set alot of times - timeCheater.cheat__setEpochNow(4); + timeCheater.cheat__setEpochNow(5); validatorSet.remove( /* index */ 0); - timeCheater.cheat__setEpochNow(5); + timeCheater.cheat__setEpochNow(6); validatorSet.add(address(3)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(3)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(3)); - timeCheater.cheat__setEpochNow(6); + timeCheater.cheat__setEpochNow(7); validatorSet.remove( /* index */ 0); - timeCheater.cheat__setEpochNow(7); + timeCheater.cheat__setEpochNow(8); validatorSet.add(address(4)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(4)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)), address(4)); // Expect past values to be maintained - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(1)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(0)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(2)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(1)); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)), address(0)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(3)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(2)); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)), address(0)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(4)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(3)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)), address(4)); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree index afead58bde41..85749c27f4ee 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree @@ -9,7 +9,8 @@ GetAddressFromIndexAtEpochTest │ ├── when querying past epoch │ │ └── it maintains historical values correctly │ ├── when validator was removed -│ │ └── it returns address(0) for that epoch +│ │ ├── it returns the same address for the current epoch +│ │ └── it returns address(0) for the next epoch │ └── when validator is removed and new one added at same position │ └── it maintains both current and historical values correctly └── when index is out of bounds diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol index 58d9f526aabe..09f316647c7e 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol @@ -9,7 +9,7 @@ import { import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; -contract LengthTest is AddressSnapshotsBase { +contract AddressSnapshotLengthTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenNoValidatorsAreRegistered() public view { @@ -20,10 +20,19 @@ contract LengthTest is AddressSnapshotsBase { function test_WhenAddingValidators() public { // It increases the length timeCheater.cheat__setEpochNow(1); + // Length starts at zero + assertEq(validatorSet.length(), 0); + validatorSet.add(address(1)); - assertEq(validatorSet.length(), 1); + // Length remains zero within this epoch + assertEq(validatorSet.length(), 0); validatorSet.add(address(2)); + // Length remains zero within this epoch + assertEq(validatorSet.length(), 0); + + timeCheater.cheat__setEpochNow(2); + // Length increases to 2 assertEq(validatorSet.length(), 2); } @@ -36,12 +45,12 @@ contract LengthTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(2); validatorSet.remove(0); - assertEq(validatorSet.length(), 1); + assertEq(validatorSet.length(), 2); validatorSet.remove(0); - assertEq(validatorSet.length(), 0); + assertEq(validatorSet.length(), 2); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(0)); + timeCheater.cheat__setEpochNow(3); + assertEq(validatorSet.length(), 0); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol index 77e788aa06ff..7c42bd5e23fe 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol @@ -9,7 +9,7 @@ import { import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; -contract RemoveByNameTest is AddressSnapshotsBase { +contract AddressSnapshotRemoveByNameTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenAddressNotInTheSet() public { @@ -25,12 +25,22 @@ contract RemoveByNameTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(1); validatorSet.add(address(1)); + // Length remains 0 within this epoch + assertEq(validatorSet.length(), 0); + // Length increases to 1 in the next epoch timeCheater.cheat__setEpochNow(2); + assertEq(validatorSet.length(), 1); + assertTrue(validatorSet.remove(address(1))); + // Length remains 1 within this epoch + assertEq(validatorSet.length(), 1); + + timeCheater.cheat__setEpochNow(3); + // Length decreases to 0 in the next epoch assertEq(validatorSet.length(), 0); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(0)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)), address(0)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); } function test_WhenRemovingMultipleValidators() public { @@ -45,24 +55,26 @@ contract RemoveByNameTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(2); validatorSet.remove(address(2)); + timeCheater.cheat__setEpochNow(3); + address[] memory vals = validatorSet.values(); assertEq(vals.length, 2); assertEq(vals[0], address(1)); assertEq(vals[1], address(3)); - timeCheater.cheat__setEpochNow(3); validatorSet.remove(address(1)); + timeCheater.cheat__setEpochNow(4); vals = validatorSet.values(); assertEq(vals.length, 1); assertEq(vals[0], address(3)); // Verify snapshots - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)), address(2)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(2, Epoch.wrap(1)), address(3)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(2)), address(3)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(2)), address(2)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(2, Epoch.wrap(2)), address(3)); + + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(3)), address(3)); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index 7f923527e8d6..99375674cba8 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -8,7 +8,7 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; -contract ValuesTest is AddressSnapshotsBase { +contract AddressSnapshotValuesTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenNoValidatorsAreRegistered() public view { @@ -26,7 +26,13 @@ contract ValuesTest is AddressSnapshotsBase { validatorSet.add(address(2)); validatorSet.add(address(3)); + // Length remains 0 within this epoch address[] memory vals = validatorSet.values(); + assertEq(vals.length, 0); + + // Move to next epoch for changes to take effect + timeCheater.cheat__setEpochNow(2); + vals = validatorSet.values(); assertEq(vals.length, 3); assertEq(vals[0], address(1)); assertEq(vals[1], address(2)); @@ -43,6 +49,8 @@ contract ValuesTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(2); validatorSet.remove(address(2)); + timeCheater.cheat__setEpochNow(3); + address[] memory vals = validatorSet.values(); assertEq(vals.length, 2); assertEq(vals[0], address(1)); From bb0868acf491fd5931242d5f34c7c9b6bf6c74e8 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 10:32:04 +0000 Subject: [PATCH 17/43] chore: update other tests --- .../core/interfaces/IValidatorSelection.sol | 1 - .../libraries/staking/AddressSnapshotLib.sol | 35 +++++++++++++++++-- .../ValidatorSelectionLib.sol | 7 ++-- .../test/staking/initiateWithdraw.t.sol | 13 +++++++ l1-contracts/test/staking/slash.t.sol | 6 ++++ .../ValidatorSelection.t.sol | 4 ++- .../address_snapshots/at.t.sol | 5 +-- .../address_snapshots/values.t.sol | 17 +++++++++ .../address_snapshots/values.tree | 3 ++ 9 files changed, 81 insertions(+), 10 deletions(-) diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index 960975531314..51825e4db508 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -11,7 +11,6 @@ import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; * @param nextSeed - The seed used to influence the NEXT epoch */ struct EpochData { - uint256 validatorSetSize; // TODO: remove in favor of commitment to comittee address[] committee; uint256 sampleSeed; diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 93a700d9ae13..f47c4934a4a4 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -147,7 +147,7 @@ library AddressSnapshotLib { uint256 numCheckpoints = _self.checkpoints[_index].length; Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); - uint256 size = _self.size.lowerLookup(Epoch.unwrap(currentEpoch).toUint32()); + uint256 size = lengthAtEpoch(_self, currentEpoch); if (_index >= size) { revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); @@ -227,7 +227,18 @@ library AddressSnapshotLib { */ function length(SnapshottedAddressSet storage _self) internal view returns (uint256) { Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); - return _self.size.upperLookup(Epoch.unwrap(currentEpoch).toUint32()); + return lengthAtEpoch(_self, currentEpoch); + } + + // TODO(md): TEST! + /** + * @notice Gets the size of the set at a specific epoch + * @param _self The storage reference to the set + * @param _epoch The epoch number to query + * @return uint256 The number of addresses in the set at the given epoch + */ + function lengthAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) internal view returns (uint256) { + return _self.size.upperLookup(Epoch.unwrap(_epoch).toUint32()); } /** @@ -247,4 +258,24 @@ library AddressSnapshotLib { } return vals; } + + // TODO(md): TEST! + /** + * @notice Gets all addresses in the set at a specific epoch + * @param _self The storage reference to the set + * @param _epoch The epoch number to query + * @return address[] Array of all addresses in the set at the given epoch + */ + function valuesAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) internal view returns (address[] memory) { + uint256 size = lengthAtEpoch(_self, _epoch); + address[] memory vals = new address[](size); + for (uint256 i; i < size;) { + vals[i] = getAddressFromIndexAtEpoch(_self, i, _epoch); + + unchecked { + i++; + } + } + return vals; + } } diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index d02c067c3a1e..22731737b3db 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -49,8 +49,6 @@ library ValidatorSelectionLib { EpochData storage epoch = store.epochs[epochNumber]; if (epoch.sampleSeed == 0) { - epoch.validatorSetSize = _stakingStore.attesters.length(); - epoch.sampleSeed = getSampleSeed(epochNumber); epoch.nextSeed = store.lastSeed = computeNextSeed(epochNumber); epoch.committee = sampleValidators(_stakingStore, epochNumber, epoch.sampleSeed); @@ -166,7 +164,7 @@ library ValidatorSelectionLib { returns (address[] memory) { ValidatorSelectionStorage storage store = getStorage(); - uint256 validatorSetSize = store.epochs[_epoch].validatorSetSize; + uint256 validatorSetSize = _stakingStore.attesters.lengthAtEpoch(_epoch); if (validatorSetSize == 0) { return new address[](0); @@ -176,7 +174,7 @@ library ValidatorSelectionLib { // If we have less validators than the target committee size, we just return the full set if (validatorSetSize <= targetCommitteeSize) { - return _stakingStore.attesters.values(); + return _stakingStore.attesters.valuesAtEpoch(_epoch); } uint256[] memory indices = @@ -193,6 +191,7 @@ library ValidatorSelectionLib { internal returns (address[] memory) { + // TODO(md): remove this with checkpointing of the sample seed setupEpoch(_stakingStore); ValidatorSelectionStorage storage store = getStorage(); diff --git a/l1-contracts/test/staking/initiateWithdraw.t.sol b/l1-contracts/test/staking/initiateWithdraw.t.sol index 4c0e342b27b5..f08f2c9d1074 100644 --- a/l1-contracts/test/staking/initiateWithdraw.t.sol +++ b/l1-contracts/test/staking/initiateWithdraw.t.sol @@ -119,6 +119,12 @@ contract InitiateWithdrawTest is StakingBase { assertEq(exit.recipient, RECIPIENT); info = staking.getInfo(ATTESTER); assertTrue(info.status == Status.EXITING); + + // The active attester count should not change until we reach the next epoch + assertEq(staking.getActiveAttesterCount(), 1); + + // Move to next epoch for changes to take effect + staking.cheat__progressEpoch(); assertEq(staking.getActiveAttesterCount(), 0); } @@ -130,6 +136,12 @@ contract InitiateWithdrawTest is StakingBase { staking.cheat__SetStatus(ATTESTER, Status.LIVING); staking.cheat__RemoveAttester(ATTESTER); + // The active attester count should not change until we reach the next epoch + assertEq(staking.getActiveAttesterCount(), 1); + + // Progress into the next epoch for changes to take effect + staking.cheat__progressEpoch(); + assertEq(stakingAsset.balanceOf(address(staking)), MINIMUM_STAKE); assertEq(stakingAsset.balanceOf(RECIPIENT), 0); Exit memory exit = staking.getExit(ATTESTER); @@ -137,6 +149,7 @@ contract InitiateWithdrawTest is StakingBase { assertEq(exit.recipient, address(0)); ValidatorInfo memory info = staking.getInfo(ATTESTER); assertTrue(info.status == Status.LIVING); + assertEq(staking.getActiveAttesterCount(), 0); vm.expectEmit(true, true, true, true, address(staking)); diff --git a/l1-contracts/test/staking/slash.t.sol b/l1-contracts/test/staking/slash.t.sol index d35ac672b96a..b1edbe1c5eec 100644 --- a/l1-contracts/test/staking/slash.t.sol +++ b/l1-contracts/test/staking/slash.t.sol @@ -173,6 +173,12 @@ contract SlashTest is StakingBase { info = staking.getInfo(ATTESTER); assertEq(info.stake, balance - slashingAmount); assertTrue(info.status == Status.LIVING); + + // The active attester count should not change until we reach the next epoch + assertEq(staking.getActiveAttesterCount(), activeAttesterCount); + + // Move to next epoch for changes to take effect + staking.cheat__progressEpoch(); assertEq(staking.getActiveAttesterCount(), activeAttesterCount - 1); } } diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index 038a74c6acf5..53783a1c1011 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -125,13 +125,15 @@ contract ValidatorSelectionTest is DecoderBase { merkleTestUtil = new MerkleTestUtil(); + // Progress into the next epoch for changes to take effect + timeCheater.cheat__progressEpoch(); _; } function testInitialCommitteeMatch() public setup(4) { address[] memory attesters = rollup.getAttesters(); address[] memory committee = rollup.getCurrentEpochCommittee(); - assertEq(rollup.getCurrentEpoch(), 0); + assertEq(rollup.getCurrentEpoch(), 1); assertEq(attesters.length, 4, "Invalid validator set size"); assertEq(committee.length, 4, "invalid committee set size"); diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index 536fc5624333..61e0a4292bc2 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -8,8 +8,6 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; -import "forge-std/console.sol"; - contract AddressSnapshotAtTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; @@ -35,8 +33,11 @@ contract AddressSnapshotAtTest is AddressSnapshotsBase { validatorSet.add(address(2)); validatorSet.add(address(3)); + vm.expectRevert(); assertEq(validatorSet.at(0), address(0)); + vm.expectRevert(); assertEq(validatorSet.at(1), address(0)); + vm.expectRevert(); assertEq(validatorSet.at(2), address(0)); // it returns the correct validator after reordering diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index 99375674cba8..82a9dd0cbae4 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -39,6 +39,23 @@ contract AddressSnapshotValuesTest is AddressSnapshotsBase { assertEq(vals[2], address(3)); } + function test_WhenValidatorsHaveNotChangedForSomeTime() public { + // It returns array with correct length + // It returns array with correct addresses in order + + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); + + timeCheater.cheat__setEpochNow(100); + address[] memory vals = validatorSet.values(); + assertEq(vals.length, 3); + assertEq(vals[0], address(1)); + assertEq(vals[1], address(2)); + assertEq(vals[2], address(3)); + } + function test_WhenValidatorsAreRemoved() public { // It returns array of remaining validators timeCheater.cheat__setEpochNow(1); diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.tree b/l1-contracts/test/validator-selection/address_snapshots/values.tree index 8330cf3ea2f3..5b4b3fe64751 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/values.tree @@ -4,5 +4,8 @@ ValuesTest ├── when validators exist │ ├── it returns array with correct length │ └── it returns array with correct addresses in order +├── when validators have not changed for some time +│ └── it returns array with correct length +│ └── it returns array with correct addresses in order └── when validators are removed └── it returns array with remaining validators From b48560804262f4b857865d7e36c1d634c7de6a24 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 10:32:13 +0000 Subject: [PATCH 18/43] fix: staking asset handler tests --- l1-contracts/test/staking_asset_handler/addValidator.t.sol | 4 ++-- l1-contracts/test/staking_asset_handler/setMintInterval.t.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/l1-contracts/test/staking_asset_handler/addValidator.t.sol b/l1-contracts/test/staking_asset_handler/addValidator.t.sol index 749feb38015f..958b3bc8e050 100644 --- a/l1-contracts/test/staking_asset_handler/addValidator.t.sol +++ b/l1-contracts/test/staking_asset_handler/addValidator.t.sol @@ -76,7 +76,7 @@ contract AddValidatorTest is StakingAssetHandlerBase { } function test_WhenEnoughTimeHasPassedSinceLastMint( - uint256 _interval, + uint32 _interval, uint256 _depositsPerMint, address _attester, address _proposer @@ -113,7 +113,7 @@ contract AddValidatorTest is StakingAssetHandlerBase { } function test_WhenItDoesNotNeedToMint( - uint256 _interval, + uint32 _interval, uint256 _depositsPerMint, address _attester, address _proposer diff --git a/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol b/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol index f7227209c1cd..09bde96fe848 100644 --- a/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol +++ b/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol @@ -65,7 +65,7 @@ contract SetMintIntervalTest is StakingAssetHandlerBase { } function test_WhenOwnerTriesToMintAfterTheNewIntervalHasPassed(uint256 _newMintInterval) external { - _newMintInterval = bound(_newMintInterval, INITIAL_MINT_INTERVAL + 1, 1e18); + _newMintInterval = bound(_newMintInterval, INITIAL_MINT_INTERVAL + 1, 1e12); stakingAssetHandler.setMintInterval(_newMintInterval); vm.warp(block.timestamp + _newMintInterval); // it mints From dd2869d723d579a25b771142347cfd2cc219b5c2 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 10:33:04 +0000 Subject: [PATCH 19/43] fmt --- .../core/libraries/staking/AddressSnapshotLib.sol | 15 ++++++++++++--- .../getAddressFromIndexAtEpoch.t.sol | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index f47c4934a4a4..48fca7cc54da 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -86,7 +86,8 @@ library AddressSnapshotLib { uint256 lastIndex = _self.size.latest() - 1; uint256 lastSnapshotLength = _self.checkpoints[lastIndex].length; - uint256 nextEpoch = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)) + Epoch.wrap(1)); + uint256 nextEpoch = + Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)) + Epoch.wrap(1)); AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; @@ -237,7 +238,11 @@ library AddressSnapshotLib { * @param _epoch The epoch number to query * @return uint256 The number of addresses in the set at the given epoch */ - function lengthAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) internal view returns (uint256) { + function lengthAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) + internal + view + returns (uint256) + { return _self.size.upperLookup(Epoch.unwrap(_epoch).toUint32()); } @@ -266,7 +271,11 @@ library AddressSnapshotLib { * @param _epoch The epoch number to query * @return address[] Array of all addresses in the set at the given epoch */ - function valuesAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) internal view returns (address[] memory) { + function valuesAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) + internal + view + returns (address[] memory) + { uint256 size = lengthAtEpoch(_self, _epoch); address[] memory vals = new address[](size); for (uint256 i; i < size;) { diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol index 431e65f93877..d4f065bac7cf 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -8,6 +8,7 @@ import { SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; + contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; From 80160f54e514a2c3d3c29ff2b1abc25281929671 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:03:24 +0000 Subject: [PATCH 20/43] fix: bench test --- l1-contracts/test/benchmark/happy.t.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/l1-contracts/test/benchmark/happy.t.sol b/l1-contracts/test/benchmark/happy.t.sol index 82dbd69d4e6f..4e1c59858849 100644 --- a/l1-contracts/test/benchmark/happy.t.sol +++ b/l1-contracts/test/benchmark/happy.t.sol @@ -290,8 +290,9 @@ contract BenchmarkRollupTest is FeeModelTestPoints, DecoderBase { } function test_Benchmarking() public { - Slot nextSlot = Slot.wrap(1); - Epoch nextEpoch = Epoch.wrap(1); + // Do nothing for the first epoch + Slot nextSlot = Slot.wrap(EPOCH_DURATION + 1); + Epoch nextEpoch = Epoch.wrap(2); rollup.setProvingCostPerMana( EthValue.wrap(points[0].outputs.mana_base_fee_components_in_wei.proving_cost) From 91796ffb150717036fbfee1aac14ea8def9424eb Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:12:24 +0000 Subject: [PATCH 21/43] chore: update bench --- l1-contracts/gas_benchmark.md | 55 ++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/l1-contracts/gas_benchmark.md b/l1-contracts/gas_benchmark.md index be4974f1cde8..caf86da29bdb 100644 --- a/l1-contracts/gas_benchmark.md +++ b/l1-contracts/gas_benchmark.md @@ -1,25 +1,34 @@ -| src/core/Rollup.sol:Rollup Contract | | | | | | +| src/core/Rollup.sol:Rollup contract | | | | | | +|-------------------------------------|-----------------|----------|----------|----------|---------| | Deployment Cost | Deployment Size | | | | | -| 8154934 | 39929 | | | | | +| 8905012 | 44009 | | | | | | Function Name | min | avg | median | max | # calls | -| cheat__InitialiseValidatorSet | 15884661 | 15884661 | 15884661 | 15884661 | 1 | -| getBlock | 1230 | 1230 | 1230 | 1230 | 12 | -| getBurnAddress | 369 | 369 | 369 | 369 | 1 | -| getCurrentEpoch | 979 | 979 | 979 | 979 | 397 | -| getCurrentProposer | 135981 | 143548 | 136210 | 383219 | 200 | -| getCurrentSlot | 735 | 740 | 735 | 2735 | 397 | -| getEpochCommittee | 135880 | 143291 | 135892 | 382675 | 100 | -| getEpochForBlock | 1065 | 1065 | 1065 | 1065 | 196 | -| getFeeHeader | 1457 | 1457 | 1457 | 1457 | 95 | -| getManaBaseFeeAt | 20381 | 26792 | 27121 | 32242 | 195 | -| getPendingBlockNumber | 507 | 511 | 507 | 2507 | 401 | -| getProvenBlockNumber | 490 | 490 | 490 | 490 | 3 | -| getTimestampForSlot | 802 | 802 | 802 | 802 | 195 | -| setProvingCostPerMana | 25915 | 25942 | 25915 | 28715 | 101 | -| submitEpochRootProof | 767179 | 780017 | 767203 | 805669 | 3 | -| src/periphery/Forwarder.sol:Forwarder contract | | | | | | -|------------------------------------------------|-----------------|--------|--------|---------|---------| -| Deployment Cost | Deployment Size | | | | | -| 358690 | 1553 | | | | | -| Function Name | min | avg | median | max | # calls | -| forward | 630841 | 685437 | 641834 | 2057987 | 100 | +| cheat__InitialiseValidatorSet | 14773538 | 14773538 | 14773538 | 14773538 | 1 | +| getBlock | 1252 | 1252 | 1252 | 1252 | 12 | +| getBurnAddress | 280 | 280 | 280 | 280 | 1 | +| getCurrentEpoch | 870 | 870 | 870 | 870 | 490 | +| getCurrentProposer | 137000 | 164906 | 137000 | 1532365 | 200 | +| getCurrentSlot | 625 | 629 | 625 | 2625 | 490 | +| getEpochCommittee | 138682 | 138682 | 138682 | 138682 | 100 | +| getEpochForBlock | 1087 | 1087 | 1087 | 1087 | 196 | +| getFeeAssetPortal | 541 | 541 | 541 | 541 | 1 | +| getFeeHeader | 1479 | 1479 | 1479 | 1479 | 95 | +| getManaBaseFeeAt | 15466 | 20025 | 20350 | 24662 | 195 | +| getPendingBlockNumber | 398 | 402 | 398 | 2398 | 494 | +| getProvenBlockNumber | 512 | 512 | 512 | 512 | 3 | +| getTimestampForSlot | 824 | 824 | 824 | 824 | 195 | +| getVersion | 404 | 424 | 404 | 2404 | 100 | +| setProvingCostPerMana | 25937 | 25964 | 25937 | 28737 | 101 | +| submitEpochRootProof | 783299 | 796011 | 783311 | 821423 | 3 | +| src/core/messagebridge/Inbox.sol:Inbox contract | | | | | | +|-------------------------------------------------|-----------------|-----|--------|-----|---------| +| Deployment Cost | Deployment Size | | | | | +| 0 | 0 | | | | | +| Function Name | min | avg | median | max | # calls | +| getFeeAssetPortal | 234 | 234 | 234 | 234 | 1 | +| src/periphery/Forwarder.sol:Forwarder contract | | | | | | +|------------------------------------------------|-----------------|--------|--------|--------|---------| +| Deployment Cost | Deployment Size | | | | | +| 358690 | 1553 | | | | | +| Function Name | min | avg | median | max | # calls | +| forward | 624506 | 630841 | 629469 | 640840 | 100 | From 5186c3944cd08aa68312bea955e2e7c9e1ccb714 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:20:54 +0000 Subject: [PATCH 22/43] chore: add length and values at epoch test --- .../libraries/staking/AddressSnapshotLib.sol | 2 -- .../address_snapshots/length.t.sol | 9 ++++++++ .../address_snapshots/values.t.sol | 21 ++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 48fca7cc54da..d3c4d66a1dd6 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -231,7 +231,6 @@ library AddressSnapshotLib { return lengthAtEpoch(_self, currentEpoch); } - // TODO(md): TEST! /** * @notice Gets the size of the set at a specific epoch * @param _self The storage reference to the set @@ -264,7 +263,6 @@ library AddressSnapshotLib { return vals; } - // TODO(md): TEST! /** * @notice Gets all addresses in the set at a specific epoch * @param _self The storage reference to the set diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol index 09f316647c7e..2f7cce2d8284 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol @@ -34,6 +34,10 @@ contract AddressSnapshotLengthTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(2); // Length increases to 2 assertEq(validatorSet.length(), 2); + + // Length at epoch maintains historical values + assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(1)), 0); + assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), 2); } function test_WhenRemovingValidators() public { @@ -52,5 +56,10 @@ contract AddressSnapshotLengthTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(3); assertEq(validatorSet.length(), 0); + + // Length at epoch maintains historical values + assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(1)), 0); + assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), 2); + assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(3)), 0); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index 82a9dd0cbae4..bd7b86600db6 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -7,7 +7,7 @@ import { SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; - +import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; contract AddressSnapshotValuesTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; @@ -54,6 +54,10 @@ contract AddressSnapshotValuesTest is AddressSnapshotsBase { assertEq(vals[0], address(1)); assertEq(vals[1], address(2)); assertEq(vals[2], address(3)); + + // Values at epoch maintains historical values + address[] memory valsAtEpoch = validatorSet.valuesAtEpoch(Epoch.wrap(1)); + assertEq(valsAtEpoch.length, 0); } function test_WhenValidatorsAreRemoved() public { @@ -72,5 +76,20 @@ contract AddressSnapshotValuesTest is AddressSnapshotsBase { assertEq(vals.length, 2); assertEq(vals[0], address(1)); assertEq(vals[1], address(3)); + + // Values at epoch maintains historical values + address[] memory valsAtEpoch = validatorSet.valuesAtEpoch(Epoch.wrap(1)); + assertEq(valsAtEpoch.length, 0); + + valsAtEpoch = validatorSet.valuesAtEpoch(Epoch.wrap(2)); + assertEq(valsAtEpoch.length, 3); + assertEq(valsAtEpoch[0], address(1)); + assertEq(valsAtEpoch[1], address(2)); + assertEq(valsAtEpoch[2], address(3)); + + valsAtEpoch = validatorSet.valuesAtEpoch(Epoch.wrap(3)); + assertEq(valsAtEpoch.length, 2); + assertEq(valsAtEpoch[0], address(1)); + assertEq(valsAtEpoch[1], address(3)); } } From 999c6b7a0186a21885cb44f1461995bead8ee1d4 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:19:16 +0000 Subject: [PATCH 23/43] chore: less optimizer runs --- l1-contracts/bootstrap.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/l1-contracts/bootstrap.sh b/l1-contracts/bootstrap.sh index 62f38ac901ea..fd3d6c7cffb0 100755 --- a/l1-contracts/bootstrap.sh +++ b/l1-contracts/bootstrap.sh @@ -37,7 +37,7 @@ function build { # Compile contracts # Step 1: Build everything in src. - forge build $(find src test -name '*.sol') + forge build $(find src test -name '*.sol') --optimizer-runs 1 # Currently lowering to make small enough to deploy. # Step 1.5: Output storage information for the rollup contract. forge inspect --json src/core/Rollup.sol:Rollup storage > ./out/Rollup.sol/storage.json @@ -106,6 +106,7 @@ function inspect { done } + function gas_report { check=${1:-"no"} echo_header "l1-contracts gas report" From 2b111ed7f06e1d0673e35a577be67499f0fa2ccd Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:51:23 +0000 Subject: [PATCH 24/43] chore: remove bytecode hash --- l1-contracts/bootstrap.sh | 2 +- l1-contracts/foundry.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/l1-contracts/bootstrap.sh b/l1-contracts/bootstrap.sh index fd3d6c7cffb0..758fd4a8c0b6 100755 --- a/l1-contracts/bootstrap.sh +++ b/l1-contracts/bootstrap.sh @@ -37,7 +37,7 @@ function build { # Compile contracts # Step 1: Build everything in src. - forge build $(find src test -name '*.sol') --optimizer-runs 1 # Currently lowering to make small enough to deploy. + forge build $(find src test -name '*.sol') # Step 1.5: Output storage information for the rollup contract. forge inspect --json src/core/Rollup.sol:Rollup storage > ./out/Rollup.sol/storage.json diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 13c51f8addf2..5b2ca66a382a 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -7,6 +7,8 @@ libs = ['lib'] solc = "0.8.27" evm_version = 'cancun' optimizer = true +optimizer_runs = 1 +bytecode_hash = "none" match_path = "test/**/*.t.sol" # Helper to get all the contract names in the src/governance and src/core directories From c912aa1eb03b61bdc862b30b7f2e5b9eb0c8f45a Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:23:27 +0000 Subject: [PATCH 25/43] chore: remove unneeded setup epoch calls --- l1-contracts/src/core/RollupCore.sol | 2 -- .../libraries/validator-selection/ValidatorSelectionLib.sol | 3 --- 2 files changed, 5 deletions(-) diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index eb443a15929c..20dc0daf00f7 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -176,7 +176,6 @@ contract RollupCore is external override(IStakingCore) { - setupEpoch(); StakingLib.deposit(_attester, _proposer, _withdrawer, _amount); } @@ -185,7 +184,6 @@ contract RollupCore is override(IStakingCore) returns (bool) { - setupEpoch(); return StakingLib.initiateWithdraw(_attester, _recipient); } diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 22731737b3db..b58395276d9f 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -191,9 +191,6 @@ library ValidatorSelectionLib { internal returns (address[] memory) { - // TODO(md): remove this with checkpointing of the sample seed - setupEpoch(_stakingStore); - ValidatorSelectionStorage storage store = getStorage(); EpochData storage epoch = store.epochs[_epochNumber]; From 1c9822d8c25bf6893ae9e1b0f87b7fb2fb50c436 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:47:35 +0000 Subject: [PATCH 26/43] chore: expect revert wrapper lib --- .../address_snapshots/at.t.sol | 88 +++++++++++++------ .../address_snapshots/values.t.sol | 1 + 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index 61e0a4292bc2..e387af7927c9 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -6,54 +6,88 @@ import { AddressSnapshotLib, SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; -import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; +import {Test} from "forge-std/Test.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {TimeCheater} from "../../staking/TimeCheater.sol"; -contract AddressSnapshotAtTest is AddressSnapshotsBase { +contract ExpectRevertWrapper { using AddressSnapshotLib for SnapshottedAddressSet; + SnapshottedAddressSet validatorSet; + + function at(uint256 index) public view returns (address) { + return validatorSet.at(index); + } + + function add(address validator) public { + validatorSet.add(validator); + } + + function remove(uint256 index) public { + validatorSet.remove(index); + } +} + +contract AddressSnapshotAtTest is Test { + ExpectRevertWrapper expectRevertWrapper; + TimeCheater timeCheater; + + function setUp() public { + expectRevertWrapper = new ExpectRevertWrapper(); + timeCheater = new TimeCheater(address(expectRevertWrapper), block.timestamp, 1, 1); + } + function test_WhenNoValidatorsAreRegistered() public { // It reverts - vm.expectRevert(); - validatorSet.at(0); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + expectRevertWrapper.at(0); } function test_WhenIndexIsOutOfBounds() public { - // It reverts - timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); + expectRevertWrapper.add(address(1)); - vm.expectRevert(); - validatorSet.at(1); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) + ); + expectRevertWrapper.at(1); } function test_WhenIndexIsValid() public { // it returns the current validator at that index timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - validatorSet.add(address(2)); - validatorSet.add(address(3)); + expectRevertWrapper.add(address(1)); + expectRevertWrapper.add(address(2)); + expectRevertWrapper.add(address(3)); - vm.expectRevert(); - assertEq(validatorSet.at(0), address(0)); - vm.expectRevert(); - assertEq(validatorSet.at(1), address(0)); - vm.expectRevert(); - assertEq(validatorSet.at(2), address(0)); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + expectRevertWrapper.at(0); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) + ); + expectRevertWrapper.at(1); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 2, 0) + ); + expectRevertWrapper.at(2); // it returns the correct validator after reordering timeCheater.cheat__setEpochNow(2); - assertEq(validatorSet.at(0), address(1)); - assertEq(validatorSet.at(1), address(2)); - assertEq(validatorSet.at(2), address(3)); + assertEq(expectRevertWrapper.at(0), address(1)); + assertEq(expectRevertWrapper.at(1), address(2)); + assertEq(expectRevertWrapper.at(2), address(3)); - validatorSet.remove(1); + expectRevertWrapper.remove(1); - assertEq(validatorSet.at(0), address(1)); - assertEq(validatorSet.at(1), address(2)); - assertEq(validatorSet.at(2), address(3)); + assertEq(expectRevertWrapper.at(0), address(1)); + assertEq(expectRevertWrapper.at(1), address(2)); + assertEq(expectRevertWrapper.at(2), address(3)); timeCheater.cheat__setEpochNow(3); - assertEq(validatorSet.at(0), address(1)); - assertEq(validatorSet.at(1), address(3)); + assertEq(expectRevertWrapper.at(0), address(1)); + assertEq(expectRevertWrapper.at(1), address(3)); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index bd7b86600db6..066e297ebb2e 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -8,6 +8,7 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; + contract AddressSnapshotValuesTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; From 4869a4160e3360131fa2b96ae526207f20eb2084 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 15 Apr 2025 15:50:18 +0000 Subject: [PATCH 27/43] fix: merge update --- l1-contracts/test/staking_asset_handler/setMintInterval.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol b/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol index d07aa8126c8a..37a9409b7cd4 100644 --- a/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol +++ b/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol @@ -51,7 +51,7 @@ contract SetMintIntervalTest is StakingAssetHandlerBase { } function test_WhenOwnerTriesToMintAfterTheNewIntervalHasPassed(uint256 _newMintInterval) external { - _newMintInterval = bound(_newMintInterval, mintInterval + 1, 1e18); + _newMintInterval = bound(_newMintInterval, mintInterval + 1, 1e12); stakingAssetHandler.setMintInterval(_newMintInterval); vm.warp(block.timestamp + _newMintInterval); // it mints From 8eb8e35c53c15e405243fbe31976c8dec11d48cc Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:23:36 +0000 Subject: [PATCH 28/43] fix: migrate to checkpoint lib --- .../libraries/staking/AddressSnapshotLib.sol | 142 +++++------------- .../address_snapshots/at.t.sol | 2 +- 2 files changed, 39 insertions(+), 105 deletions(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index d3c4d66a1dd6..fa5e99289b32 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -11,24 +11,19 @@ import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol"; * @notice Structure to store a set of addresses with their historical snapshots * @param size The current number of addresses in the set * @param checkpoints Mapping of index to array of address snapshots - * @param validatorToIndex Mapping of validator address to its current index in the set + * @param attestorToIndex Mapping of attestor address to its current index in the set */ struct SnapshottedAddressSet { // This size must also be snapshotted Checkpoints.Trace224 size; - mapping(uint256 index => AddressSnapshot[]) checkpoints; - // Store up to date position for each validator - mapping(address validator => uint256 index) validatorToIndex; + mapping(uint256 index => Checkpoints.Trace224) checkpoints; + // Store up to date position for each address + mapping(address addr => Index index) addressToIndex; } -/** - * @notice Structure to store a single address snapshot - * @param addr The address being snapshotted - * @param epochNumber The epoch number when this snapshot was taken - */ -struct AddressSnapshot { - address addr; - uint96 epochNumber; +struct Index { + bool exists; + uint224 index; } /** @@ -38,7 +33,7 @@ struct AddressSnapshot { * and allows querying the state of addresses at any point in time */ library AddressSnapshotLib { - using SafeCast for uint256; + using SafeCast for *; using Checkpoints for Checkpoints.Trace224; /** @@ -49,27 +44,25 @@ library AddressSnapshotLib { /** * @notice Adds a validator to the set * @param _self The storage reference to the set - * @param _validator The address of the validator to add - * @return bool True if the validator was added, false if it was already present + * @param _address The address of the address to add + * @return bool True if the address was added, false if it was already present */ - function add(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { + function add(SnapshottedAddressSet storage _self, address _address) internal returns (bool) { // Prevent against double insertion - if (_self.validatorToIndex[_validator] != 0) { + if (_self.addressToIndex[_address].exists) { return false; } // Insert into the end of the array Epoch _nextEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)) + Epoch.wrap(1); - uint256 index = _self.size.latest(); + uint224 index = _self.size.latest(); // Include max bit to indicate that the validator is in the set - means 0 index is not 0 - uint256 indexWithBit = index | PRESENCE_BIT; + Index memory storedIndex = Index({exists: true, index: index}); - _self.validatorToIndex[_validator] = indexWithBit; - _self.checkpoints[index].push( - AddressSnapshot({addr: _validator, epochNumber: Epoch.unwrap(_nextEpoch).toUint96()}) - ); + _self.addressToIndex[_address] = storedIndex; + _self.checkpoints[index].push(Epoch.unwrap(_nextEpoch).toUint32(), uint160(_address).toUint224()); _self.size.push(Epoch.unwrap(_nextEpoch).toUint32(), (index + 1).toUint224()); return true; @@ -81,40 +74,30 @@ library AddressSnapshotLib { * @param _index The index of the validator to remove * @return bool True if the validator was removed, false otherwise */ - function remove(SnapshottedAddressSet storage _self, uint256 _index) internal returns (bool) { + function remove(SnapshottedAddressSet storage _self, uint224 _index) internal returns (bool) { // To remove from the list, we push the last item into the index and reduce the size - uint256 lastIndex = _self.size.latest() - 1; - uint256 lastSnapshotLength = _self.checkpoints[lastIndex].length; - + uint224 lastIndex = _self.size.latest() - 1; uint256 nextEpoch = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)) + Epoch.wrap(1)); - AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; + address lastValidator = address(_self.checkpoints[lastIndex].latest().toUint160()); - address lastValidator = lastSnapshot.addr; - uint256 newLocation = _index | PRESENCE_BIT; + Index memory newLocation = Index({exists: true, index: _index.toUint224()}); // If we are removing the last item, we cannot swap it with anything // so we append a new address of zero for this epoch // And since we are removing it, we set the location to 0 if (lastIndex == _index) { - lastSnapshot.addr = address(0); - newLocation = 0; - } - - _self.validatorToIndex[lastValidator] = newLocation; + newLocation.exists = false; + newLocation.index = 0; + _self.addressToIndex[lastValidator] = newLocation; - lastSnapshot.epochNumber = nextEpoch.toUint96(); - // Check if there's already a checkpoint for this index in the current epoch - uint256 checkpointCount = _self.checkpoints[_index].length; - if ( - checkpointCount > 0 && _self.checkpoints[_index][checkpointCount - 1].epochNumber == nextEpoch - ) { - // If there's already a checkpoint for this epoch, update it instead of adding a new one - _self.checkpoints[_index][checkpointCount - 1] = lastSnapshot; + _self.checkpoints[_index].push(nextEpoch.toUint32(), uint160(0).toUint224()); } else { - // Otherwise add a new checkpoint - _self.checkpoints[_index].push(lastSnapshot); + // Otherwise, we swap the last item with the item we are removing + // and update the location of the last item + _self.addressToIndex[lastValidator] = newLocation; + _self.checkpoints[_index].push(nextEpoch.toUint32(), uint160(lastValidator).toUint224()); } _self.size.push(nextEpoch.toUint32(), (lastIndex).toUint224()); @@ -122,50 +105,34 @@ library AddressSnapshotLib { } /** - * @notice Removes a validator from the set by address + * @notice Removes a address from the set by address * @param _self The storage reference to the set - * @param _validator The address of the validator to remove - * @return bool True if the validator was removed, false if it wasn't found + * @param _address The address of the address to remove + * @return bool True if the address was removed, false if it wasn't found */ - function remove(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { - uint256 index = _self.validatorToIndex[_validator]; - if (index == 0) { + function remove(SnapshottedAddressSet storage _self, address _address) internal returns (bool) { + Index memory index = _self.addressToIndex[_address]; + if (!index.exists) { return false; } - // Remove top most bit - index = index & ~PRESENCE_BIT; - return remove(_self, index); + return remove(_self, index.index); } /** - * @notice Gets the current address at a specific index + * @notice Gets the current address at a specific index at the time right now * @param _self The storage reference to the set * @param _index The index to query * @return address The current address at the given index */ function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { - uint256 numCheckpoints = _self.checkpoints[_index].length; Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); uint256 size = lengthAtEpoch(_self, currentEpoch); - if (_index >= size) { revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); } - if (numCheckpoints == 0) { - return address(0); - } - - AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1]; - - // The staking set is frozen in time until the end of the epoch, so we must check if the last checkpoint is in the current epoch - // If it is, we can return it, otherwise, we must perform a search - if (lastSnapshot.epochNumber == Epoch.unwrap(currentEpoch)) { - return lastSnapshot.addr; - } - // Otherwise, we must perform a search return getAddressFromIndexAtEpoch(_self, _index, currentEpoch); } @@ -182,43 +149,10 @@ library AddressSnapshotLib { uint256 _index, Epoch _epoch ) internal view returns (address) { - uint256 numCheckpoints = _self.checkpoints[_index].length; uint96 epoch = Epoch.unwrap(_epoch).toUint96(); - if (numCheckpoints == 0) { - return address(0); - } - - // Check the most recent checkpoint - AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1]; - if (lastSnapshot.epochNumber <= epoch) { - return lastSnapshot.addr; - } - - // Binary search - uint256 lower = 0; - - unchecked { - uint256 upper = numCheckpoints - 1; - while (upper > lower) { - uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow - AddressSnapshot memory cp = _self.checkpoints[_index][center]; - if (cp.epochNumber == epoch) { - return cp.addr; - } else if (cp.epochNumber < epoch) { - lower = center; - } else { - upper = center - 1; - } - } - } - - // Guard against the found epoch being greater than the epoch we are querying - if (epoch < _self.checkpoints[_index][lower].epochNumber) { - return address(0); - } - - return _self.checkpoints[_index][lower].addr; + uint224 addr = _self.checkpoints[_index].upperLookup(epoch.toUint32()); + return address(addr.toUint160()); } /** diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index e387af7927c9..29999302d53a 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -23,7 +23,7 @@ contract ExpectRevertWrapper { validatorSet.add(validator); } - function remove(uint256 index) public { + function remove(uint224 index) public { validatorSet.remove(index); } } From 8d774e70c2a4b641d63361f92f634d8ea70092f0 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:23:56 +0000 Subject: [PATCH 29/43] exp: remove foundry changes --- l1-contracts/foundry.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 5b2ca66a382a..13c51f8addf2 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -7,8 +7,6 @@ libs = ['lib'] solc = "0.8.27" evm_version = 'cancun' optimizer = true -optimizer_runs = 1 -bytecode_hash = "none" match_path = "test/**/*.t.sol" # Helper to get all the contract names in the src/governance and src/core directories From e2213d1e1bee28b85d4918b2eaf979f70a012beb Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:29:02 +0000 Subject: [PATCH 30/43] comments --- .../address_snapshots/AddressSnapshotsBase.t.sol | 5 +++-- .../test/validator-selection/address_snapshots/add.t.sol | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol index 050ceb889c3a..e1b84b3c0d56 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol @@ -10,12 +10,13 @@ import { import {Test} from "forge-std/Test.sol"; import {TimeLib, TimeStorage, Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {TimeCheater} from "../../staking/TimeCheater.sol"; +import {TestConstants} from "../../harnesses/TestConstants.sol"; contract AddressSnapshotsBase is Test { using AddressSnapshotLib for SnapshottedAddressSet; - uint256 private constant SLOT_DURATION = 36; - uint256 private constant EPOCH_DURATION = 48; + uint256 private constant SLOT_DURATION = TestConstants.AZTEC_SLOT_DURATION; + uint256 private constant EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION; uint256 private immutable GENESIS_TIME = block.timestamp; SnapshottedAddressSet validatorSet; diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index 01462238f9c9..a1dd563259f2 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -25,7 +25,7 @@ contract AddressSnapshotAddTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(2); assertEq(validatorSet.length(), 1); - // Epoch remains frozen, so this number should not update + // Epoch 0 remains frozen, so this number should not update assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(0)); assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); } From d165b11f03c1257660d4f1b185c3271dc4caeb62 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:38:41 +0000 Subject: [PATCH 31/43] fmt --- .../src/core/libraries/staking/AddressSnapshotLib.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index fa5e99289b32..9662fed6fe67 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -62,7 +62,9 @@ library AddressSnapshotLib { Index memory storedIndex = Index({exists: true, index: index}); _self.addressToIndex[_address] = storedIndex; - _self.checkpoints[index].push(Epoch.unwrap(_nextEpoch).toUint32(), uint160(_address).toUint224()); + _self.checkpoints[index].push( + Epoch.unwrap(_nextEpoch).toUint32(), uint160(_address).toUint224() + ); _self.size.push(Epoch.unwrap(_nextEpoch).toUint32(), (index + 1).toUint224()); return true; From 2141087d6ecf893a523b090de5f43ac837205998 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:47:51 +0000 Subject: [PATCH 32/43] WARNING: NO ATTESTERS ON THE ORIGINAL COMMITTEE --- .../src/e2e_p2p/gossip_network_no_cheat.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts b/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts index 2eb54fb5b862..853258c1e0a6 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/gossip_network_no_cheat.test.ts @@ -121,9 +121,9 @@ describe('e2e_p2p_network', () => { }); } - const attesters = await rollup.read.getAttesters(); - expect(attesters.length).toBe(validators.length); - expect(attesters.length).toBe(NUM_NODES); + // Changes do not take effect until the next epoch + const attestersImmedatelyAfterAdding = await rollup.read.getAttesters(); + expect(attestersImmedatelyAfterAdding.length).toBe(0); // Check that the validators are added correctly const withdrawer = await stakingAssetHandler.read.withdrawer(); @@ -142,6 +142,11 @@ describe('e2e_p2p_network', () => { t.logger.debug('Warp failed, time already satisfied'); } + // Changes have now taken effect + const attesters = await rollup.read.getAttesters(); + expect(attesters.length).toBe(validators.length); + expect(attesters.length).toBe(NUM_NODES); + // Send and await a tx to make sure we mine a block for the warp to correctly progress. await t.ctx.deployL1ContractsValues.publicClient.waitForTransactionReceipt({ hash: await t.ctx.deployL1ContractsValues.walletClient.sendTransaction({ From 9e7579ca31fb0568713ead01b5200a8c5307b161 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 16 Apr 2025 22:20:39 +0000 Subject: [PATCH 33/43] fix: update slashing tesst --- yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts b/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts index b7a08b22ff4c..5499b3158395 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts @@ -317,7 +317,9 @@ describe('e2e_p2p_slashing', () => { // Check that status is Living expect(attesterInfo.status).toEqual(2); } - const committee = await rollup.getEpochCommittee(targetEpoch); + + // Committee should only update in the next epoch + const committee = await rollup.getEpochCommittee(targetEpoch + 1n); expect(attestersPre.length).toBe(committee.length); expect(attestersPost.length).toBe(0); }, 1_000_000); From 698e969c6cfff755f73d9004056a1f1d1d3ac3f5 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 10:45:36 +0000 Subject: [PATCH 34/43] fix: slashing test take 2 --- .../aztec.js/src/test/rollup_cheat_codes.ts | 24 +++++++- .../end-to-end/src/e2e_p2p/p2p_network.ts | 39 +++++++----- .../end-to-end/src/e2e_p2p/slashing.test.ts | 61 +++++++++++++++++-- 3 files changed, 104 insertions(+), 20 deletions(-) diff --git a/yarn-project/aztec.js/src/test/rollup_cheat_codes.ts b/yarn-project/aztec.js/src/test/rollup_cheat_codes.ts index efcf163ab9cf..93eb6812869c 100644 --- a/yarn-project/aztec.js/src/test/rollup_cheat_codes.ts +++ b/yarn-project/aztec.js/src/test/rollup_cheat_codes.ts @@ -1,4 +1,4 @@ -import type { ViemPublicClient } from '@aztec/ethereum'; +import { RollupContract, type ViemPublicClient } from '@aztec/ethereum'; import { EthCheatCodes } from '@aztec/ethereum/eth-cheatcodes'; import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -54,6 +54,28 @@ export class RollupCheatCodes { }; } + /** + * Logs the current state of the rollup contract. + */ + public async debugRollup() { + const rollup = new RollupContract(this.client, this.rollup.address); + const pendingNum = await rollup.getBlockNumber(); + const provenNum = await rollup.getProvenBlockNumber(); + const validators = await rollup.getAttesters(); + const committee = await rollup.getCurrentEpochCommittee(); + const archive = await rollup.archive(); + const epochNum = await rollup.getEpochNumber(); + const slot = await this.getSlot(); + + this.logger.info(`Pending block num: ${pendingNum}`); + this.logger.info(`Proven block num: ${provenNum}`); + this.logger.info(`Validators: ${validators.map(v => v.toString()).join(', ')}`); + this.logger.info(`Committee: ${committee.map(v => v.toString()).join(', ')}`); + this.logger.info(`Archive: ${archive}`); + this.logger.info(`Epoch num: ${epochNum}`); + this.logger.info(`Slot: ${slot}`); + } + /** Fetches the epoch and slot duration config from the rollup contract */ public async getConfig(): Promise<{ /** Epoch duration */ epochDuration: bigint; diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index e709bea79933..35f4da4a67a4 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -13,7 +13,7 @@ import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees'; import { getGenesisValues } from '@aztec/world-state/testing'; import getPort from 'get-port'; -import { getContract } from 'viem'; +import { type PublicClient, type WalletClient, getContract } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { @@ -232,13 +232,7 @@ export class P2PNetworkTest { } // Send and await a tx to make sure we mine a block for the warp to correctly progress. - await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ - hash: await deployL1ContractsValues.walletClient.sendTransaction({ - to: this.baseAccount.address, - value: 1n, - account: this.baseAccount, - }), - }); + await this._sendDummyTx(deployL1ContractsValues.publicClient, deployL1ContractsValues.walletClient); // Set the system time in the node, only after we have warped the time and waited for a block // Time is only set in the NEXT block @@ -284,13 +278,10 @@ export class P2PNetworkTest { 'remove-inital-validator', async ({ deployL1ContractsValues, aztecNode, dateProvider }) => { // Send and await a tx to make sure we mine a block for the warp to correctly progress. - const receipt = await deployL1ContractsValues.publicClient.waitForTransactionReceipt({ - hash: await deployL1ContractsValues.walletClient.sendTransaction({ - to: this.baseAccount.address, - value: 1n, - account: this.baseAccount, - }), - }); + const receipt = await this._sendDummyTx( + deployL1ContractsValues.publicClient, + deployL1ContractsValues.walletClient, + ); const block = await deployL1ContractsValues.publicClient.getBlock({ blockNumber: receipt.blockNumber, }); @@ -301,6 +292,24 @@ export class P2PNetworkTest { ); } + async sendDummyTx() { + return await this._sendDummyTx( + this.ctx.deployL1ContractsValues.publicClient, + this.ctx.deployL1ContractsValues.walletClient, + ); + } + + private async _sendDummyTx(publicClient: PublicClient, walletClient: WalletClient) { + return await publicClient.waitForTransactionReceipt({ + hash: await walletClient.sendTransaction({ + to: walletClient.account!.address, + value: 1n, + account: walletClient.account!, + chain: publicClient.chain, + }), + }); + } + async setup() { this.ctx = await this.snapshotManager.setup(); diff --git a/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts b/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts index 5499b3158395..7ee0407201b6 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts @@ -100,6 +100,10 @@ describe('e2e_p2p_slashing', () => { return { roundNumber: await slashingProposer.read.computeRound([slotNumber]), slotNumber }; }; + const debugRollup = async () => { + await t.ctx.cheatCodes.rollup.debugRollup(); + }; + /** * Get the slashing info for a given round number. * @param roundNumber - The round number to get the slashing info for. @@ -127,6 +131,14 @@ describe('e2e_p2p_slashing', () => { t.ctx.aztecNodeConfig.validatorReexecute = false; t.ctx.aztecNodeConfig.minTxsPerBlock = 0; + // Jump forward to an epoch in the future such that the validator set is not empty + const slotsInEpoch = await rollup.getEpochDuration(); + const epochToJumpInto = 4n; + const timestamp = await rollup.getTimestampForSlot(slotsInEpoch * epochToJumpInto); + await t.ctx.cheatCodes.eth.warp(Number(timestamp)); + // Send tx + await t.sendDummyTx(); + // create our network of nodes and submit txs into each of them // the number of txs per node and the number of txs per rollup // should be set so that the only way for rollups to be built @@ -155,11 +167,15 @@ describe('e2e_p2p_slashing', () => { slasher.slashingAmount = slashingAmount; } + await debugRollup(); + // wait a bit for peers to discover each other await sleep(4000); const votesNeeded = await slashingProposer.read.N(); + await debugRollup(); + // Produce blocks until we hit an issue with pruning. // Then we should jump in time to the next round so we are sure that we have the votes // Then we just sit on our hands and wait. @@ -172,8 +188,12 @@ describe('e2e_p2p_slashing', () => { const slasher = (sequencer as any).slasherClient; let slashEvents: any[] = []; + await debugRollup(); + t.logger.info(`Producing blocks until we hit a pruning event`); + await debugRollup(); + // Run for up to the slashing round size, or as long as needed to get a slash event // Variable because sometimes hit race-condition issues with attestations. for (let i = 0; i < slashingRoundSize; i++) { @@ -195,6 +215,8 @@ describe('e2e_p2p_slashing', () => { } } + await debugRollup(); + expect(slashEvents.length).toBeGreaterThan(0); await waitUntilNextRound(); @@ -202,6 +224,8 @@ describe('e2e_p2p_slashing', () => { const { roundNumber, slotNumber } = await getRoundAndSlotNumber(); let sInfo = await slashingInfo(roundNumber); + await debugRollup(); + // For the next round we will try to cast votes. // Stop early if we have enough votes. t.logger.info(`Waiting for votes to be cast`); @@ -215,8 +239,15 @@ describe('e2e_p2p_slashing', () => { sInfo = await slashingInfo(roundNumber); t.logger.info(`We have ${sInfo.leaderVotes} votes in round ${sInfo.roundNumber} on ${sInfo.info[1]}`); if (sInfo.leaderVotes >= votesNeeded) { - t.logger.info(`We have sufficient votes`); - break; + // We need there to be an actual committee to slash for this round + const epoch = await rollup.getEpochNumberForSlotNumber(sInfo.slotNumber); + const committee = await rollup.getEpochCommittee(epoch); + if (committee.length > 0) { + t.logger.info(`We have sufficient votes, and a committee for epoch ${epoch}`); + break; + } else { + t.logger.info(`No committee found for epoch ${epoch}, waiting for next round`); + } } } @@ -228,6 +259,8 @@ describe('e2e_p2p_slashing', () => { // we don't have that in place. const targetAddress = sInfo.info[1]; + await debugRollup(); + let targetEpoch = 0n; for (let i = 0; i <= slotNumber; i++) { const epoch = await rollup.getEpochNumberForSlotNumber(BigInt(i)); @@ -239,6 +272,8 @@ describe('e2e_p2p_slashing', () => { } } + await debugRollup(); + await l1TxUtils.sendAndMonitorTransaction({ to: slashFactory.address, data: encodeFunctionData({ @@ -248,6 +283,8 @@ describe('e2e_p2p_slashing', () => { }), }); + await debugRollup(); + t.logger.info(`Slash payload for ${targetEpoch}, ${slashingAmount} deployed at ${targetAddress}`); t.logger.info( `Committee for epoch ${targetEpoch}: ${(await rollup.getEpochCommittee(targetEpoch)).map(addr => @@ -255,6 +292,8 @@ describe('e2e_p2p_slashing', () => { )}`, ); + await debugRollup(); + t.logger.info(`We wait until next round to execute the payload`); await waitUntilNextRound(); const attestersPre = await rollup.getAttesters(); @@ -286,6 +325,8 @@ describe('e2e_p2p_slashing', () => { t.logger.info(`Performed slash in ${receipt.transactionHash}`); + await debugRollup(); + const slashingEvents = parseEventLogs({ abi: RollupAbi, logs: receipt.logs, @@ -312,15 +353,27 @@ describe('e2e_p2p_slashing', () => { const attestersPost = await rollup.getAttesters(); + // Attesters next epoch + await t.ctx.cheatCodes.rollup.advanceToNextEpoch(); + // Send tx + await t.sendDummyTx(); + + // Slashed parties should be removed from the validator set in the next epoch + const attestersNextEpoch = await rollup.getAttesters(); + for (const attester of attestersPre) { const attesterInfo = await rollup.getInfo(attester); // Check that status is Living expect(attesterInfo.status).toEqual(2); } + await debugRollup(); + // Committee should only update in the next epoch - const committee = await rollup.getEpochCommittee(targetEpoch + 1n); + const committee = await rollup.getEpochCommittee(targetEpoch); expect(attestersPre.length).toBe(committee.length); - expect(attestersPost.length).toBe(0); + expect(attestersPost.length).toBe(committee.length); + + expect(attestersNextEpoch.length).toBe(0); }, 1_000_000); }); From 4c5086c41ca8cc215b28fd3b5e6f85ecc9255bc6 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:53:45 +0000 Subject: [PATCH 35/43] chore: update --- l1-contracts/src/core/libraries/Errors.sol | 3 +- .../libraries/staking/AddressSnapshotLib.sol | 65 ++++++-------- .../AddressSnapshotsBase.t.sol | 47 +++++++++- .../address_snapshots/add.t.sol | 10 ++- .../address_snapshots/at.t.sol | 65 +++++--------- .../getAddressFromIndexAtEpoch.t.sol | 86 +++++++++++++++---- .../getAddressFromIndexAtEpoch.tree | 4 +- .../address_snapshots/removeByName.t.sol | 8 +- 8 files changed, 179 insertions(+), 109 deletions(-) diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index 3248621ef91c..a119d3984a67 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -136,5 +136,6 @@ library Errors { error FeeLib__InvalidFeeAssetPriceModifier(); // 0xf2fb32ad // AddressSnapshotLib - error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // 0xd789b71a + error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // + error AddressSnapshotLib__RequestingLengthForFutureEpoch(Epoch requested, Epoch current); // } diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 9662fed6fe67..e4a08a1e6998 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -7,6 +7,8 @@ import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {SafeCast} from "@oz/utils/math/SafeCast.sol"; import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol"; +import "forge-std/console.sol"; + /** * @notice Structure to store a set of addresses with their historical snapshots * @param size The current number of addresses in the set @@ -36,15 +38,10 @@ library AddressSnapshotLib { using SafeCast for *; using Checkpoints for Checkpoints.Trace224; - /** - * @notice Bit mask used to indicate presence of a validator in the set - */ - uint256 internal constant PRESENCE_BIT = 1 << 255; - /** * @notice Adds a validator to the set * @param _self The storage reference to the set - * @param _address The address of the address to add + * @param _address The address to add * @return bool True if the address was added, false if it was already present */ function add(SnapshottedAddressSet storage _self, address _address) internal returns (bool) { @@ -58,10 +55,7 @@ library AddressSnapshotLib { uint224 index = _self.size.latest(); - // Include max bit to indicate that the validator is in the set - means 0 index is not 0 - Index memory storedIndex = Index({exists: true, index: index}); - - _self.addressToIndex[_address] = storedIndex; + _self.addressToIndex[_address] = Index({exists: true, index: index}); _self.checkpoints[index].push( Epoch.unwrap(_nextEpoch).toUint32(), uint160(_address).toUint224() ); @@ -77,28 +71,28 @@ library AddressSnapshotLib { * @return bool True if the validator was removed, false otherwise */ function remove(SnapshottedAddressSet storage _self, uint224 _index) internal returns (bool) { + uint224 size = _self.size.latest(); + if (_index >= size) { + revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); + } + // To remove from the list, we push the last item into the index and reduce the size - uint224 lastIndex = _self.size.latest() - 1; + uint224 lastIndex = size - 1; uint256 nextEpoch = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)) + Epoch.wrap(1)); address lastValidator = address(_self.checkpoints[lastIndex].latest().toUint160()); - Index memory newLocation = Index({exists: true, index: _index.toUint224()}); - // If we are removing the last item, we cannot swap it with anything // so we append a new address of zero for this epoch // And since we are removing it, we set the location to 0 if (lastIndex == _index) { - newLocation.exists = false; - newLocation.index = 0; - _self.addressToIndex[lastValidator] = newLocation; - - _self.checkpoints[_index].push(nextEpoch.toUint32(), uint160(0).toUint224()); + _self.addressToIndex[lastValidator] = Index({exists: false, index: 0}); + _self.checkpoints[_index].push(nextEpoch.toUint32(), uint224(0)); } else { // Otherwise, we swap the last item with the item we are removing // and update the location of the last item - _self.addressToIndex[lastValidator] = newLocation; + _self.addressToIndex[lastValidator] = Index({exists: true, index: _index.toUint224()}); _self.checkpoints[_index].push(nextEpoch.toUint32(), uint160(lastValidator).toUint224()); } @@ -129,13 +123,6 @@ library AddressSnapshotLib { */ function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); - - uint256 size = lengthAtEpoch(_self, currentEpoch); - if (_index >= size) { - revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); - } - - // Otherwise, we must perform a search return getAddressFromIndexAtEpoch(_self, _index, currentEpoch); } @@ -151,9 +138,13 @@ library AddressSnapshotLib { uint256 _index, Epoch _epoch ) internal view returns (address) { - uint96 epoch = Epoch.unwrap(_epoch).toUint96(); + uint256 size = lengthAtEpoch(_self, _epoch); + + if (_index >= size) { + revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); + } - uint224 addr = _self.checkpoints[_index].upperLookup(epoch.toUint32()); + uint224 addr = _self.checkpoints[_index].upperLookup(Epoch.unwrap(_epoch).toUint32()); return address(addr.toUint160()); } @@ -172,12 +163,18 @@ library AddressSnapshotLib { * @param _self The storage reference to the set * @param _epoch The epoch number to query * @return uint256 The number of addresses in the set at the given epoch + * + * @dev Throws if the epoch is in the future */ function lengthAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) internal view returns (uint256) { + Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); + if (_epoch > currentEpoch) { + revert Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch(_epoch, currentEpoch); + } return _self.size.upperLookup(Epoch.unwrap(_epoch).toUint32()); } @@ -187,16 +184,8 @@ library AddressSnapshotLib { * @return address[] Array of all current addresses in the set */ function values(SnapshottedAddressSet storage _self) internal view returns (address[] memory) { - uint256 size = length(_self); - address[] memory vals = new address[](size); - for (uint256 i; i < size;) { - vals[i] = at(_self, i); - - unchecked { - i++; - } - } - return vals; + Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); + return valuesAtEpoch(_self, currentEpoch); } /** diff --git a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol index e1b84b3c0d56..0517f80a5025 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol @@ -12,6 +12,48 @@ import {TimeLib, TimeStorage, Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {TimeCheater} from "../../staking/TimeCheater.sol"; import {TestConstants} from "../../harnesses/TestConstants.sol"; +contract AddressSetWrapper { + using AddressSnapshotLib for SnapshottedAddressSet; + + SnapshottedAddressSet validatorSet; + + function add(address _address) public returns (bool) { + return validatorSet.add(_address); + } + + function remove(uint224 _index) public returns (bool) { + return validatorSet.remove(_index); + } + + function remove(address _address) public returns (bool) { + return validatorSet.remove(_address); + } + + function at(uint256 _index) public view returns (address) { + return validatorSet.at(_index); + } + + function getAddressFromIndexAtEpoch(uint256 _index, Epoch _epoch) public view returns (address) { + return validatorSet.getAddressFromIndexAtEpoch(_index, _epoch); + } + + function length() public view returns (uint256) { + return validatorSet.length(); + } + + function lengthAtEpoch(Epoch _epoch) public view returns (uint256) { + return validatorSet.lengthAtEpoch(_epoch); + } + + function values() public view returns (address[] memory) { + return validatorSet.values(); + } + + function valuesAtEpoch(Epoch _epoch) public view returns (address[] memory) { + return validatorSet.valuesAtEpoch(_epoch); + } +} + contract AddressSnapshotsBase is Test { using AddressSnapshotLib for SnapshottedAddressSet; @@ -19,10 +61,11 @@ contract AddressSnapshotsBase is Test { uint256 private constant EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION; uint256 private immutable GENESIS_TIME = block.timestamp; - SnapshottedAddressSet validatorSet; + AddressSetWrapper validatorSet; TimeCheater timeCheater; function setUp() public { - timeCheater = new TimeCheater(address(this), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); + validatorSet = new AddressSetWrapper(); + timeCheater = new TimeCheater(address(validatorSet), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index a1dd563259f2..2aec21cc3542 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -8,9 +8,8 @@ import { SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; - +import {Errors} from "@aztec/core/libraries/Errors.sol"; contract AddressSnapshotAddTest is AddressSnapshotsBase { - using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenValidatorIsNotInTheSet() public { // It returns true @@ -26,8 +25,11 @@ contract AddressSnapshotAddTest is AddressSnapshotsBase { assertEq(validatorSet.length(), 1); // Epoch 0 remains frozen, so this number should not update - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)), address(0)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)); + validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)); } function test_WhenValidatorIsAlreadyInTheSet() public { diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index 29999302d53a..08cbb78eaf7a 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -6,88 +6,63 @@ import { AddressSnapshotLib, SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; -import {Test} from "forge-std/Test.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {TimeCheater} from "../../staking/TimeCheater.sol"; +import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; -contract ExpectRevertWrapper { - using AddressSnapshotLib for SnapshottedAddressSet; - - SnapshottedAddressSet validatorSet; - - function at(uint256 index) public view returns (address) { - return validatorSet.at(index); - } - - function add(address validator) public { - validatorSet.add(validator); - } - - function remove(uint224 index) public { - validatorSet.remove(index); - } -} - -contract AddressSnapshotAtTest is Test { - ExpectRevertWrapper expectRevertWrapper; - TimeCheater timeCheater; - - function setUp() public { - expectRevertWrapper = new ExpectRevertWrapper(); - timeCheater = new TimeCheater(address(expectRevertWrapper), block.timestamp, 1, 1); - } +contract AddressSnapshotAtTest is AddressSnapshotsBase { function test_WhenNoValidatorsAreRegistered() public { // It reverts vm.expectRevert( abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) ); - expectRevertWrapper.at(0); + validatorSet.at(0); } function test_WhenIndexIsOutOfBounds() public { - expectRevertWrapper.add(address(1)); + validatorSet.add(address(1)); vm.expectRevert( abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) ); - expectRevertWrapper.at(1); + validatorSet.at(1); } function test_WhenIndexIsValid() public { // it returns the current validator at that index timeCheater.cheat__setEpochNow(1); - expectRevertWrapper.add(address(1)); - expectRevertWrapper.add(address(2)); - expectRevertWrapper.add(address(3)); + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); vm.expectRevert( abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) ); - expectRevertWrapper.at(0); + validatorSet.at(0); vm.expectRevert( abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) ); - expectRevertWrapper.at(1); + validatorSet.at(1); vm.expectRevert( abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 2, 0) ); - expectRevertWrapper.at(2); + validatorSet.at(2); // it returns the correct validator after reordering timeCheater.cheat__setEpochNow(2); - assertEq(expectRevertWrapper.at(0), address(1)); - assertEq(expectRevertWrapper.at(1), address(2)); - assertEq(expectRevertWrapper.at(2), address(3)); + assertEq(validatorSet.at(0), address(1)); + assertEq(validatorSet.at(1), address(2)); + assertEq(validatorSet.at(2), address(3)); - expectRevertWrapper.remove(1); + validatorSet.remove(1); - assertEq(expectRevertWrapper.at(0), address(1)); - assertEq(expectRevertWrapper.at(1), address(2)); - assertEq(expectRevertWrapper.at(2), address(3)); + assertEq(validatorSet.at(0), address(1)); + assertEq(validatorSet.at(1), address(2)); + assertEq(validatorSet.at(2), address(3)); timeCheater.cheat__setEpochNow(3); - assertEq(expectRevertWrapper.at(0), address(1)); - assertEq(expectRevertWrapper.at(1), address(3)); + assertEq(validatorSet.at(0), address(1)); + assertEq(validatorSet.at(1), address(3)); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol index d4f065bac7cf..76054b604ffd 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -8,13 +8,17 @@ import { SnapshottedAddressSet } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; - function test_WhenNoValidatorsAreRegistered() public view { - // It should return address(0) - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(0)), address(0)); + function test_WhenNoValidatorsAreRegistered() public { + // It throws out of bounds + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(0)); } modifier whenValidatorsExist() { @@ -40,7 +44,12 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { // It should return the validator address from the snapshot assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)), address(0)); + + // In a past epoch, it is out of bounds + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)); } function test_WhenValidatorsExist_WhenValidatorWasRemoved() public whenValidatorsExist { @@ -50,12 +59,18 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { // It should return address(0) in the next epoch timeCheater.cheat__setEpochNow(3); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)), address(0)); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)); } function test_WhenIndexIsOutOfBounds() public whenValidatorsExist { - // It should return address(0) - assertEq(validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)), address(0)); + // It should throw out of bounds + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)); } function test_WhenValidatorIsRemovedAndNewOneAddedAtSamePosition() public whenValidatorsExist { @@ -66,38 +81,77 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { validatorSet.remove( /* index */ 0); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)), address(0)); + + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, Epoch.wrap(4), Epoch.wrap(3)) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); // Add validator 2 to the first index in the set timeCheater.cheat__setEpochNow(4); validatorSet.add(address(2)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)), address(0)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(2)); + + // Length is zero + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); // Setup and remove the last item in the set alot of times timeCheater.cheat__setEpochNow(5); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(2)); + validatorSet.remove( /* index */ 0); timeCheater.cheat__setEpochNow(6); validatorSet.add(address(3)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)), address(0)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(3)); + + // Length is zero + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)); timeCheater.cheat__setEpochNow(7); + assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(3)); + validatorSet.remove( /* index */ 0); timeCheater.cheat__setEpochNow(8); validatorSet.add(address(4)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)), address(0)); + + // Length is zero + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, Epoch.wrap(9), Epoch.wrap(8)) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)); + + timeCheater.cheat__setEpochNow(9); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)), address(4)); // Expect past values to be maintained assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(1)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)), address(0)); + + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(2)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)), address(0)); + + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(3)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)), address(0)); + + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)), address(4)); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree index 85749c27f4ee..5d9ff425f84d 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.tree @@ -1,6 +1,6 @@ GetAddressFromIndexAtEpochTest ├── when no validators are registered -│ └── it returns address(0) +│ └── it throws out of bounds ├── when validators exist │ ├── when querying current epoch │ │ └── it returns the correct validator address @@ -14,4 +14,4 @@ GetAddressFromIndexAtEpochTest │ └── when validator is removed and new one added at same position │ └── it maintains both current and historical values correctly └── when index is out of bounds - └── it returns address(0) + └── it throws out of bounds diff --git a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol index 7c42bd5e23fe..d1580d1449cc 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol @@ -8,6 +8,7 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; contract AddressSnapshotRemoveByNameTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; @@ -39,7 +40,12 @@ contract AddressSnapshotRemoveByNameTest is AddressSnapshotsBase { timeCheater.cheat__setEpochNow(3); // Length decreases to 0 in the next epoch assertEq(validatorSet.length(), 0); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)), address(0)); + + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); } From 86b1bd33236d2bca94b025c0c8676743d9e9c591 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:55:27 +0000 Subject: [PATCH 36/43] fmt --- l1-contracts/src/core/libraries/Errors.sol | 4 ++-- .../core/libraries/staking/AddressSnapshotLib.sol | 2 -- .../address_snapshots/AddressSnapshotsBase.t.sol | 3 ++- .../validator-selection/address_snapshots/add.t.sol | 2 +- .../validator-selection/address_snapshots/at.t.sol | 1 - .../getAddressFromIndexAtEpoch.t.sol | 12 ++++++++++-- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index a119d3984a67..31dcea90f16e 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -136,6 +136,6 @@ library Errors { error FeeLib__InvalidFeeAssetPriceModifier(); // 0xf2fb32ad // AddressSnapshotLib - error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // - error AddressSnapshotLib__RequestingLengthForFutureEpoch(Epoch requested, Epoch current); // + error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // 0xd789b71a + error AddressSnapshotLib__RequestingLengthForFutureEpoch(Epoch requested, Epoch current); // 0x851b2370 } diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index e4a08a1e6998..7dc04189b45e 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -7,8 +7,6 @@ import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; import {SafeCast} from "@oz/utils/math/SafeCast.sol"; import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol"; -import "forge-std/console.sol"; - /** * @notice Structure to store a set of addresses with their historical snapshots * @param size The current number of addresses in the set diff --git a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol index 0517f80a5025..24677c329b66 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol @@ -66,6 +66,7 @@ contract AddressSnapshotsBase is Test { function setUp() public { validatorSet = new AddressSetWrapper(); - timeCheater = new TimeCheater(address(validatorSet), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); + timeCheater = + new TimeCheater(address(validatorSet), GENESIS_TIME, SLOT_DURATION, EPOCH_DURATION); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index 2aec21cc3542..730e8fe026a3 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -9,8 +9,8 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; -contract AddressSnapshotAddTest is AddressSnapshotsBase { +contract AddressSnapshotAddTest is AddressSnapshotsBase { function test_WhenValidatorIsNotInTheSet() public { // It returns true // It increases the length diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index 08cbb78eaf7a..bbac10b72461 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -11,7 +11,6 @@ import {TimeCheater} from "../../staking/TimeCheater.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; contract AddressSnapshotAtTest is AddressSnapshotsBase { - function test_WhenNoValidatorsAreRegistered() public { // It reverts vm.expectRevert( diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol index 76054b604ffd..501e7632a5a7 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -83,7 +83,11 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(1)); vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, Epoch.wrap(4), Epoch.wrap(3)) + abi.encodeWithSelector( + Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, + Epoch.wrap(4), + Epoch.wrap(3) + ) ); validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); @@ -126,7 +130,11 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { ); validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)); vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, Epoch.wrap(9), Epoch.wrap(8)) + abi.encodeWithSelector( + Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, + Epoch.wrap(9), + Epoch.wrap(8) + ) ); validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)); From 5de3410f3858caf93ce49853ffca94ffb5378c3c Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:03:49 +0000 Subject: [PATCH 37/43] test: add tests for new guards --- .../address_snapshots/length.t.sol | 16 ++++++++++++++ .../address_snapshots/length.tree | 2 ++ .../{removeByName.t.sol => remove.t.sol} | 22 ++++++++++++++++++- .../{removeByName.tree => remove.tree} | 4 +++- .../address_snapshots/values.t.sol | 14 ++++++++++++ .../address_snapshots/values.tree | 2 ++ 6 files changed, 58 insertions(+), 2 deletions(-) rename l1-contracts/test/validator-selection/address_snapshots/{removeByName.t.sol => remove.t.sol} (80%) rename l1-contracts/test/validator-selection/address_snapshots/{removeByName.tree => remove.tree} (80%) diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol index 2f7cce2d8284..07910fa505ed 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol @@ -8,6 +8,7 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; contract AddressSnapshotLengthTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; @@ -40,6 +41,21 @@ contract AddressSnapshotLengthTest is AddressSnapshotsBase { assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), 2); } + function test_WhenQueryingLengthForFutureEpoch() public { + // It reverts + timeCheater.cheat__setEpochNow(1); + + vm.expectRevert( + abi.encodeWithSelector( + Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, + Epoch.wrap(3), + Epoch.wrap(1) + ) + ); + validatorSet.lengthAtEpoch(Epoch.wrap(3)); + } + + // It decrease the length function test_WhenRemovingValidators() public { // It decrease the length // It maintains historical values correctly diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.tree b/l1-contracts/test/validator-selection/address_snapshots/length.tree index edd05e87888b..2122879ba6e9 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/length.tree @@ -3,6 +3,8 @@ LengthTest │ └── it returns 0 ├── when adding validators │ └── it increases the length +├── when querying the length for a future epoch +│ └── it reverts └── when removing validators ├── it decreases the length └── it maintains historical values correctly diff --git a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol b/l1-contracts/test/validator-selection/address_snapshots/remove.t.sol similarity index 80% rename from l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol rename to l1-contracts/test/validator-selection/address_snapshots/remove.t.sol index d1580d1449cc..5ab9c5bc3f10 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/removeByName.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/remove.t.sol @@ -10,7 +10,7 @@ import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; -contract AddressSnapshotRemoveByNameTest is AddressSnapshotsBase { +contract AddressSnapshotRemoveTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; function test_WhenAddressNotInTheSet() public { @@ -49,6 +49,26 @@ contract AddressSnapshotRemoveByNameTest is AddressSnapshotsBase { assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); } + function test_WhenValidatorRemovingAnIndexLargerThanTheCurrentLength() public { + // It reverts + timeCheater.cheat__setEpochNow(1); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + ); + validatorSet.remove(0); + + // Add some validators + validatorSet.add(address(1)); + validatorSet.add(address(2)); + validatorSet.add(address(3)); + + timeCheater.cheat__setEpochNow(2); + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 10, 3) + ); + validatorSet.remove(10); + } + function test_WhenRemovingMultipleValidators() public { // It maintains correct order of remaining validators // It updates snapshots correctly for each removal diff --git a/l1-contracts/test/validator-selection/address_snapshots/removeByName.tree b/l1-contracts/test/validator-selection/address_snapshots/remove.tree similarity index 80% rename from l1-contracts/test/validator-selection/address_snapshots/removeByName.tree rename to l1-contracts/test/validator-selection/address_snapshots/remove.tree index 4074d04d8024..827f21409654 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/removeByName.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/remove.tree @@ -1,4 +1,4 @@ -RemoveByNameTest +RemoveTest ├── when address not in the set │ └── it returns false ├── when validator is in the set @@ -6,6 +6,8 @@ RemoveByNameTest │ ├── it decreases the length │ ├── it updates the snapshot for that index │ └── it maintains historical values correctly +├── when validator removing an index larger than the current length +│ └── it reverts └── when removing multiple validators ├── it maintains correct order of remaining validators └── it updates snapshots correctly for each removal diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index 066e297ebb2e..29f3e15947bf 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -8,6 +8,7 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; contract AddressSnapshotValuesTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; @@ -61,6 +62,19 @@ contract AddressSnapshotValuesTest is AddressSnapshotsBase { assertEq(valsAtEpoch.length, 0); } + function test_WhenQueryingValuesForFutureEpoch() public { + // It reverts + timeCheater.cheat__setEpochNow(1); + vm.expectRevert( + abi.encodeWithSelector( + Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, + Epoch.wrap(2), + Epoch.wrap(1) + ) + ); + validatorSet.valuesAtEpoch(Epoch.wrap(2)); + } + function test_WhenValidatorsAreRemoved() public { // It returns array of remaining validators timeCheater.cheat__setEpochNow(1); diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.tree b/l1-contracts/test/validator-selection/address_snapshots/values.tree index 5b4b3fe64751..16ccc9d3ad40 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/values.tree @@ -7,5 +7,7 @@ ValuesTest ├── when validators have not changed for some time │ └── it returns array with correct length │ └── it returns array with correct addresses in order +├── when querying values for a future epoch +│ └── it reverts └── when validators are removed └── it returns array with remaining validators From c082c864cf98877f2da3927454b202ea8009b900 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:12:33 +0000 Subject: [PATCH 38/43] chore: extra test case --- .../address_snapshots/add.t.sol | 32 +++++++++++++++++++ .../address_snapshots/add.tree | 7 ++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index 730e8fe026a3..f0a10af59510 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -39,4 +39,36 @@ contract AddressSnapshotAddTest is AddressSnapshotsBase { validatorSet.add(address(1)); assertFalse(validatorSet.add(address(1))); } + + function test_WhenValidatorHasBeenRemovedFromTheSet() public { + // It can be added again + + // Added and removed + timeCheater.cheat__setEpochNow(1); + validatorSet.add(address(1)); + + timeCheater.cheat__setEpochNow(2); + validatorSet.remove(0); + + timeCheater.cheat__setEpochNow(3); + assertTrue(validatorSet.add(address(1))); + + timeCheater.cheat__setEpochNow(4); + validatorSet.remove(0); + + // Added at the end of a clump + timeCheater.cheat__setEpochNow(5); + assertTrue(validatorSet.add(address(10))); + assertTrue(validatorSet.add(address(11))); + assertTrue(validatorSet.add(address(12))); + assertTrue(validatorSet.add(address(1))); + + timeCheater.cheat__setEpochNow(6); + // assert they are in the set + assertEq(validatorSet.length(), 4); + assertEq(validatorSet.at(0), address(10)); + assertEq(validatorSet.at(1), address(11)); + assertEq(validatorSet.at(2), address(12)); + assertEq(validatorSet.at(3), address(1)); + } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.tree b/l1-contracts/test/validator-selection/address_snapshots/add.tree index 76c8294e25f3..25885a04b8ec 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/add.tree @@ -4,5 +4,8 @@ AddTest │ ├── it increases the length │ ├── it creates a checkpoint for the next epoch │ └── it does not change the current epoch -└── when validator is already in the set - └── it returns false +├── when validator is already in the set +│ └── it returns false +└── when validator has been removed from the set + └── it can be added again + From 0500342564e7c9e9a7dbe21a3030eee37042324d Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:48:25 +0000 Subject: [PATCH 39/43] chore: remove length query from future epoch --- l1-contracts/src/core/libraries/Errors.sol | 1 - .../core/libraries/staking/AddressSnapshotLib.sol | 9 ++++----- .../getAddressFromIndexAtEpoch.t.sol | 15 +-------------- .../address_snapshots/length.t.sol | 14 -------------- .../address_snapshots/length.tree | 2 -- .../address_snapshots/values.t.sol | 13 ------------- .../address_snapshots/values.tree | 2 -- 7 files changed, 5 insertions(+), 51 deletions(-) diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index 31dcea90f16e..3248621ef91c 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -137,5 +137,4 @@ library Errors { // AddressSnapshotLib error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // 0xd789b71a - error AddressSnapshotLib__RequestingLengthForFutureEpoch(Epoch requested, Epoch current); // 0x851b2370 } diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 7dc04189b45e..f2ea3f16a3d0 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -162,17 +162,13 @@ library AddressSnapshotLib { * @param _epoch The epoch number to query * @return uint256 The number of addresses in the set at the given epoch * - * @dev Throws if the epoch is in the future + * @dev Note, the values returned from this function are in flux if the epoch is in the future. */ function lengthAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) internal view returns (uint256) { - Epoch currentEpoch = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); - if (_epoch > currentEpoch) { - revert Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch(_epoch, currentEpoch); - } return _self.size.upperLookup(Epoch.unwrap(_epoch).toUint32()); } @@ -191,6 +187,9 @@ library AddressSnapshotLib { * @param _self The storage reference to the set * @param _epoch The epoch number to query * @return address[] Array of all addresses in the set at the given epoch + * + * @dev Note, the values returned from this function are in flux if the epoch is in the future. + * */ function valuesAtEpoch(SnapshottedAddressSet storage _self, Epoch _epoch) internal diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol index 501e7632a5a7..1705bf7fd9aa 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -81,13 +81,8 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { validatorSet.remove( /* index */ 0); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(1)); - vm.expectRevert( - abi.encodeWithSelector( - Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, - Epoch.wrap(4), - Epoch.wrap(3) - ) + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) ); validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); @@ -129,14 +124,6 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) ); validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)); - vm.expectRevert( - abi.encodeWithSelector( - Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, - Epoch.wrap(9), - Epoch.wrap(8) - ) - ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)); timeCheater.cheat__setEpochNow(9); assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)), address(4)); diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol index 07910fa505ed..be709540a6e6 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol @@ -41,20 +41,6 @@ contract AddressSnapshotLengthTest is AddressSnapshotsBase { assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), 2); } - function test_WhenQueryingLengthForFutureEpoch() public { - // It reverts - timeCheater.cheat__setEpochNow(1); - - vm.expectRevert( - abi.encodeWithSelector( - Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, - Epoch.wrap(3), - Epoch.wrap(1) - ) - ); - validatorSet.lengthAtEpoch(Epoch.wrap(3)); - } - // It decrease the length function test_WhenRemovingValidators() public { // It decrease the length diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.tree b/l1-contracts/test/validator-selection/address_snapshots/length.tree index 2122879ba6e9..edd05e87888b 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/length.tree @@ -3,8 +3,6 @@ LengthTest │ └── it returns 0 ├── when adding validators │ └── it increases the length -├── when querying the length for a future epoch -│ └── it reverts └── when removing validators ├── it decreases the length └── it maintains historical values correctly diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index 29f3e15947bf..45cdb7848cce 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -62,19 +62,6 @@ contract AddressSnapshotValuesTest is AddressSnapshotsBase { assertEq(valsAtEpoch.length, 0); } - function test_WhenQueryingValuesForFutureEpoch() public { - // It reverts - timeCheater.cheat__setEpochNow(1); - vm.expectRevert( - abi.encodeWithSelector( - Errors.AddressSnapshotLib__RequestingLengthForFutureEpoch.selector, - Epoch.wrap(2), - Epoch.wrap(1) - ) - ); - validatorSet.valuesAtEpoch(Epoch.wrap(2)); - } - function test_WhenValidatorsAreRemoved() public { // It returns array of remaining validators timeCheater.cheat__setEpochNow(1); diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.tree b/l1-contracts/test/validator-selection/address_snapshots/values.tree index 16ccc9d3ad40..5b4b3fe64751 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.tree +++ b/l1-contracts/test/validator-selection/address_snapshots/values.tree @@ -7,7 +7,5 @@ ValuesTest ├── when validators have not changed for some time │ └── it returns array with correct length │ └── it returns array with correct addresses in order -├── when querying values for a future epoch -│ └── it reverts └── when validators are removed └── it returns array with remaining validators From 28cc590d5a6d8071126e2925aed86c6207b957ad Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:43:58 +0000 Subject: [PATCH 40/43] chore: fuzz tests for add.t.sol --- .../libraries/staking/AddressSnapshotLib.sol | 44 +++++---- l1-contracts/test/staking/TimeCheater.sol | 5 + .../AddressSnapshotsBase.t.sol | 6 +- .../address_snapshots/add.t.sol | 91 +++++++++++++------ 4 files changed, 99 insertions(+), 47 deletions(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index f2ea3f16a3d0..866898ef7c88 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -62,6 +62,22 @@ library AddressSnapshotLib { return true; } + + /** + * @notice Removes a address from the set by address + * @param _self The storage reference to the set + * @param _address The address of the address to remove + * @return bool True if the address was removed, false if it wasn't found + */ + function remove(SnapshottedAddressSet storage _self, address _address) internal returns (bool) { + Index memory index = _self.addressToIndex[_address]; + if (!index.exists) { + return false; + } + + return _remove(_self, index.index, _address); + } + /** * @notice Removes a validator from the set by index * @param _self The storage reference to the set @@ -69,6 +85,17 @@ library AddressSnapshotLib { * @return bool True if the validator was removed, false otherwise */ function remove(SnapshottedAddressSet storage _self, uint224 _index) internal returns (bool) { + address _address = address(_self.checkpoints[_index].latest().toUint160()); + return _remove(_self, _index, _address); + } + + /** + * @notice Removes a validator from the set by index + * @param _self The storage reference to the set + * @param _index The index of the validator to remove + * @return bool True if the validator was removed, false otherwise + */ + function _remove(SnapshottedAddressSet storage _self, uint224 _index, address _address) private returns (bool) { uint224 size = _self.size.latest(); if (_index >= size) { revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); @@ -84,8 +111,8 @@ library AddressSnapshotLib { // If we are removing the last item, we cannot swap it with anything // so we append a new address of zero for this epoch // And since we are removing it, we set the location to 0 + _self.addressToIndex[_address] = Index({exists: false, index: 0}); if (lastIndex == _index) { - _self.addressToIndex[lastValidator] = Index({exists: false, index: 0}); _self.checkpoints[_index].push(nextEpoch.toUint32(), uint224(0)); } else { // Otherwise, we swap the last item with the item we are removing @@ -98,21 +125,6 @@ library AddressSnapshotLib { return true; } - /** - * @notice Removes a address from the set by address - * @param _self The storage reference to the set - * @param _address The address of the address to remove - * @return bool True if the address was removed, false if it wasn't found - */ - function remove(SnapshottedAddressSet storage _self, address _address) internal returns (bool) { - Index memory index = _self.addressToIndex[_address]; - if (!index.exists) { - return false; - } - - return remove(_self, index.index); - } - /** * @notice Gets the current address at a specific index at the time right now * @param _self The storage reference to the set diff --git a/l1-contracts/test/staking/TimeCheater.sol b/l1-contracts/test/staking/TimeCheater.sol index f8b2aebc02c8..9d1394144fd3 100644 --- a/l1-contracts/test/staking/TimeCheater.sol +++ b/l1-contracts/test/staking/TimeCheater.sol @@ -61,4 +61,9 @@ contract TimeCheater { currentEpoch++; vm.warp(genesisTime + 1 + currentEpoch * slotDuration * epochDuration); } + + function cheat_jumpForwardEpochs(uint256 _epochs) public { + currentEpoch += _epochs; + vm.warp(genesisTime + 1 + currentEpoch * slotDuration * epochDuration); + } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol index 24677c329b66..2e7b32828886 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol @@ -15,7 +15,7 @@ import {TestConstants} from "../../harnesses/TestConstants.sol"; contract AddressSetWrapper { using AddressSnapshotLib for SnapshottedAddressSet; - SnapshottedAddressSet validatorSet; + SnapshottedAddressSet private validatorSet; function add(address _address) public returns (bool) { return validatorSet.add(_address); @@ -61,8 +61,8 @@ contract AddressSnapshotsBase is Test { uint256 private constant EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION; uint256 private immutable GENESIS_TIME = block.timestamp; - AddressSetWrapper validatorSet; - TimeCheater timeCheater; + AddressSetWrapper internal validatorSet; + TimeCheater internal timeCheater; function setUp() public { validatorSet = new AddressSetWrapper(); diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index f0a10af59510..43565d33a5aa 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -11,64 +11,99 @@ import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; contract AddressSnapshotAddTest is AddressSnapshotsBase { - function test_WhenValidatorIsNotInTheSet() public { + function test_WhenValidatorIsNotInTheSet(address _addr) public { // It returns true // It increases the length // It creates a checkpoint for the next epoch // It does not change the current epoch timeCheater.cheat__setEpochNow(1); - assertTrue(validatorSet.add(address(1))); + assertTrue(validatorSet.add(_addr)); assertEq(validatorSet.length(), 0); timeCheater.cheat__setEpochNow(2); assertEq(validatorSet.length(), 1); - // Epoch 0 remains frozen, so this number should not update vm.expectRevert( abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) ); validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(1)); - validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)); + + assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), _addr); } - function test_WhenValidatorIsAlreadyInTheSet() public { + function test_WhenValidatorIsAlreadyInTheSet(address _addr) public { // It returns false timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - assertFalse(validatorSet.add(address(1))); + validatorSet.add(_addr); + + // Addition should fail, and no writes should be made + vm.record(); + assertFalse(validatorSet.add(_addr)); + + (, bytes32[] memory writes) = vm.accesses(address(validatorSet)); + assertEq(writes.length, 0); } - function test_WhenValidatorHasBeenRemovedFromTheSet() public { + function test_WhenValidatorHasBeenRemovedFromTheSet(address[] memory _addrs) public { // It can be added again - // Added and removed + // Ensure addresses within _addrSet1 are unique + vm.assume(_addrs.length > 0); + for (uint256 i = 0; i < _addrs.length; i++) { + for (uint256 j = 0; j < i; j++) { + vm.assume(_addrs[i] != _addrs[j]); + } + } timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); + + // Add all of the addresses + for (uint256 i = 0; i < _addrs.length; i++) { + assertTrue(validatorSet.add(_addrs[i])); + } + + // Addresses should be empty for the current epoch + for (uint256 i = 0; i < _addrs.length; i++) { + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, i, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(i, Epoch.wrap(1)); + } timeCheater.cheat__setEpochNow(2); - validatorSet.remove(0); + // Addresses should now be added + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(validatorSet.getAddressFromIndexAtEpoch(i, Epoch.wrap(2)), _addrs[i]); + } + + // Remove all of the addresses + for (uint256 i = 0; i < _addrs.length; i++) { + assertTrue(validatorSet.remove(_addrs[i])); + } + + // Addresses should now remain during the current epoch after removal + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(validatorSet.getAddressFromIndexAtEpoch(i, Epoch.wrap(2)), _addrs[i]); + } timeCheater.cheat__setEpochNow(3); - assertTrue(validatorSet.add(address(1))); + for (uint256 i = 0; i < _addrs.length; i++) { + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, i, 0) + ); + validatorSet.getAddressFromIndexAtEpoch(i, Epoch.wrap(1)); + } + + // Add all of the addresses again + for (uint256 i = 0; i < _addrs.length; i++) { + assertTrue(validatorSet.add(_addrs[i])); + } timeCheater.cheat__setEpochNow(4); - validatorSet.remove(0); - - // Added at the end of a clump - timeCheater.cheat__setEpochNow(5); - assertTrue(validatorSet.add(address(10))); - assertTrue(validatorSet.add(address(11))); - assertTrue(validatorSet.add(address(12))); - assertTrue(validatorSet.add(address(1))); - - timeCheater.cheat__setEpochNow(6); - // assert they are in the set - assertEq(validatorSet.length(), 4); - assertEq(validatorSet.at(0), address(10)); - assertEq(validatorSet.at(1), address(11)); - assertEq(validatorSet.at(2), address(12)); - assertEq(validatorSet.at(3), address(1)); + // Assert both sets are in the set + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(validatorSet.getAddressFromIndexAtEpoch(i, Epoch.wrap(4)), _addrs[i]); + } } } From b51dcd58440f450358452bf14f731bfb5f766d4e Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:28:45 +0000 Subject: [PATCH 41/43] fix: prop test suite --- .../libraries/staking/AddressSnapshotLib.sol | 6 +- .../AddressSnapshotsBase.t.sol | 11 ++ .../address_snapshots/add.t.sol | 9 +- .../address_snapshots/at.t.sol | 68 ++++---- .../getAddressFromIndexAtEpoch.t.sol | 154 ++++++++---------- .../address_snapshots/length.t.sol | 39 ++--- .../address_snapshots/values.t.sol | 41 +++-- 7 files changed, 161 insertions(+), 167 deletions(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index 866898ef7c88..c5b1f224d3ad 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -62,7 +62,6 @@ library AddressSnapshotLib { return true; } - /** * @notice Removes a address from the set by address * @param _self The storage reference to the set @@ -95,7 +94,10 @@ library AddressSnapshotLib { * @param _index The index of the validator to remove * @return bool True if the validator was removed, false otherwise */ - function _remove(SnapshottedAddressSet storage _self, uint224 _index, address _address) private returns (bool) { + function _remove(SnapshottedAddressSet storage _self, uint224 _index, address _address) + private + returns (bool) + { uint224 size = _self.size.latest(); if (_index >= size) { revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, size); diff --git a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol index 2e7b32828886..7a06576981b8 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/AddressSnapshotsBase.t.sol @@ -64,6 +64,17 @@ contract AddressSnapshotsBase is Test { AddressSetWrapper internal validatorSet; TimeCheater internal timeCheater; + function boundUnique(address[] memory _addrs) internal pure returns (address[] memory) { + // Ensure addresses within _addrSet1 are unique + vm.assume(_addrs.length > 0); + for (uint256 i = 0; i < _addrs.length; i++) { + for (uint256 j = 0; j < i; j++) { + vm.assume(_addrs[i] != _addrs[j]); + } + } + return _addrs; + } + function setUp() public { validatorSet = new AddressSetWrapper(); timeCheater = diff --git a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol index 43565d33a5aa..8d231395d615 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/add.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/add.t.sol @@ -49,13 +49,8 @@ contract AddressSnapshotAddTest is AddressSnapshotsBase { function test_WhenValidatorHasBeenRemovedFromTheSet(address[] memory _addrs) public { // It can be added again - // Ensure addresses within _addrSet1 are unique - vm.assume(_addrs.length > 0); - for (uint256 i = 0; i < _addrs.length; i++) { - for (uint256 j = 0; j < i; j++) { - vm.assume(_addrs[i] != _addrs[j]); - } - } + _addrs = boundUnique(_addrs); + timeCheater.cheat__setEpochNow(1); // Add all of the addresses diff --git a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol index bbac10b72461..bda64d487572 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/at.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/at.t.sol @@ -11,57 +11,63 @@ import {TimeCheater} from "../../staking/TimeCheater.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; contract AddressSnapshotAtTest is AddressSnapshotsBase { - function test_WhenNoValidatorsAreRegistered() public { + function test_WhenNoValidatorsAreRegistered(uint256 _index) public { // It reverts vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, _index, 0) ); - validatorSet.at(0); + validatorSet.at(_index); } - function test_WhenIndexIsOutOfBounds() public { + function test_WhenIndexIsOutOfBounds(uint256 _index) public { validatorSet.add(address(1)); vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, _index, 0) ); - validatorSet.at(1); + validatorSet.at(_index); } - function test_WhenIndexIsValid() public { + function test_WhenIndexIsValid(address[] memory _addrs) public { + vm.assume(_addrs.length > 2); + _addrs = boundUnique(_addrs); + // it returns the current validator at that index timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - validatorSet.add(address(2)); - validatorSet.add(address(3)); + for (uint256 i = 0; i < _addrs.length; i++) { + validatorSet.add(_addrs[i]); + } - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) - ); - validatorSet.at(0); - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) - ); - validatorSet.at(1); - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 2, 0) - ); - validatorSet.at(2); + for (uint256 i = 0; i < _addrs.length; i++) { + vm.expectRevert( + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, i, 0) + ); + validatorSet.at(i); + } // it returns the correct validator after reordering timeCheater.cheat__setEpochNow(2); - assertEq(validatorSet.at(0), address(1)); - assertEq(validatorSet.at(1), address(2)); - assertEq(validatorSet.at(2), address(3)); + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(validatorSet.at(i), _addrs[i]); + } - validatorSet.remove(1); + // Remove a random index + // -1 to not remove the last item + uint224 randomIndex = uint224( + uint256(keccak256(abi.encodePacked(block.timestamp, _addrs.length))) % (_addrs.length - 1) + ); + address removedAddr = _addrs[randomIndex]; + validatorSet.remove(randomIndex); - assertEq(validatorSet.at(0), address(1)); - assertEq(validatorSet.at(1), address(2)); - assertEq(validatorSet.at(2), address(3)); + // All still there + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(validatorSet.at(i), _addrs[i]); + } + // Progress in time, the deletion should take place timeCheater.cheat__setEpochNow(3); - assertEq(validatorSet.at(0), address(1)); - assertEq(validatorSet.at(1), address(3)); + + // The item at the random index should be different, as it has been replaced + assertNotEq(validatorSet.at(randomIndex), removedAddr); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol index 1705bf7fd9aa..1cad2773e227 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/getAddressFromIndexAtEpoch.t.sol @@ -21,132 +21,112 @@ contract GetAddressFromIndexAtEpochTest is AddressSnapshotsBase { validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(0)); } - modifier whenValidatorsExist() { + modifier whenValidatorsExist(address[] memory _addrs) { + _addrs = boundUnique(_addrs); + timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); + for (uint256 i = 0; i < _addrs.length; i++) { + validatorSet.add(_addrs[i]); + } timeCheater.cheat__setEpochNow(2); _; } - function test_WhenValidatorsExist_WhenQueryingCurrentEpoch() public whenValidatorsExist { + function test_whenQueryingCurrentEpoch(address[] memory _addrs) + public + whenValidatorsExist(_addrs) + { + _addrs = boundUnique(_addrs); + // It should return the current validator address - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(validatorSet.getAddressFromIndexAtEpoch(i, Epoch.wrap(2)), _addrs[i]); + } } - function test_WhenValidatorsExist_WhenQueryingFutureEpoch() public whenValidatorsExist { + function test_WhenValidatorsExist_WhenQueryingFutureEpoch(address[] memory _addrs) + public + whenValidatorsExist(_addrs) + { + _addrs = boundUnique(_addrs); // It should return the current validator address - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(validatorSet.getAddressFromIndexAtEpoch(i, Epoch.wrap(3)), _addrs[i]); + } } - function test_WhenValidatorsExist_WhenQueryingPastEpoch() public whenValidatorsExist { - // It should return the validator address from the snapshot - validatorSet.add(address(2)); + function test_WhenValidatorsExist_WhenQueryingPastEpoch(address[] memory _addrs, uint224 _index) + public + whenValidatorsExist(_addrs) + { + _addrs = boundUnique(_addrs); + _index = uint224(bound(_index, 0, _addrs.length - 1)); // It should return the validator address from the snapshot - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); + assertEq(validatorSet.getAddressFromIndexAtEpoch(_index, Epoch.wrap(2)), _addrs[_index]); // In a past epoch, it is out of bounds vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, _index, 0) ); - validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)); + validatorSet.getAddressFromIndexAtEpoch(_index, Epoch.wrap(1)); } - function test_WhenValidatorsExist_WhenValidatorWasRemoved() public whenValidatorsExist { + function test_WhenValidatorWasRemoved(address[] memory _addrs) public whenValidatorsExist(_addrs) { + _addrs = boundUnique(_addrs); // It should not remove until the next epoch - validatorSet.remove(0); - assertEq(validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(2)), address(1)); - // It should return address(0) in the next epoch + uint224 lastIndex = uint224(_addrs.length - 1); + address lastValidator = _addrs[lastIndex]; + + validatorSet.remove(lastIndex); + assertEq(validatorSet.getAddressFromIndexAtEpoch(lastIndex, Epoch.wrap(2)), lastValidator); + timeCheater.cheat__setEpochNow(3); vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + abi.encodeWithSelector( + Errors.AddressSnapshotLib__IndexOutOfBounds.selector, lastIndex, lastIndex + ) ); - validatorSet.getAddressFromIndexAtEpoch(0, Epoch.wrap(3)); + validatorSet.getAddressFromIndexAtEpoch(lastIndex, Epoch.wrap(3)); } - function test_WhenIndexIsOutOfBounds() public whenValidatorsExist { + function test_WhenIndexIsOutOfBounds(address[] memory _addrs) public whenValidatorsExist(_addrs) { // It should throw out of bounds vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 1, 0) + abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, _addrs.length, 0) ); - validatorSet.getAddressFromIndexAtEpoch(1, Epoch.wrap(1)); + validatorSet.getAddressFromIndexAtEpoch(_addrs.length, Epoch.wrap(1)); } - function test_WhenValidatorIsRemovedAndNewOneAddedAtSamePosition() public whenValidatorsExist { - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(1)); - - // Remove index 0 from the set, in the new epoch - timeCheater.cheat__setEpochNow(3); + function test_WhenValidatorIsRemovedAndNewOneAddedAtSamePosition(address[] memory _addrs) + public + whenValidatorsExist(_addrs) + { + vm.assume(_addrs.length > 2); + // it maintains both current and historical values correctly - validatorSet.remove( /* index */ 0); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(3)), address(1)); - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + // Random index to remove + uint224 randomIndex = uint224( + uint256(keccak256(abi.encodePacked(block.timestamp, _addrs.length))) % (_addrs.length - 1) ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); - // Add validator 2 to the first index in the set - timeCheater.cheat__setEpochNow(4); - validatorSet.add(address(2)); + // Remove the validator + validatorSet.remove(randomIndex); - // Length is zero - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) - ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); - - // Setup and remove the last item in the set alot of times - timeCheater.cheat__setEpochNow(5); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(2)); - - validatorSet.remove( /* index */ 0); - - timeCheater.cheat__setEpochNow(6); - validatorSet.add(address(3)); - - // Length is zero - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) - ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)); - - timeCheater.cheat__setEpochNow(7); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(3)); - - validatorSet.remove( /* index */ 0); - - timeCheater.cheat__setEpochNow(8); - validatorSet.add(address(4)); - - // Length is zero - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) - ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)); - - timeCheater.cheat__setEpochNow(9); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)), address(4)); + timeCheater.cheat__setEpochNow(3); - // Expect past values to be maintained - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(2)), address(1)); + // In epoch 3, there should be a different validator at random index, as it has been replaced with the last validator + // But we should still be able to query the old validator at random index in epoch 2 - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + // Check it now contains the last validators + assertEq( + validatorSet.getAddressFromIndexAtEpoch(randomIndex, Epoch.wrap(3)), _addrs[_addrs.length - 1] ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(4)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(5)), address(2)); - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) - ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(6)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(7)), address(3)); - - vm.expectRevert( - abi.encodeWithSelector(Errors.AddressSnapshotLib__IndexOutOfBounds.selector, 0, 0) + // Check it still contains the old validator at random index in epoch 2 + assertEq( + validatorSet.getAddressFromIndexAtEpoch(randomIndex, Epoch.wrap(2)), _addrs[randomIndex] ); - validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(8)); - assertEq(validatorSet.getAddressFromIndexAtEpoch( /* index */ 0, Epoch.wrap(9)), address(4)); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol index be709540a6e6..245f77560fe7 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/length.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/length.t.sol @@ -8,7 +8,6 @@ import { } from "@aztec/core/libraries/staking/AddressSnapshotLib.sol"; import {AddressSnapshotsBase} from "./AddressSnapshotsBase.t.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; -import {Errors} from "@aztec/core/libraries/Errors.sol"; contract AddressSnapshotLengthTest is AddressSnapshotsBase { using AddressSnapshotLib for SnapshottedAddressSet; @@ -18,50 +17,52 @@ contract AddressSnapshotLengthTest is AddressSnapshotsBase { assertEq(validatorSet.length(), 0); } - function test_WhenAddingValidators() public { + function test_WhenAddingValidators(address[] memory _addrs) public { + _addrs = boundUnique(_addrs); + // It increases the length timeCheater.cheat__setEpochNow(1); // Length starts at zero assertEq(validatorSet.length(), 0); - validatorSet.add(address(1)); - // Length remains zero within this epoch - assertEq(validatorSet.length(), 0); - - validatorSet.add(address(2)); + for (uint256 i = 0; i < _addrs.length; i++) { + validatorSet.add(_addrs[i]); + } // Length remains zero within this epoch assertEq(validatorSet.length(), 0); timeCheater.cheat__setEpochNow(2); - // Length increases to 2 - assertEq(validatorSet.length(), 2); + // It increases after the epoch boundary + assertEq(validatorSet.length(), _addrs.length); // Length at epoch maintains historical values assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(1)), 0); - assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), 2); + assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), _addrs.length); } // It decrease the length - function test_WhenRemovingValidators() public { + function test_WhenRemovingValidators(address[] memory _addrs) public { + _addrs = boundUnique(_addrs); + // It decrease the length // It maintains historical values correctly timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - validatorSet.add(address(2)); + for (uint256 i = 0; i < _addrs.length; i++) { + validatorSet.add(_addrs[i]); + } timeCheater.cheat__setEpochNow(2); - validatorSet.remove(0); - assertEq(validatorSet.length(), 2); - - validatorSet.remove(0); - assertEq(validatorSet.length(), 2); + for (uint256 i = 0; i < _addrs.length; i++) { + validatorSet.remove(_addrs[i]); + } + assertEq(validatorSet.length(), _addrs.length); timeCheater.cheat__setEpochNow(3); assertEq(validatorSet.length(), 0); // Length at epoch maintains historical values assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(1)), 0); - assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), 2); + assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(2)), _addrs.length); assertEq(validatorSet.lengthAtEpoch(Epoch.wrap(3)), 0); } } diff --git a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol index 45cdb7848cce..05a30a363395 100644 --- a/l1-contracts/test/validator-selection/address_snapshots/values.t.sol +++ b/l1-contracts/test/validator-selection/address_snapshots/values.t.sol @@ -19,43 +19,42 @@ contract AddressSnapshotValuesTest is AddressSnapshotsBase { assertEq(vals.length, 0); } - function test_WhenValidatorsExist() public { + function test_WhenValidatorsExist(address[] memory _addrs) public { + _addrs = boundUnique(_addrs); // It returns array with correct length // It returns array with addresses in order timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - validatorSet.add(address(2)); - validatorSet.add(address(3)); - - // Length remains 0 within this epoch - address[] memory vals = validatorSet.values(); - assertEq(vals.length, 0); + for (uint256 i = 0; i < _addrs.length; i++) { + validatorSet.add(_addrs[i]); + } // Move to next epoch for changes to take effect timeCheater.cheat__setEpochNow(2); - vals = validatorSet.values(); - assertEq(vals.length, 3); - assertEq(vals[0], address(1)); - assertEq(vals[1], address(2)); - assertEq(vals[2], address(3)); + address[] memory vals = validatorSet.values(); + assertEq(vals.length, _addrs.length); + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(vals[i], _addrs[i]); + } } - function test_WhenValidatorsHaveNotChangedForSomeTime() public { + function test_WhenValidatorsHaveNotChangedForSomeTime(address[] memory _addrs) public { + _addrs = boundUnique(_addrs); + // It returns array with correct length // It returns array with correct addresses in order timeCheater.cheat__setEpochNow(1); - validatorSet.add(address(1)); - validatorSet.add(address(2)); - validatorSet.add(address(3)); + for (uint256 i = 0; i < _addrs.length; i++) { + validatorSet.add(_addrs[i]); + } timeCheater.cheat__setEpochNow(100); address[] memory vals = validatorSet.values(); - assertEq(vals.length, 3); - assertEq(vals[0], address(1)); - assertEq(vals[1], address(2)); - assertEq(vals[2], address(3)); + assertEq(vals.length, _addrs.length); + for (uint256 i = 0; i < _addrs.length; i++) { + assertEq(vals[i], _addrs[i]); + } // Values at epoch maintains historical values address[] memory valsAtEpoch = validatorSet.valuesAtEpoch(Epoch.wrap(1)); From 39de09485cb0713e1340e4194cafc7a3278dccd3 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:29:31 +0000 Subject: [PATCH 42/43] ordering --- l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol index c5b1f224d3ad..3c63f6463ee2 100644 --- a/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol +++ b/l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol @@ -95,7 +95,7 @@ library AddressSnapshotLib { * @return bool True if the validator was removed, false otherwise */ function _remove(SnapshottedAddressSet storage _self, uint224 _index, address _address) - private + internal returns (bool) { uint224 size = _self.size.latest(); From 8fa9dc90a8f3bf59ac4e39c9f939dfc46642bcc1 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 18 Apr 2025 20:23:48 +0000 Subject: [PATCH 43/43] chore: use l1 tx utils --- .../end-to-end/src/e2e_p2p/p2p_network.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index 35f4da4a67a4..4bf34bd882f0 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -2,7 +2,14 @@ import { getSchnorrWalletWithSecretKey } from '@aztec/accounts/schnorr'; import type { InitialAccountData } from '@aztec/accounts/testing'; import type { AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; import type { AccountWalletWithSecretKey } from '@aztec/aztec.js'; -import { RollupContract, getExpectedAddress, getL1ContractsConfigEnvVars } from '@aztec/ethereum'; +import { + L1TxUtils, + RollupContract, + type ViemPublicClient, + type ViemWalletClient, + getExpectedAddress, + getL1ContractsConfigEnvVars, +} from '@aztec/ethereum'; import { ChainMonitor, EthCheatCodesWithState } from '@aztec/ethereum/test'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { ForwarderAbi, ForwarderBytecode, RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; @@ -13,7 +20,7 @@ import type { PublicDataTreeLeaf } from '@aztec/stdlib/trees'; import { getGenesisValues } from '@aztec/world-state/testing'; import getPort from 'get-port'; -import { type PublicClient, type WalletClient, getContract } from 'viem'; +import { getContract } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { @@ -278,7 +285,7 @@ export class P2PNetworkTest { 'remove-inital-validator', async ({ deployL1ContractsValues, aztecNode, dateProvider }) => { // Send and await a tx to make sure we mine a block for the warp to correctly progress. - const receipt = await this._sendDummyTx( + const { receipt } = await this._sendDummyTx( deployL1ContractsValues.publicClient, deployL1ContractsValues.walletClient, ); @@ -299,14 +306,11 @@ export class P2PNetworkTest { ); } - private async _sendDummyTx(publicClient: PublicClient, walletClient: WalletClient) { - return await publicClient.waitForTransactionReceipt({ - hash: await walletClient.sendTransaction({ - to: walletClient.account!.address, - value: 1n, - account: walletClient.account!, - chain: publicClient.chain, - }), + private async _sendDummyTx(publicClient: ViemPublicClient, walletClient: ViemWalletClient) { + const l1TxUtils = new L1TxUtils(publicClient, walletClient); + return await l1TxUtils.sendAndMonitorTransaction({ + to: walletClient.account!.address, + value: 1n, }); }