Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7374db1
validator snapshots
Maddiaa0 Mar 26, 2025
e74a472
test: add cheating time
Maddiaa0 Mar 26, 2025
dfbddb2
checkpoint
Maddiaa0 Mar 27, 2025
0b4038a
chore: shift sampling
Maddiaa0 Mar 27, 2025
50d96e3
fix: other tests
Maddiaa0 Mar 27, 2025
642572d
clean
Maddiaa0 Mar 27, 2025
0829959
include tree
Maddiaa0 Mar 27, 2025
6f72dd2
chore: remove redundant test
Maddiaa0 Mar 27, 2025
30bbf83
clean
Maddiaa0 Mar 27, 2025
a9c4263
fix: post rebase
Maddiaa0 Mar 27, 2025
93c6939
chore: embed time cheater into staking cheater + prev linting rules
Maddiaa0 Mar 27, 2025
76b0454
include: benchmarks, it gets worse before it gets better
Maddiaa0 Mar 27, 2025
3c85b8f
chore: include setupepoch in view function
Maddiaa0 Mar 27, 2025
d166a5f
chore: more isolated tree testing
Maddiaa0 Apr 14, 2025
7243887
Merge branch 'master' into md/val-snapshot
Maddiaa0 Apr 14, 2025
ee20cc5
chore: remove old tests
Maddiaa0 Apr 14, 2025
633839c
feat: also snapshot size of set
Maddiaa0 Apr 15, 2025
bb0868a
chore: update other tests
Maddiaa0 Apr 15, 2025
b485608
fix: staking asset handler tests
Maddiaa0 Apr 15, 2025
dd2869d
fmt
Maddiaa0 Apr 15, 2025
80160f5
fix: bench test
Maddiaa0 Apr 15, 2025
90200fd
Merge branch 'master' into md/val-snapshot
Maddiaa0 Apr 15, 2025
91796ff
chore: update bench
Maddiaa0 Apr 15, 2025
5186c39
chore: add length and values at epoch test
Maddiaa0 Apr 15, 2025
999c6b7
chore: less optimizer runs
Maddiaa0 Apr 15, 2025
2b111ed
chore: remove bytecode hash
Maddiaa0 Apr 15, 2025
c912aa1
chore: remove unneeded setup epoch calls
Maddiaa0 Apr 15, 2025
1c9822d
chore: expect revert wrapper lib
Maddiaa0 Apr 15, 2025
52050cf
Merge branch 'master' into md/val-snapshot
Maddiaa0 Apr 15, 2025
4869a41
fix: merge update
Maddiaa0 Apr 15, 2025
c57c437
Merge branch 'master' into md/val-snapshot
Maddiaa0 Apr 16, 2025
8eb8e35
fix: migrate to checkpoint lib
Maddiaa0 Apr 16, 2025
8d774e7
exp: remove foundry changes
Maddiaa0 Apr 16, 2025
e2213d1
comments
Maddiaa0 Apr 16, 2025
d165b11
fmt
Maddiaa0 Apr 16, 2025
2141087
WARNING: NO ATTESTERS ON THE ORIGINAL COMMITTEE
Maddiaa0 Apr 16, 2025
42c147f
Merge branch 'master' into md/val-snapshot
Maddiaa0 Apr 16, 2025
9e7579c
fix: update slashing tesst
Maddiaa0 Apr 16, 2025
698e969
fix: slashing test take 2
Maddiaa0 Apr 17, 2025
4c5086c
chore: update
Maddiaa0 Apr 17, 2025
86b1bd3
fmt
Maddiaa0 Apr 17, 2025
5de3410
test: add tests for new guards
Maddiaa0 Apr 17, 2025
c082c86
chore: extra test case
Maddiaa0 Apr 17, 2025
0500342
chore: remove length query from future epoch
Maddiaa0 Apr 17, 2025
28cc590
chore: fuzz tests for add.t.sol
Maddiaa0 Apr 17, 2025
b51dcd5
fix: prop test suite
Maddiaa0 Apr 17, 2025
39de094
ordering
Maddiaa0 Apr 17, 2025
8fa9dc9
chore: use l1 tx utils
Maddiaa0 Apr 18, 2025
001c2b4
Merge branch 'master' into md/val-snapshot
Maddiaa0 Apr 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions l1-contracts/gas_benchmark.md
Original file line number Diff line number Diff line change
@@ -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 |
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
115 changes: 59 additions & 56 deletions l1-contracts/gas_report.md

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ import {
FeeHeader,
RollupConfigInput
} from "@aztec/core/interfaces/IRollup.sol";
import {
IStaking,
ValidatorInfo,
Exit,
OperatorInfo,
EnumerableSet
} 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 {ProposeLib, ValidateHeaderArgs} from "./libraries/rollup/ProposeLib.sol";
import {ValidatorSelectionLib} from "./libraries/validator-selection/ValidatorSelectionLib.sol";
Expand Down Expand Up @@ -59,7 +58,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;
Comment thread
Maddiaa0 marked this conversation as resolved.

using TimeLib for Timestamp;
using TimeLib for Slot;
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/src/core/interfaces/IStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +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 {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol";

// None -> Does not exist in our setup
// Validating -> Participating as validator
Expand Down Expand Up @@ -40,7 +40,7 @@ struct StakingStorage {
address slasher;
uint256 minimumStake;
Timestamp exitDelay;
EnumerableSet.AddressSet attesters;
SnapshottedAddressSet attesters;
mapping(address attester => ValidatorInfo) info;
mapping(address attester => Exit) exits;
}
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/src/core/interfaces/IValidatorSelection.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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;
Expand Down
3 changes: 3 additions & 0 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,7 @@ library Errors {

// FeeLib
error FeeLib__InvalidFeeAssetPriceModifier(); // 0xf2fb32ad

// AddressSnapshotLib
error AddressSnapshotLib__IndexOutOfBounds(uint256 index, uint256 size); // 0xd789b71a
}
17 changes: 11 additions & 6 deletions l1-contracts/src/core/libraries/TimeLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@ 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 {
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 {
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 = _genesisTime;
store.slotDuration = _slotDuration;
store.epochDuration = _epochDuration;
store.genesisTime = _genesisTime.toUint128();
store.slotDuration = _slotDuration.toUint32();
store.epochDuration = _epochDuration.toUint32();
}

function toTimestamp(Slot _a) internal view returns (Timestamp) {
Expand All @@ -45,6 +49,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)
);
Expand Down
228 changes: 228 additions & 0 deletions l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Aztec Labs.
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
* @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;
// Store up to date position for each validator
mapping(address validator => uint256 index) validatorToIndex;
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
}

/**
* @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;
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
}

/**
* @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 {
using SafeCast for uint256;

/**
* @notice Bit mask used to indicate presence of a validator in the set
*/
uint256 internal constant PRESENCE_BIT = 1 << 255;
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated

/**
* @notice Adds a validator to the set
* @param _self The storage reference to the set
* @param _validator The address of the validator to add
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
* @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;
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
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
uint256 indexWithBit = index | PRESENCE_BIT;
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated

_self.validatorToIndex[_validator] = indexWithBit;
_self.checkpoints[index].push(
AddressSnapshot({addr: _validator, epochNumber: Epoch.unwrap(_epochNumber).toUint96()})
);
_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
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
uint256 lastIndex = _self.size - 1;
uint256 lastSnapshotLength = _self.checkpoints[lastIndex].length;

uint256 epochNow = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)));

AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1];

address lastValidator = lastSnapshot.addr;
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);
newLocation = 0;
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
}

_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;
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
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.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];
Comment thread
Maddiaa0 marked this conversation as resolved.
Outdated
if (index == 0) {
return false;
}

// Remove top most bit
index = index & ~PRESENCE_BIT;
return remove(_self, 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) {
uint256 numCheckpoints = _self.checkpoints[_index].length;

if (_index >= _self.size) {
revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, _self.size);
}

if (numCheckpoints == 0) {
return address(0);
}

AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1];
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(
Comment thread
LHerskind marked this conversation as resolved.
SnapshottedAddressSet storage _self,
uint256 _index,
Epoch _epoch
) internal view returns (address) {
Comment thread
LHerskind marked this conversation as resolved.
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;
}
}
}

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;
}

/**
* @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) {
uint256 size = _self.size;
address[] memory vals = new address[](size);
for (uint256 i; i < size;) {
vals[i] = at(_self, i);

unchecked {
i++;
}
}
return vals;
}
}
Loading