diff --git a/.test_patterns.yml b/.test_patterns.yml index ce0754b58361..94a4767f01e2 100644 --- a/.test_patterns.yml +++ b/.test_patterns.yml @@ -91,6 +91,18 @@ tests: skip: true owners: - *lasse + - regex: "src/slasher_client" + skip: true + owners: + - *mitch + - regex: "src/e2e_p2p/validators_sentinel" + skip: true + owners: + - *mitch + - regex: "testbench/port_change.test.ts" + skip: true + owners: + - *sean # e2e tests flakes - regex: "src/e2e_p2p" error_regex: "could not listen on any available address" @@ -195,10 +207,6 @@ tests: error_regex: "✕ should stop after max retry attempts" owners: - *sean - - regex: "p2p/src/testbench/port_change.test.ts" - error_regex: "Timeout waiting for worker" - owners: - - *sean - regex: "yarn-project/kv-store" error_regex: "Could not import your test module" owners: diff --git a/l1-contracts/bootstrap.sh b/l1-contracts/bootstrap.sh index 772fab7a4c3a..f258a141f724 100755 --- a/l1-contracts/bootstrap.sh +++ b/l1-contracts/bootstrap.sh @@ -248,7 +248,7 @@ function validator_costs { report="gas_benchmark.new.md" # will be overwritten each run # keep ONLY these functions, in this order - wanted_funcs="forward setupEpoch submitEpochRootProof" + wanted_funcs="propose setupEpoch submitEpochRootProof" # one label per numeric column (use | to separate) labels='Min|Avg|Median|Max|# Calls' @@ -310,7 +310,7 @@ function validator_costs { END{ for (k = 1; k <= nf; k++) { fn = order[k] - div = (fn == "forward" ? 360 : 11520) # change 11520→720 if desired + div = (fn == "propose" ? 360 : 11520) # change 11520→720 if desired for (j = 1; j <= cols[fn]; j++) { idx = j + 2 diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index b1f6774502bd..d1dd91f60fb3 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -202,8 +202,7 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { bytes32 tipArchive = rollupStore.blocks[pendingBlockNumber].archive; require(tipArchive == _archive, Errors.Rollup__InvalidArchive(tipArchive, _archive)); - Epoch epochNumber = slot.epochFromSlot(); - address proposer = ValidatorSelectionLib.getProposerAt(slot, epochNumber); + address proposer = ValidatorSelectionLib.getProposerAt(slot, slot.epochFromSlot()); require( proposer == msg.sender, Errors.ValidatorSelection__InvalidProposer(proposer, msg.sender) ); @@ -367,10 +366,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return STFLib.getStorage().blobPublicInputsHashes[_blockNumber]; } - function getProposerAtIndex(uint256 _index) external view override(IStaking) returns (address) { - return getProposerForAttester(getAttesterAtIndex(_index)); - } - function getConfig(address _attester) external view @@ -645,22 +640,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return ValidatorSelectionLib.getProposerAt(slot, epochNumber); } - /** - * @notice Get the proposer for an attester - * - * @param _attester - The attester to get the proposer for - * - * @return The proposer for the attester - */ - function getProposerForAttester(address _attester) - public - view - override(IStaking) - returns (address) - { - return StakingLib.getProposerForAttester(_attester); - } - /** * @notice Get the attester at an index * diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index c62cf21a00e1..d3533cfcb6d8 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -178,11 +178,11 @@ contract RollupCore is return RewardLib.claimProverRewards(_recipient, _epochs); } - function deposit(address _attester, address _proposer, address _withdrawer, bool _onCanonical) + function deposit(address _attester, address _withdrawer, bool _onCanonical) external override(IStakingCore) { - StakingLib.deposit(_attester, _proposer, _withdrawer, _onCanonical); + StakingLib.deposit(_attester, _withdrawer, _onCanonical); } function initiateWithdraw(address _attester, address _recipient) diff --git a/l1-contracts/src/core/interfaces/IStaking.sol b/l1-contracts/src/core/interfaces/IStaking.sol index 3c649d1afebb..6d7c040a4fd4 100644 --- a/l1-contracts/src/core/interfaces/IStaking.sol +++ b/l1-contracts/src/core/interfaces/IStaking.sol @@ -9,16 +9,13 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol"; interface IStakingCore { event SlasherUpdated(address indexed oldSlasher, address indexed newSlasher); - event Deposit( - address indexed attester, address indexed proposer, address indexed withdrawer, uint256 amount - ); + event Deposit(address indexed attester, address indexed withdrawer, uint256 amount); event WithdrawInitiated(address indexed attester, address indexed recipient, uint256 amount); event WithdrawFinalised(address indexed attester, address indexed recipient, uint256 amount); event Slashed(address indexed attester, uint256 amount); function setSlasher(address _slasher) external; - function deposit(address _attester, address _proposer, address _withdrawer, bool _onCanonical) - external; + function deposit(address _attester, address _withdrawer, bool _onCanonical) external; function initiateWithdraw(address _attester, address _recipient) external returns (bool); function finaliseWithdraw(address _attester) external; function slash(address _attester, uint256 _amount) external; @@ -29,8 +26,6 @@ interface IStaking is IStakingCore { function getExit(address _attester) external view returns (Exit memory); function getActiveAttesterCount() external view returns (uint256); function getAttesterAtIndex(uint256 _index) external view returns (address); - function getProposerAtIndex(uint256 _index) external view returns (address); - function getProposerForAttester(address _attester) external view returns (address); function getSlasher() external view returns (address); function getStakingAsset() external view returns (IERC20); function getMinimumStake() external view returns (uint256); diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index f8db3d4affd9..e184ab3cf08a 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -101,8 +101,8 @@ library Errors { error ValidatorSelection__InvalidProposer(address expected, address actual); // 0xa8843a68 error ValidatorSelection__InvalidDeposit(address attester, address proposer); // 0x533169bd error ValidatorSelection__InsufficientAttestations(uint256 minimumNeeded, uint256 provided); // 0xaf47297f - error ValidatorSelection__InvalidCommitteeCommitment(bytes32 reconstructed, bytes32 expected); // 0x10816cae - error ValidatorSelection__InvalidAttestationsLength(uint256 expected, uint256 actual); + error ValidatorSelection__InvalidCommitteeCommitment(bytes32 reconstructed, bytes32 expected); // 0xca8d5954 + error ValidatorSelection__InvalidAttestationsLength(uint256 expected, uint256 actual); // 0xe923198c // Staking error Staking__AlreadyActive(address attester); // 0x5e206fa4 @@ -124,7 +124,6 @@ library Errors { error Staking__RollupAlreadyRegistered(address); // 0x108a39c8 error Staking__InvalidRollupAddress(address); // 0xd876720e error Staking__NotCanonical(address); // 0x6244212e - error Staking__InvalidProposer(); // Fee Juice Portal error FeeJuicePortal__AlreadyInitialized(); // 0xc7a172fe diff --git a/l1-contracts/src/core/libraries/crypto/SignatureLib.sol b/l1-contracts/src/core/libraries/crypto/SignatureLib.sol index c3dad56750d2..6953ef242143 100644 --- a/l1-contracts/src/core/libraries/crypto/SignatureLib.sol +++ b/l1-contracts/src/core/libraries/crypto/SignatureLib.sol @@ -35,12 +35,21 @@ library SignatureLib { * @param _signer - The expected signer of the signature * @param _digest - The digest that was signed */ - function verify(Signature memory _signature, address _signer, bytes32 _digest) internal pure { + function verify(Signature memory _signature, address _signer, bytes32 _digest) + internal + pure + returns (bool) + { address recovered = ecrecover(_digest, _signature.v, _signature.r, _signature.s); require(_signer == recovered, Errors.SignatureLib__InvalidSignature(_signer, recovered)); + return true; } function toBytes(Signature memory _signature) internal pure returns (bytes memory) { return abi.encodePacked(_signature.r, _signature.s, _signature.v); } + + function isEmpty(Signature memory _signature) internal pure returns (bool) { + return _signature.v == 0; + } } diff --git a/l1-contracts/src/core/libraries/staking/StakingLib.sol b/l1-contracts/src/core/libraries/staking/StakingLib.sol index 160363c638ee..71b2e05a901c 100644 --- a/l1-contracts/src/core/libraries/staking/StakingLib.sol +++ b/l1-contracts/src/core/libraries/staking/StakingLib.sol @@ -131,12 +131,10 @@ library StakingLib { emit IStakingCore.Slashed(_attester, _amount); } - function deposit(address _attester, address _proposer, address _withdrawer, bool _onCanonical) - internal - { + function deposit(address _attester, address _withdrawer, bool _onCanonical) internal { require( - _attester != address(0) && _proposer != address(0), - Errors.Staking__InvalidDeposit(_attester, _proposer) + _attester != address(0) && _withdrawer != address(0), + Errors.Staking__InvalidDeposit(_attester, _withdrawer) ); StakingStorage storage store = getStorage(); require(!store.exits[_attester].exists, Errors.Staking__AlreadyRegistered(_attester)); @@ -147,7 +145,7 @@ library StakingLib { store.stakingAsset.transferFrom(msg.sender, address(this), amount); store.stakingAsset.approve(address(store.gse), amount); - store.gse.deposit(_attester, _proposer, _withdrawer, _onCanonical); + store.gse.deposit(_attester, _withdrawer, _onCanonical); } function initiateWithdraw(address _attester, address _recipient) internal returns (bool) { @@ -204,11 +202,6 @@ library StakingLib { ); } - function getProposerForAttester(address _attester) internal view returns (address) { - (address proposer,,) = getStorage().gse.getProposer(address(this), _attester); - return proposer; - } - function getAttestersFromIndicesAtTime(Timestamp _timestamp, uint256[] memory _indices) internal view diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index abc306b49367..c819c02fb73b 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -101,34 +101,29 @@ library ValidatorSelectionLib { return; } + if (_flags.ignoreSignatures) { + return; + } + require( _attestations.length == committeeSize, Errors.ValidatorSelection__InvalidAttestationsLength(committeeSize, _attestations.length) ); + // We determine who the proposer from indexing into the provided attestations array, we then recover their proposer + // address from storage uint256 proposerIndex = computeProposerIndex(_epochNumber, _slot, getSampleSeed(_epochNumber), committeeSize); - // We determine who the proposer from indexing into the provided attestations array, we then recover their proposer - // address from storage // The user controls this value, however, if a false value is provided, the recalculated committee commitment will // be incorrect, and we will revert. - address attester = _attestations[proposerIndex].addr; - address proposer = StakingLib.getProposerForAttester(attester); - - require( - proposer == msg.sender, Errors.ValidatorSelection__InvalidProposer(proposer, msg.sender) - ); - - if (_flags.ignoreSignatures) { - return; - } // Validate the attestations uint256 needed = committeeSize * 2 / 3 + 1; uint256 validAttestations = 0; address[] memory reconstructedCommittee = new address[](committeeSize); + bool proposerVerified = false; bytes32 digest = _digest.toEthSignedMessageHash(); for (uint256 i = 0; i < _attestations.length; i++) { @@ -141,11 +136,21 @@ library ValidatorSelectionLib { ); reconstructedCommittee[i] = recovered; validAttestations++; + if (i == proposerIndex) { + proposerVerified = true; + } } else { reconstructedCommittee[i] = attestation.addr; } } + address proposer = reconstructedCommittee[proposerIndex]; + + require( + proposerVerified || proposer == msg.sender, + Errors.ValidatorSelection__InvalidProposer(proposer, msg.sender) + ); + require( validAttestations >= needed, Errors.ValidatorSelection__InsufficientAttestations(needed, validAttestations) @@ -171,10 +176,7 @@ library ValidatorSelectionLib { return address(0); } - address attester = - committee[computeProposerIndex(_epochNumber, _slot, sampleSeed, committee.length)]; - - return StakingLib.getProposerForAttester(attester); + return committee[computeProposerIndex(_epochNumber, _slot, sampleSeed, committee.length)]; } /** diff --git a/l1-contracts/src/core/staking/GSE.sol b/l1-contracts/src/core/staking/GSE.sol index 53d83c42094d..d832d87fdf7a 100644 --- a/l1-contracts/src/core/staking/GSE.sol +++ b/l1-contracts/src/core/staking/GSE.sol @@ -15,7 +15,6 @@ import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol"; struct AttesterConfig { address withdrawer; - address proposer; } struct InstanceStaking { @@ -27,13 +26,10 @@ struct InstanceStaking { } interface IGSE { - event Deposit( - address indexed instance, address indexed attester, address proposer, address withdrawer - ); + event Deposit(address indexed instance, address indexed attester, address withdrawer); function addRollup(address _rollup) external; - function deposit(address _attester, address _proposer, address _withdrawer, bool _onCanonical) - external; + function deposit(address _attester, address _withdrawer, bool _onCanonical) external; function withdraw(address _attester, uint256 _amount) external returns (uint256, bool); function isRegistered(address _instance, address _attester) external view returns (bool); @@ -125,12 +121,11 @@ contract GSE is IGSE, Ownable { * @dev Deposits only allowed to and by listed rollups. * * @param _attester - The attester address of the validator - * @param _proposer - The proposer address of the validator * @param _withdrawer - The withdrawer address of the validator * @param _onCanonical - Whether to deposit into the specific instance, or canonical * @dev Must be the current canonical for `_onCanonical = true` to be valid. */ - function deposit(address _attester, address _proposer, address _withdrawer, bool _onCanonical) + function deposit(address _attester, address _withdrawer, bool _onCanonical) external override(IGSE) onlyRollup @@ -143,17 +138,14 @@ contract GSE is IGSE, Ownable { InstanceStaking storage instanceStaking = instances[_onCanonical ? CANONICAL_MAGIC_ADDRESS : msg.sender]; require(instanceStaking.attesters.add(_attester), Errors.Staking__AlreadyRegistered(_attester)); - instanceStaking.configOf[_attester] = - AttesterConfig({withdrawer: _withdrawer, proposer: _proposer}); + instanceStaking.configOf[_attester] = AttesterConfig({withdrawer: _withdrawer}); instanceStaking.balanceOf[_attester] += MINIMUM_DEPOSIT; instanceStaking.supply += MINIMUM_DEPOSIT; totalSupply += MINIMUM_DEPOSIT; STAKING_ASSET.transferFrom(msg.sender, address(this), MINIMUM_DEPOSIT); - emit Deposit( - _onCanonical ? CANONICAL_MAGIC_ADDRESS : msg.sender, _attester, _proposer, _withdrawer - ); + emit Deposit(_onCanonical ? CANONICAL_MAGIC_ADDRESS : msg.sender, _attester, _withdrawer); } /** @@ -232,7 +224,7 @@ contract GSE is IGSE, Ownable { _getInstanceStoreWithAttester(_instance, _attester); if (!attesterExists) { - return AttesterConfig({withdrawer: address(0), proposer: address(0)}); + return AttesterConfig({withdrawer: address(0)}); } return instanceStaking.configOf[_attester]; @@ -269,7 +261,7 @@ contract GSE is IGSE, Ownable { return (address(0), false, false); } - return (instanceStaking.configOf[_attester].proposer, true, isCanonical); + return (_attester, true, isCanonical); } function balanceOf(address _instance, address _attester) diff --git a/l1-contracts/src/governance/interfaces/IGovernanceProposer.sol b/l1-contracts/src/governance/interfaces/IGovernanceProposer.sol index 80d26947c050..e2fe6e20016b 100644 --- a/l1-contracts/src/governance/interfaces/IGovernanceProposer.sol +++ b/l1-contracts/src/governance/interfaces/IGovernanceProposer.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2024 Aztec Labs. +// solhint-disable imports-order pragma solidity >=0.8.27; import {Slot} from "@aztec/core/libraries/TimeLib.sol"; import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; interface IGovernanceProposer { event VoteCast(IPayload indexed proposal, uint256 indexed round, address indexed voter); @@ -11,6 +13,8 @@ interface IGovernanceProposer { event ProposalExecuted(IPayload indexed proposal, uint256 indexed round); function vote(IPayload _proposal) external returns (bool); + function voteWithSig(IPayload _proposal, Signature memory _sig) external returns (bool); + function executeProposal(uint256 _roundNumber) external returns (bool); function yeaCount(address _instance, uint256 _round, IPayload _proposal) external diff --git a/l1-contracts/src/governance/proposer/EmpireBase.sol b/l1-contracts/src/governance/proposer/EmpireBase.sol index a38903f4cb85..460292d63586 100644 --- a/l1-contracts/src/governance/proposer/EmpireBase.sol +++ b/l1-contracts/src/governance/proposer/EmpireBase.sol @@ -1,12 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2024 Aztec Labs. +// solhint-disable imports-order pragma solidity >=0.8.27; +import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol"; import {IValidatorSelection} from "@aztec/core/interfaces/IValidatorSelection.sol"; import {Slot, SlotLib} from "@aztec/core/libraries/TimeLib.sol"; -import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol"; -import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; import {Errors} from "@aztec/governance/libraries/Errors.sol"; +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {EIP712} from "@oz/utils/cryptography/EIP712.sol"; /** * @notice A GovernanceProposer implementation following the empire model @@ -15,8 +18,9 @@ import {Errors} from "@aztec/governance/libraries/Errors.sol"; * This also means that the implementation here will need to be "updated" if * the interfaces of the sequencer selection changes, for example going optimistic. */ -abstract contract EmpireBase is IGovernanceProposer { +abstract contract EmpireBase is EIP712, IGovernanceProposer { using SlotLib for Slot; + using SignatureLib for Signature; struct RoundAccounting { Slot lastVote; @@ -26,6 +30,8 @@ abstract contract EmpireBase is IGovernanceProposer { } uint256 public constant LIFETIME_IN_ROUNDS = 5; + // EIP-712 type hash for the Vote struct + bytes32 public constant VOTE_TYPEHASH = keccak256("Vote(address proposal)"); // The quorum size uint256 public immutable N; @@ -34,7 +40,7 @@ abstract contract EmpireBase is IGovernanceProposer { mapping(address instance => mapping(uint256 roundNumber => RoundAccounting)) public rounds; - constructor(uint256 _n, uint256 _m) { + constructor(uint256 _n, uint256 _m) EIP712("EmpireBase", "1") { N = _n; M = _m; @@ -55,45 +61,25 @@ abstract contract EmpireBase is IGovernanceProposer { * @return True if executed successfully, false otherwise */ function vote(IPayload _proposal) external override(IGovernanceProposer) returns (bool) { - // For now, skipping this as the check is not really needed but there were not full agreement - /*require( - address(_proposal).code.length > 0, Errors.GovernanceProposer__ProposalHaveNoCode(_proposal) - );*/ - - address instance = getInstance(); - require(instance.code.length > 0, Errors.GovernanceProposer__InstanceHaveNoCode(instance)); - - IValidatorSelection selection = IValidatorSelection(instance); - Slot currentSlot = selection.getCurrentSlot(); - - uint256 roundNumber = computeRound(currentSlot); - - RoundAccounting storage round = rounds[instance][roundNumber]; - - require( - currentSlot > round.lastVote, Errors.GovernanceProposer__VoteAlreadyCastForSlot(currentSlot) - ); - - address proposer = selection.getCurrentProposer(); - require( - msg.sender == proposer, Errors.GovernanceProposer__OnlyProposerCanVote(msg.sender, proposer) - ); - - round.yeaCount[_proposal] += 1; - round.lastVote = currentSlot; - - // @todo We can optimise here for gas by storing some of it packed with the leader. - if (round.leader != _proposal && round.yeaCount[_proposal] > round.yeaCount[round.leader]) { - round.leader = _proposal; - } - - emit VoteCast(_proposal, roundNumber, msg.sender); - - if (round.yeaCount[_proposal] == N) { - emit ProposalExecutable(_proposal, roundNumber); - } + return _internalVote(_proposal, Signature({v: 0, r: bytes32(0), s: bytes32(0)})); + } - return true; + /** + * @notice Cast a vote on a proposal + * Note that this is assuming that the canonical rollup will cast it as + * part of block production, we will perform it here + * + * @param _proposal - The proposal to cast a vote on + * @param _sig - A signature from the proposer + * + * @return True if executed successfully, false otherwise + */ + function voteWithSig(IPayload _proposal, Signature memory _sig) + external + override(IGovernanceProposer) + returns (bool) + { + return _internalVote(_proposal, _sig); } /** @@ -182,4 +168,52 @@ abstract contract EmpireBase is IGovernanceProposer { function getInstance() public view virtual override(IGovernanceProposer) returns (address); function getExecutor() public view virtual override(IGovernanceProposer) returns (address); function _execute(IPayload _proposal) internal virtual returns (bool); + + function _internalVote(IPayload _proposal, Signature memory _sig) internal returns (bool) { + address instance = getInstance(); + require(instance.code.length > 0, Errors.GovernanceProposer__InstanceHaveNoCode(instance)); + + IValidatorSelection selection = IValidatorSelection(instance); + Slot currentSlot = selection.getCurrentSlot(); + + uint256 roundNumber = computeRound(currentSlot); + + RoundAccounting storage round = rounds[instance][roundNumber]; + + require( + currentSlot > round.lastVote, Errors.GovernanceProposer__VoteAlreadyCastForSlot(currentSlot) + ); + + address proposer = selection.getCurrentProposer(); + + if (_sig.isEmpty()) { + require( + msg.sender == proposer, Errors.GovernanceProposer__OnlyProposerCanVote(msg.sender, proposer) + ); + } else { + bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(VOTE_TYPEHASH, _proposal))); + + // _sig.verify will throw if invalid, it is more my sanity that I am doing this for. + require( + _sig.verify(proposer, digest), + Errors.GovernanceProposer__OnlyProposerCanVote(msg.sender, proposer) + ); + } + + round.yeaCount[_proposal] += 1; + round.lastVote = currentSlot; + + // @todo We can optimise here for gas by storing some of it packed with the leader. + if (round.leader != _proposal && round.yeaCount[_proposal] > round.yeaCount[round.leader]) { + round.leader = _proposal; + } + + emit VoteCast(_proposal, roundNumber, msg.sender); + + if (round.yeaCount[_proposal] == N) { + emit ProposalExecutable(_proposal, roundNumber); + } + + return true; + } } diff --git a/l1-contracts/src/mock/MultiAdder.sol b/l1-contracts/src/mock/MultiAdder.sol index 26a95cac1c1c..f5bfaf9d8c34 100644 --- a/l1-contracts/src/mock/MultiAdder.sol +++ b/l1-contracts/src/mock/MultiAdder.sol @@ -7,7 +7,6 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol"; struct CheatDepositArgs { address attester; - address proposer; address withdrawer; } @@ -30,7 +29,7 @@ contract MultiAdder is IMultiAdder { function addValidators(CheatDepositArgs[] memory _args) external override(IMultiAdder) { require(msg.sender == OWNER, "Not owner"); for (uint256 i = 0; i < _args.length; i++) { - STAKING.deposit(_args[i].attester, _args[i].proposer, _args[i].withdrawer, true); + STAKING.deposit(_args[i].attester, _args[i].withdrawer, true); } } } diff --git a/l1-contracts/src/mock/StakingAssetHandler.sol b/l1-contracts/src/mock/StakingAssetHandler.sol index eb6354560ff3..a8d419476677 100644 --- a/l1-contracts/src/mock/StakingAssetHandler.sol +++ b/l1-contracts/src/mock/StakingAssetHandler.sol @@ -27,9 +27,7 @@ import {Ownable} from "@oz/access/Ownable.sol"; */ interface IStakingAssetHandler { event ToppedUp(uint256 _amount); - event ValidatorAdded( - address indexed _rollup, address indexed _attester, address _proposer, address _withdrawer - ); + event ValidatorAdded(address indexed _rollup, address indexed _attester, address _withdrawer); event IntervalUpdated(uint256 _interval); event DepositsPerMintUpdated(uint256 _depositsPerMint); event WithdrawerUpdated(address indexed _withdrawer); @@ -40,7 +38,7 @@ interface IStakingAssetHandler { error CannotMintZeroAmount(); error ValidatorQuotaFilledUntil(uint256 _timestamp); - function addValidator(address _attester, address _proposer) external; + function addValidator(address _attester) external; function setMintInterval(uint256 _interval) external; function setDepositsPerMint(uint256 _depositsPerMint) external; function setWithdrawer(address _withdrawer) external; @@ -93,10 +91,7 @@ contract StakingAssetHandler is IStakingAssetHandler, Ownable { emit UnhingedAdded(_owner); } - function addValidator(address _attester, address _proposer) - external - override(IStakingAssetHandler) - { + function addValidator(address _attester) external override(IStakingAssetHandler) { IStaking rollup = IStaking(address(REGISTRY.getCanonicalRollup())); uint256 depositAmount = rollup.getMinimumStake(); @@ -122,8 +117,8 @@ contract StakingAssetHandler is IStakingAssetHandler, Ownable { } STAKING_ASSET.approve(address(rollup), depositAmount); - rollup.deposit(_attester, _proposer, withdrawer, true); - emit ValidatorAdded(address(rollup), _attester, _proposer, withdrawer); + rollup.deposit(_attester, withdrawer, true); + emit ValidatorAdded(address(rollup), _attester, withdrawer); } function setMintInterval(uint256 _interval) external override(IStakingAssetHandler) onlyOwner { diff --git a/l1-contracts/src/periphery/Forwarder.sol b/l1-contracts/src/periphery/Forwarder.sol index 0f3673336e2b..7c0a8607e43e 100644 --- a/l1-contracts/src/periphery/Forwarder.sol +++ b/l1-contracts/src/periphery/Forwarder.sol @@ -2,20 +2,13 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {Ownable} from "@oz/access/Ownable.sol"; import {Address} from "@oz/utils/Address.sol"; import {IForwarder} from "./interfaces/IForwarder.sol"; -contract Forwarder is Ownable, IForwarder { +contract Forwarder is IForwarder { using Address for address; - constructor(address __owner) Ownable(__owner) {} - - function forward(address[] calldata _to, bytes[] calldata _data) - external - override(IForwarder) - onlyOwner - { + function forward(address[] calldata _to, bytes[] calldata _data) external override(IForwarder) { require( _to.length == _data.length, IForwarder.ForwarderLengthMismatch(_to.length, _data.length) ); diff --git a/l1-contracts/test/Forwarder.t.sol b/l1-contracts/test/Forwarder.t.sol index 0470dbb4c8ff..f66464861d3f 100644 --- a/l1-contracts/test/Forwarder.t.sol +++ b/l1-contracts/test/Forwarder.t.sol @@ -20,8 +20,7 @@ contract ForwarderTest is Test { owner = makeAddr("owner"); user = makeAddr("user"); - vm.prank(owner); - forwarder = new Forwarder(owner); + forwarder = new Forwarder(); token1 = new TestERC20("Token1", "TK1", address(forwarder)); token2 = new TestERC20("Token2", "TK2", address(forwarder)); @@ -46,21 +45,10 @@ contract ForwarderTest is Test { assertEq(token2.balanceOf(address(this)), 200); } - function testRevertWhenNotOwner(address _user) public { - address[] memory targets = new address[](1); - bytes[] memory data = new bytes[](1); - - vm.assume(_user != owner); - vm.prank(_user); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _user)); - forwarder.forward(targets, data); - } - function testRevertWhenLengthMismatch() public { address[] memory targets = new address[](2); bytes[] memory data = new bytes[](1); - vm.prank(owner); vm.expectRevert(abi.encodeWithSelector(IForwarder.ForwarderLengthMismatch.selector, 2, 1)); forwarder.forward(targets, data); } @@ -75,7 +63,6 @@ contract ForwarderTest is Test { bytes[] memory data = new bytes[](1); data[0] = hex"12345678"; - vm.prank(owner); vm.expectRevert(); forwarder.forward(targets, data); } diff --git a/l1-contracts/test/base/Base.sol b/l1-contracts/test/base/Base.sol index 802a2ab86d27..5af19cf38c92 100644 --- a/l1-contracts/test/base/Base.sol +++ b/l1-contracts/test/base/Base.sol @@ -242,7 +242,6 @@ contract TestBase is Test { function logAttesterConfig(AttesterConfig memory config) internal { emit log("attester config"); - emit log_named_address("\tproposer ", config.proposer); emit log_named_address("\twithdrawer", config.withdrawer); } diff --git a/l1-contracts/test/benchmark/happy.t.sol b/l1-contracts/test/benchmark/happy.t.sol index 93c4541146b3..4836b01baba4 100644 --- a/l1-contracts/test/benchmark/happy.t.sol +++ b/l1-contracts/test/benchmark/happy.t.sol @@ -133,9 +133,8 @@ contract BenchmarkRollupTest is FeeModelTestPoints, DecoderBase { CommitteeAttestation internal emptyAttestation; mapping(address attester => uint256 privateKey) internal attesterPrivateKeys; - mapping(address proposer => address attester) internal proposerToAttester; - Forwarder internal baseForwarder = new Forwarder(address(this)); + Forwarder internal baseForwarder = new Forwarder(); modifier prepare(uint256 _validatorCount) { // We deploy a the rollup and sets the time and all to @@ -162,12 +161,7 @@ contract BenchmarkRollupTest is FeeModelTestPoints, DecoderBase { address attester = vm.addr(attesterPrivateKey); attesterPrivateKeys[attester] = attesterPrivateKey; - address proposer = address(new Forwarder(attester)); - - proposerToAttester[proposer] = attester; - - initialValidators[i - 1] = - CheatDepositArgs({attester: attester, proposer: proposer, withdrawer: address(this)}); + initialValidators[i - 1] = CheatDepositArgs({attester: attester, withdrawer: address(this)}); } MultiAdder multiAdder = new MultiAdder(address(rollup), address(this)); @@ -323,19 +317,10 @@ contract BenchmarkRollupTest is FeeModelTestPoints, DecoderBase { skipBlobCheck(address(rollup)); - address[] memory targets = new address[](1); - targets[0] = address(rollup); - - bytes[] memory data = new bytes[](1); - data[0] = abi.encodeCall(IRollupCore.propose, (b.proposeArgs, b.attestations, b.blobInputs)); - - if (proposer == address(0)) { - baseForwarder.forward(targets, data); - } else { - address caller = proposerToAttester[proposer]; - vm.prank(caller); - Forwarder(proposer).forward(targets, data); - } + // @note This is checking the happy path, if there are additional voting it would need to + // be using a forwarder. + vm.prank(proposer); + rollup.propose(b.proposeArgs, b.attestations, b.blobInputs); nextSlot = nextSlot + Slot.wrap(1); } diff --git a/l1-contracts/test/governance/governance-proposer/voteWithsig.t.sol b/l1-contracts/test/governance/governance-proposer/voteWithsig.t.sol new file mode 100644 index 000000000000..73a7d0b9b686 --- /dev/null +++ b/l1-contracts/test/governance/governance-proposer/voteWithsig.t.sol @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import {IPayload} from "@aztec/governance/interfaces/IPayload.sol"; +import {IGovernanceProposer} from "@aztec/governance/interfaces/IGovernanceProposer.sol"; +import {GovernanceProposerBase} from "./Base.t.sol"; +import {Errors} from "@aztec/governance/libraries/Errors.sol"; +import {Slot, SlotLib, Timestamp} from "@aztec/core/libraries/TimeLib.sol"; +import {Fakerollup} from "./mocks/Fakerollup.sol"; +import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; + +contract VoteWithSigTest is GovernanceProposerBase { + using SlotLib for Slot; + + IPayload internal proposal = IPayload(address(0xdeadbeef)); + address internal proposer = address(0); + Fakerollup internal validatorSelection; + + // Skipping this test since the it matches the for now skipped check in `EmpireBase::vote` + function skip__test_WhenProposalHoldNoCode() external { + // it revert + vm.expectRevert( + abi.encodeWithSelector(Errors.GovernanceProposer__ProposalHaveNoCode.selector, proposal) + ); + governanceProposer.vote(proposal); + } + + modifier whenProposalHoldCode() { + proposal = IPayload(address(this)); + _; + } + + function test_GivenCanonicalRollupHoldNoCode() external whenProposalHoldCode { + // it revert + + // Somehow we added a new rollup, and then its code was deleted. Or the registry implementation differed + address f = address(new Fakerollup()); + vm.prank(registry.getGovernance()); + registry.addRollup(IRollup(f)); + vm.etch(f, ""); + + vm.expectRevert( + abi.encodeWithSelector(Errors.GovernanceProposer__InstanceHaveNoCode.selector, address(f)) + ); + governanceProposer.vote(proposal); + } + + modifier givenCanonicalRollupHoldCode() { + validatorSelection = new Fakerollup(); + vm.prank(registry.getGovernance()); + registry.addRollup(IRollup(address(validatorSelection))); + + // We jump into the future since slot 0, will behave as if already voted in + vm.warp(Timestamp.unwrap(validatorSelection.getTimestampForSlot(Slot.wrap(1)))); + _; + } + + function test_GivenAVoteAlreadyCastInTheSlot() + external + whenProposalHoldCode + givenCanonicalRollupHoldCode + { + // it revert + + Slot currentSlot = validatorSelection.getCurrentSlot(); + assertEq(currentSlot.unwrap(), 1); + vm.prank(proposer); + governanceProposer.vote(proposal); + + vm.expectRevert( + abi.encodeWithSelector( + Errors.GovernanceProposer__VoteAlreadyCastForSlot.selector, currentSlot + ) + ); + governanceProposer.vote(proposal); + } + + modifier givenNoVoteAlreadyCastInTheSlot() { + _; + } + + function test_WhenSigNotFromProposer(address _proposer) + external + whenProposalHoldCode + givenCanonicalRollupHoldCode + givenNoVoteAlreadyCastInTheSlot + { + // @todo + // it revert + vm.assume(_proposer != proposer); + vm.prank(_proposer); + vm.expectRevert( + abi.encodeWithSelector( + Errors.GovernanceProposer__OnlyProposerCanVote.selector, _proposer, proposer + ) + ); + governanceProposer.vote(proposal); + } + + modifier whenCallerIsProposer() { + // Lets make sure that there first is a leader + uint256 votesOnProposal = 5; + + // @todo FIX THIS + + for (uint256 i = 0; i < votesOnProposal; i++) { + vm.warp( + Timestamp.unwrap( + validatorSelection.getTimestampForSlot(validatorSelection.getCurrentSlot() + Slot.wrap(1)) + ) + ); + vm.prank(proposer); + governanceProposer.vote(proposal); + } + + Slot currentSlot = validatorSelection.getCurrentSlot(); + uint256 round = governanceProposer.computeRound(currentSlot); + (Slot lastVote, IPayload leader, bool executed) = + governanceProposer.rounds(address(validatorSelection), round); + assertEq( + governanceProposer.yeaCount(address(validatorSelection), round, leader), + votesOnProposal, + "invalid number of votes" + ); + assertFalse(executed); + assertEq(address(leader), address(proposal)); + assertEq(currentSlot.unwrap(), lastVote.unwrap()); + + vm.warp( + Timestamp.unwrap( + validatorSelection.getTimestampForSlot(validatorSelection.getCurrentSlot() + Slot.wrap(1)) + ) + ); + + _; + } + + function test_GivenNewCanonicalInstance() + external + whenProposalHoldCode + givenCanonicalRollupHoldCode + givenNoVoteAlreadyCastInTheSlot + whenCallerIsProposer + { + // it ignore votes from prior instance + // it increase the yea count + // it updates the leader to the proposal + // it emits {VoteCast} event + // it returns true + + Slot validatorSelectionSlot = validatorSelection.getCurrentSlot(); + uint256 validatorSelectionRound = governanceProposer.computeRound(validatorSelectionSlot); + uint256 yeaBefore = + governanceProposer.yeaCount(address(validatorSelection), validatorSelectionRound, proposal); + + Fakerollup freshInstance = new Fakerollup(); + vm.prank(registry.getGovernance()); + registry.addRollup(IRollup(address(freshInstance))); + + vm.warp(Timestamp.unwrap(freshInstance.getTimestampForSlot(Slot.wrap(1)))); + + Slot freshSlot = freshInstance.getCurrentSlot(); + uint256 freshRound = governanceProposer.computeRound(freshSlot); + + vm.prank(proposer); + vm.expectEmit(true, true, true, true, address(governanceProposer)); + emit IGovernanceProposer.VoteCast(proposal, freshRound, proposer); + assertTrue(governanceProposer.vote(proposal)); + + // Check the new instance + { + (Slot lastVote, IPayload leader, bool executed) = + governanceProposer.rounds(address(freshInstance), freshRound); + assertEq( + governanceProposer.yeaCount(address(freshInstance), freshRound, leader), + 1, + "invalid number of votes" + ); + assertFalse(executed); + assertEq(address(leader), address(proposal)); + assertEq(freshSlot.unwrap(), lastVote.unwrap(), "invalid slot [FRESH]"); + } + + // The old instance + { + (Slot lastVote, IPayload leader, bool executed) = + governanceProposer.rounds(address(validatorSelection), validatorSelectionRound); + assertEq( + governanceProposer.yeaCount(address(validatorSelection), validatorSelectionRound, proposal), + yeaBefore, + "invalid number of votes" + ); + assertFalse(executed); + assertEq(address(leader), address(proposal)); + assertEq( + validatorSelectionSlot.unwrap(), lastVote.unwrap() + 1, "invalid slot [ValidatorSelection]" + ); + } + } + + function test_GivenRoundChanged() + external + whenProposalHoldCode + givenCanonicalRollupHoldCode + givenNoVoteAlreadyCastInTheSlot + whenCallerIsProposer + { + // it ignore votes in prior round + // it increase the yea count + // it updates the leader to the proposal + // it emits {VoteCast} event + // it returns true + } + + modifier givenRoundAndInstanceIsStable() { + _; + } + + function test_GivenProposalIsLeader() + external + whenProposalHoldCode + givenCanonicalRollupHoldCode + givenNoVoteAlreadyCastInTheSlot + whenCallerIsProposer + givenRoundAndInstanceIsStable + { + // it increase the yea count + // it emits {VoteCast} event + // it returns true + + Slot currentSlot = validatorSelection.getCurrentSlot(); + uint256 round = governanceProposer.computeRound(currentSlot); + + uint256 yeaBefore = governanceProposer.yeaCount(address(validatorSelection), round, proposal); + + vm.prank(proposer); + vm.expectEmit(true, true, true, true, address(governanceProposer)); + emit IGovernanceProposer.VoteCast(proposal, round, proposer); + assertTrue(governanceProposer.vote(proposal)); + + (Slot lastVote, IPayload leader, bool executed) = + governanceProposer.rounds(address(validatorSelection), round); + assertEq( + governanceProposer.yeaCount(address(validatorSelection), round, leader), + yeaBefore + 1, + "invalid number of votes" + ); + assertFalse(executed); + assertEq(address(leader), address(proposal)); + assertEq(currentSlot.unwrap(), lastVote.unwrap()); + } + + function test_GivenProposalHaveFeverVotesThanLeader() + external + whenProposalHoldCode + givenCanonicalRollupHoldCode + givenNoVoteAlreadyCastInTheSlot + whenCallerIsProposer + givenRoundAndInstanceIsStable + { + // it increase the yea count + // it emits {VoteCast} event + // it returns true + + Slot currentSlot = validatorSelection.getCurrentSlot(); + uint256 round = governanceProposer.computeRound(currentSlot); + + uint256 leaderYeaBefore = + governanceProposer.yeaCount(address(validatorSelection), round, proposal); + + vm.prank(proposer); + vm.expectEmit(true, true, true, true, address(governanceProposer)); + emit IGovernanceProposer.VoteCast(IPayload(address(validatorSelection)), round, proposer); + assertTrue(governanceProposer.vote(IPayload(address(validatorSelection)))); + + (Slot lastVote, IPayload leader, bool executed) = + governanceProposer.rounds(address(validatorSelection), round); + assertEq( + governanceProposer.yeaCount(address(validatorSelection), round, leader), + leaderYeaBefore, + "invalid number of votes" + ); + assertEq( + governanceProposer.yeaCount( + address(validatorSelection), round, IPayload(address(validatorSelection)) + ), + 1, + "invalid number of votes" + ); + assertFalse(executed); + assertEq(address(leader), address(proposal)); + assertEq(currentSlot.unwrap(), lastVote.unwrap()); + } + + function test_GivenProposalHaveMoreVotesThanLeader() + external + whenProposalHoldCode + givenCanonicalRollupHoldCode + givenNoVoteAlreadyCastInTheSlot + whenCallerIsProposer + givenRoundAndInstanceIsStable + { + // it increase the yea count + // it updates the leader to the proposal + // it emits {VoteCast} event + // it returns true + + Slot currentSlot = validatorSelection.getCurrentSlot(); + uint256 round = governanceProposer.computeRound(currentSlot); + + uint256 leaderYeaBefore = + governanceProposer.yeaCount(address(validatorSelection), round, proposal); + + for (uint256 i = 0; i < leaderYeaBefore + 1; i++) { + vm.prank(proposer); + vm.expectEmit(true, true, true, true, address(governanceProposer)); + emit IGovernanceProposer.VoteCast(IPayload(address(validatorSelection)), round, proposer); + assertTrue(governanceProposer.vote(IPayload(address(validatorSelection)))); + + vm.warp( + Timestamp.unwrap( + validatorSelection.getTimestampForSlot(validatorSelection.getCurrentSlot() + Slot.wrap(1)) + ) + ); + } + + { + (Slot lastVote, IPayload leader, bool executed) = + governanceProposer.rounds(address(validatorSelection), round); + assertEq( + governanceProposer.yeaCount( + address(validatorSelection), round, IPayload(address(validatorSelection)) + ), + leaderYeaBefore + 1, + "invalid number of votes" + ); + assertFalse(executed); + assertEq(address(leader), address(validatorSelection)); + assertEq( + governanceProposer.yeaCount(address(validatorSelection), round, proposal), + leaderYeaBefore, + "invalid number of votes" + ); + assertEq(lastVote.unwrap(), currentSlot.unwrap() + leaderYeaBefore); + } + } +} diff --git a/l1-contracts/test/governance/scenario/UpgradeGovernanceProposerTest.t.sol b/l1-contracts/test/governance/scenario/UpgradeGovernanceProposerTest.t.sol index c40edc9087e3..a1d69f47cc2d 100644 --- a/l1-contracts/test/governance/scenario/UpgradeGovernanceProposerTest.t.sol +++ b/l1-contracts/test/governance/scenario/UpgradeGovernanceProposerTest.t.sol @@ -65,8 +65,7 @@ contract UpgradeGovernanceProposerTest is TestBase { address validator = vm.addr(privateKey); privateKeys[validator] = privateKey; validators[i - 1] = validator; - initialValidators[i - 1] = - CheatDepositArgs({attester: validator, proposer: validator, withdrawer: validator}); + initialValidators[i - 1] = CheatDepositArgs({attester: validator, withdrawer: validator}); } MultiAdder multiAdder = new MultiAdder(address(rollup), address(this)); diff --git a/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol b/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol index a8f6ba72ad23..b9e1974c5f94 100644 --- a/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol +++ b/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol @@ -49,11 +49,8 @@ contract SlashingTest is TestBase { for (uint256 i = 1; i < validatorCount + 1; i++) { uint256 attesterPrivateKey = uint256(keccak256(abi.encode("attester", i))); address attester = vm.addr(attesterPrivateKey); - uint256 proposerPrivateKey = uint256(keccak256(abi.encode("proposer", i))); - address proposer = vm.addr(proposerPrivateKey); - initialValidators[i - 1] = - CheatDepositArgs({attester: attester, proposer: proposer, withdrawer: address(this)}); + initialValidators[i - 1] = CheatDepositArgs({attester: attester, withdrawer: address(this)}); } RollupBuilder builder = new RollupBuilder(address(this)); diff --git a/l1-contracts/test/staking/base.t.sol b/l1-contracts/test/staking/base.t.sol index 11813fe94fb1..99e98151dfcd 100644 --- a/l1-contracts/test/staking/base.t.sol +++ b/l1-contracts/test/staking/base.t.sol @@ -12,7 +12,6 @@ contract StakingBase is TestBase { Registry internal registry; TestERC20 internal stakingAsset; - address internal constant PROPOSER = address(bytes20("PROPOSER")); address internal constant ATTESTER = address(bytes20("ATTESTER")); address internal constant WITHDRAWER = address(bytes20("WITHDRAWER")); address internal constant RECIPIENT = address(bytes20("RECIPIENT")); diff --git a/l1-contracts/test/staking/deposit.t.sol b/l1-contracts/test/staking/deposit.t.sol index 5b7f09857f68..86896b760720 100644 --- a/l1-contracts/test/staking/deposit.t.sol +++ b/l1-contracts/test/staking/deposit.t.sol @@ -17,12 +17,7 @@ contract DepositTest is StakingBase { ) ); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); } modifier givenCallerHasSufficientAllowance() { @@ -39,12 +34,7 @@ contract DepositTest is StakingBase { ) ); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); } modifier givenCallerHasSufficientFunds() { @@ -59,42 +49,22 @@ contract DepositTest is StakingBase { { // it reverts - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); vm.expectRevert(abi.encodeWithSelector(Errors.Staking__AlreadyActive.selector, ATTESTER)); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); vm.prank(SLASHER); staking.slash(ATTESTER, MINIMUM_STAKE / 2); assertEq(uint256(staking.getStatus(ATTESTER)), uint256(Status.LIVING)); vm.expectRevert(abi.encodeWithSelector(Errors.Staking__AlreadyRegistered.selector, ATTESTER)); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); vm.prank(WITHDRAWER); staking.initiateWithdraw(ATTESTER, WITHDRAWER); vm.expectRevert(abi.encodeWithSelector(Errors.Staking__AlreadyRegistered.selector, ATTESTER)); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); } modifier givenAttesterIsNotRegistered() { @@ -127,23 +97,15 @@ contract DepositTest is StakingBase { assertEq(stakingAsset.balanceOf(address(staking)), 0); vm.expectEmit(true, true, true, true, address(staking.getGSE())); - emit IGSE.Deposit( - address(staking.getGSE().CANONICAL_MAGIC_ADDRESS()), ATTESTER, PROPOSER, WITHDRAWER - ); + emit IGSE.Deposit(address(staking.getGSE().CANONICAL_MAGIC_ADDRESS()), ATTESTER, WITHDRAWER); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); assertEq(stakingAsset.balanceOf(address(staking.getGSE())), MINIMUM_STAKE); AttesterView memory attesterView = staking.getAttesterView(ATTESTER); assertEq(attesterView.effectiveBalance, MINIMUM_STAKE, "effective balance"); assertEq(attesterView.config.withdrawer, WITHDRAWER); - assertEq(attesterView.config.proposer, PROPOSER); assertTrue(attesterView.status == Status.VALIDATING); } } diff --git a/l1-contracts/test/staking/finaliseWithdraw.t.sol b/l1-contracts/test/staking/finaliseWithdraw.t.sol index ccc49de9f821..b3359f4a833f 100644 --- a/l1-contracts/test/staking/finaliseWithdraw.t.sol +++ b/l1-contracts/test/staking/finaliseWithdraw.t.sol @@ -17,12 +17,7 @@ contract FinaliseWithdrawTest is StakingBase { stakingAsset.mint(address(this), MINIMUM_STAKE); stakingAsset.approve(address(staking), MINIMUM_STAKE); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); vm.expectRevert(abi.encodeWithSelector(Errors.Staking__NotExiting.selector, ATTESTER)); staking.finaliseWithdraw(ATTESTER); @@ -40,12 +35,7 @@ contract FinaliseWithdrawTest is StakingBase { stakingAsset.mint(address(this), MINIMUM_STAKE); stakingAsset.approve(address(staking), MINIMUM_STAKE); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); 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 d51262ce1809..f1b7dc053b51 100644 --- a/l1-contracts/test/staking/getters.t.sol +++ b/l1-contracts/test/staking/getters.t.sol @@ -9,12 +9,7 @@ contract GettersTest is StakingBase { stakingAsset.mint(address(this), MINIMUM_STAKE); stakingAsset.approve(address(staking), MINIMUM_STAKE); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); } function test_getAttesterAtIndex() external view { @@ -26,19 +21,4 @@ contract GettersTest is StakingBase { vm.expectRevert(); staking.getAttesterAtIndex(1); } - - function test_getProposerAtIndex() external view { - address proposer = staking.getProposerAtIndex(0); - assertEq(proposer, PROPOSER); - } - - function test_getProposerOutOfBounds() external { - vm.expectRevert(); - staking.getProposerAtIndex(1); - } - - function test_getProposerForAttester() external view { - assertEq(staking.getProposerForAttester(ATTESTER), PROPOSER); - assertEq(staking.getProposerForAttester(address(1)), address(0)); - } } diff --git a/l1-contracts/test/staking/initiateWithdraw.t.sol b/l1-contracts/test/staking/initiateWithdraw.t.sol index 4e1597a162e0..1926ff32eef5 100644 --- a/l1-contracts/test/staking/initiateWithdraw.t.sol +++ b/l1-contracts/test/staking/initiateWithdraw.t.sol @@ -18,12 +18,7 @@ contract InitiateWithdrawTest is StakingBase { modifier whenAttesterIsRegistered() { stakingAsset.mint(address(this), MINIMUM_STAKE); stakingAsset.approve(address(staking), MINIMUM_STAKE); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); _; } diff --git a/l1-contracts/test/staking/move.t.sol b/l1-contracts/test/staking/move.t.sol index 53f19c8d51cc..09963d593440 100644 --- a/l1-contracts/test/staking/move.t.sol +++ b/l1-contracts/test/staking/move.t.sol @@ -37,7 +37,6 @@ contract MoveTest is StakingBase { oldRollup.deposit({ _attester: address(uint160(i + 1000)), - _proposer: PROPOSER, _withdrawer: WITHDRAWER, _onCanonical: onCanonical }); diff --git a/l1-contracts/test/staking/slash.t.sol b/l1-contracts/test/staking/slash.t.sol index e34023e43250..207d3df5a90e 100644 --- a/l1-contracts/test/staking/slash.t.sol +++ b/l1-contracts/test/staking/slash.t.sol @@ -40,12 +40,7 @@ contract SlashTest is StakingBase { stakingAsset.mint(address(this), DEPOSIT_AMOUNT); stakingAsset.approve(address(staking), DEPOSIT_AMOUNT); - staking.deposit({ - _attester: ATTESTER, - _proposer: PROPOSER, - _withdrawer: WITHDRAWER, - _onCanonical: true - }); + staking.deposit({_attester: ATTESTER, _withdrawer: WITHDRAWER, _onCanonical: true}); _; } diff --git a/l1-contracts/test/staking_asset_handler/addValidator.t.sol b/l1-contracts/test/staking_asset_handler/addValidator.t.sol index 7af689c65b6c..b94410861101 100644 --- a/l1-contracts/test/staking_asset_handler/addValidator.t.sol +++ b/l1-contracts/test/staking_asset_handler/addValidator.t.sol @@ -23,18 +23,16 @@ contract AddValidatorTest is StakingAssetHandlerBase { stakingAssetHandler.addUnhinged(unhinged); } - function test_WhenCallerIsUnhinged(address _attester, address _proposer, bool _isExiting) - external - { + function test_WhenCallerIsUnhinged(address _attester, bool _isExiting) external { // it exits the attester if needed // it deposits into the rollup // it emits a {ValidatorAdded} event - vm.assume(_attester != address(0) && _proposer != address(0)); + vm.assume(_attester != address(0)); // If exiting, we need to create a sequencer that can be exited and exit it first. if (_isExiting) { vm.prank(unhinged); - stakingAssetHandler.addValidator(_attester, _proposer); + stakingAssetHandler.addValidator(_attester); vm.prank(WITHDRAWER); staking.initiateWithdraw(_attester, address(this)); @@ -44,10 +42,9 @@ contract AddValidatorTest is StakingAssetHandlerBase { } vm.prank(unhinged); - stakingAssetHandler.addValidator(_attester, _proposer); + stakingAssetHandler.addValidator(_attester); AttesterView memory attesterView = staking.getAttesterView(_attester); - assertEq(attesterView.config.proposer, _proposer); assertEq(attesterView.config.withdrawer, WITHDRAWER); assertEq(attesterView.effectiveBalance, MINIMUM_STAKE); assertTrue(attesterView.status == Status.VALIDATING); @@ -62,13 +59,13 @@ contract AddValidatorTest is StakingAssetHandlerBase { _; } - function test_WhenInsufficientTimePassed(address _caller, address _attester, address _proposer) + function test_WhenInsufficientTimePassed(address _caller, address _attester) external whenCallerIsNotUnhinged(_caller) givenBalanceLTDepositamount { // it reverts - vm.assume(_attester != address(0) && _proposer != address(0)); + vm.assume(_attester != address(0)); // We overwrite the lastMintTimestamp to be now such that we can see if will revert. stdstore.target(address(stakingAssetHandler)).sig("lastMintTimestamp()").checked_write( @@ -83,10 +80,10 @@ contract AddValidatorTest is StakingAssetHandlerBase { ) ); vm.prank(_caller); - stakingAssetHandler.addValidator(_attester, _proposer); + stakingAssetHandler.addValidator(_attester); } - function test_WhenSufficientTimePassed(address _caller, address _attester, address _proposer) + function test_WhenSufficientTimePassed(address _caller, address _attester) external whenCallerIsNotUnhinged(_caller) givenBalanceLTDepositamount @@ -97,19 +94,18 @@ contract AddValidatorTest is StakingAssetHandlerBase { // it deposits into the rollup // it emits a {ValidatorAdded} event - vm.assume(_attester != address(0) && _proposer != address(0)); + vm.assume(_attester != address(0)); uint256 revertTimestamp = stakingAssetHandler.lastMintTimestamp() + mintInterval; vm.warp(revertTimestamp); vm.expectEmit(true, true, true, true, address(stakingAssetHandler)); emit IStakingAssetHandler.ToppedUp(MINIMUM_STAKE * depositsPerMint); vm.expectEmit(true, true, true, true, address(stakingAssetHandler)); - emit IStakingAssetHandler.ValidatorAdded(address(staking), _attester, _proposer, WITHDRAWER); + emit IStakingAssetHandler.ValidatorAdded(address(staking), _attester, WITHDRAWER); vm.prank(_caller); - stakingAssetHandler.addValidator(_attester, _proposer); + stakingAssetHandler.addValidator(_attester); AttesterView memory attesterView = staking.getAttesterView(_attester); - assertEq(attesterView.config.proposer, _proposer); assertEq(attesterView.config.withdrawer, WITHDRAWER); assertEq(attesterView.effectiveBalance, MINIMUM_STAKE); assertTrue(attesterView.status == Status.VALIDATING); @@ -117,7 +113,7 @@ contract AddValidatorTest is StakingAssetHandlerBase { assertEq(stakingAssetHandler.lastMintTimestamp(), block.timestamp); } - function test_GivenBalanceGEDepositAmount(address _caller, address _attester, address _proposer) + function test_GivenBalanceGEDepositAmount(address _caller, address _attester) external whenCallerIsNotUnhinged(_caller) { @@ -125,19 +121,18 @@ contract AddValidatorTest is StakingAssetHandlerBase { // it deposits into the rollup // it emits a {ValidatorAdded} event - vm.assume(_attester != address(0) && _proposer != address(0)); + vm.assume(_attester != address(0)); uint256 revertTimestamp = stakingAssetHandler.lastMintTimestamp() + mintInterval; vm.warp(revertTimestamp); vm.expectEmit(true, true, true, true, address(stakingAssetHandler)); emit IStakingAssetHandler.ToppedUp(MINIMUM_STAKE * depositsPerMint); vm.expectEmit(true, true, true, true, address(stakingAssetHandler)); - emit IStakingAssetHandler.ValidatorAdded(address(staking), _attester, _proposer, WITHDRAWER); + emit IStakingAssetHandler.ValidatorAdded(address(staking), _attester, WITHDRAWER); vm.prank(_caller); - stakingAssetHandler.addValidator(_attester, _proposer); + stakingAssetHandler.addValidator(_attester); AttesterView memory attesterView = staking.getAttesterView(_attester); - assertEq(attesterView.config.proposer, _proposer); assertEq(attesterView.config.withdrawer, WITHDRAWER); assertEq(attesterView.effectiveBalance, MINIMUM_STAKE); assertTrue(attesterView.status == Status.VALIDATING); diff --git a/l1-contracts/test/staking_asset_handler/setDepositsPerMint.t.sol b/l1-contracts/test/staking_asset_handler/setDepositsPerMint.t.sol index 0a38e2f670f7..33af668ef22b 100644 --- a/l1-contracts/test/staking_asset_handler/setDepositsPerMint.t.sol +++ b/l1-contracts/test/staking_asset_handler/setDepositsPerMint.t.sol @@ -66,9 +66,9 @@ contract SetDepositsPerMintTest is StakingAssetHandlerBase { for (uint256 i = 0; i < _depositsPerMint; i++) { vm.expectEmit(true, true, true, true, address(stakingAssetHandler)); - emit IStakingAssetHandler.ValidatorAdded(rollup, validators[i], validators[i], WITHDRAWER); + emit IStakingAssetHandler.ValidatorAdded(rollup, validators[i], WITHDRAWER); vm.prank(caller); - stakingAssetHandler.addValidator(validators[i], validators[i]); + stakingAssetHandler.addValidator(validators[i]); } uint256 lastMintTimestamp = stakingAssetHandler.lastMintTimestamp(); @@ -82,7 +82,7 @@ contract SetDepositsPerMintTest is StakingAssetHandlerBase { ) ); vm.prank(caller); - stakingAssetHandler.addValidator(address(0xbeefdeef), address(0xbeefdeef)); + stakingAssetHandler.addValidator(address(0xbeefdeef)); emit log_named_uint("balance", stakingAsset.balanceOf(address(stakingAssetHandler))); } diff --git a/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol b/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol index 57f511eb5afd..cb9960c7f23d 100644 --- a/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol +++ b/l1-contracts/test/staking_asset_handler/setMintInterval.t.sol @@ -47,7 +47,7 @@ contract SetMintIntervalTest is StakingAssetHandlerBase { ) ); vm.prank(address(0xbeefdeef)); - stakingAssetHandler.addValidator(address(1), address(1)); + stakingAssetHandler.addValidator(address(1)); } function test_WhenOwnerTriesToMintAfterTheNewIntervalHasPassed(uint256 _newMintInterval) external { @@ -61,9 +61,9 @@ contract SetMintIntervalTest is StakingAssetHandlerBase { address rollup = stakingAssetHandler.getRollup(); vm.expectEmit(true, true, true, true, address(stakingAssetHandler)); - emit IStakingAssetHandler.ValidatorAdded(rollup, address(1), address(1), WITHDRAWER); + emit IStakingAssetHandler.ValidatorAdded(rollup, address(1), WITHDRAWER); vm.prank(address(0xbeefdeef)); - stakingAssetHandler.addValidator(address(1), address(1)); + stakingAssetHandler.addValidator(address(1)); assertEq(stakingAssetHandler.lastMintTimestamp(), block.timestamp); } } diff --git a/l1-contracts/test/staking_asset_handler/setWithdrawer.t.sol b/l1-contracts/test/staking_asset_handler/setWithdrawer.t.sol index 98c002bb0561..dbcc9f1e6f25 100644 --- a/l1-contracts/test/staking_asset_handler/setWithdrawer.t.sol +++ b/l1-contracts/test/staking_asset_handler/setWithdrawer.t.sol @@ -42,11 +42,10 @@ contract SetWithdrawerTest is StakingAssetHandlerBase { address rollup = stakingAssetHandler.getRollup(); address attester = address(1); - address proposer = address(2); vm.expectEmit(true, true, true, true, address(stakingAssetHandler)); - emit IStakingAssetHandler.ValidatorAdded(rollup, attester, proposer, _newWithdrawer); - stakingAssetHandler.addValidator(attester, proposer); + emit IStakingAssetHandler.ValidatorAdded(rollup, attester, _newWithdrawer); + stakingAssetHandler.addValidator(attester); assertEq(staking.getConfig(attester).withdrawer, _newWithdrawer); } } diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index 87ad2cce7d39..fa840e55053d 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -76,7 +76,7 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { // The proposer is not necessarily an attester, we have to map it back. We can do this here // because we created a 1:1 link. In practice, there could be multiple attesters for the same proposer address proposer = rollup.getCurrentProposer(); - assertTrue(_seenCommittee[proposerToAttester[proposer]]); + assertTrue(_seenCommittee[proposer]); } function testProposerForNonSetupEpoch(uint8 _epochsToJump) public setup(4) progressEpochs(2) { @@ -93,7 +93,7 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { // Add a validator which will also setup the epoch testERC20.mint(address(this), rollup.getMinimumStake()); testERC20.approve(address(rollup), rollup.getMinimumStake()); - rollup.deposit(address(0xdead), address(0xdead), address(0xdead), true); + rollup.deposit(address(0xdead), address(0xdead), true); address actualProposer = rollup.getCurrentProposer(); assertEq(expectedProposer, actualProposer, "Invalid proposer"); @@ -140,7 +140,7 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { // add a new validator testERC20.mint(address(this), rollup.getMinimumStake()); testERC20.approve(address(rollup), rollup.getMinimumStake()); - rollup.deposit(address(0xdead), address(0xdead), address(0xdead), true); + rollup.deposit(address(0xdead), address(0xdead), true); assertEq(rollup.getCurrentEpoch(), epoch); address[] memory committee = rollup.getCurrentEpochCommittee(); @@ -226,14 +226,15 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { } } - function testInvalidProposer() public setup(4) progressEpochs(2) { + function testRelayedForProposer() public setup(4) progressEpochs(2) { + // Having someone that is not the proposer submit it, but with all signatures (so there is signature from proposer) _testBlock( "mixed_block_1", - true, - 3, + false, + 4, TestFlags({ invalidProposer: true, - provideEmptyAttestations: true, + provideEmptyAttestations: false, proposerNotProvided: false, invalidCommitteeCommitment: false }) @@ -379,13 +380,22 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase { // Set all attestations, including the propser's addr to 0 if (_flags.proposerNotProvided) { + bytes32 correctCommitteeCommitment = keccak256(abi.encode(validators)); + address[] memory incorrectCommittee = new address[](validators.length); + uint256 invalidAttesterKey = uint256(keccak256(abi.encode("invalid", block.timestamp))); + address invalidAttester = vm.addr(invalidAttesterKey); + attesterPrivateKeys[invalidAttester] = invalidAttesterKey; for (uint256 i = 0; i < attestations.length; ++i) { - attestations[i].addr = address(0); + attestations[i] = createAttestation(invalidAttester, digest); + incorrectCommittee[i] = attestations[i].addr; } + bytes32 incorrectCommitteeCommitment = keccak256(abi.encode(incorrectCommittee)); vm.expectRevert( abi.encodeWithSelector( - Errors.ValidatorSelection__InvalidProposer.selector, address(0), ree.proposer + Errors.ValidatorSelection__InvalidCommitteeCommitment.selector, + incorrectCommitteeCommitment, + correctCommitteeCommitment ) ); } diff --git a/l1-contracts/test/validator-selection/ValidatorSelectionBase.sol b/l1-contracts/test/validator-selection/ValidatorSelectionBase.sol index 1f7b875ab605..95c797a49c46 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelectionBase.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelectionBase.sol @@ -54,8 +54,6 @@ contract ValidatorSelectionTestBase is DecoderBase { 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; mapping(address => bool) internal _seenValidators; mapping(address => bool) internal _seenCommittee; @@ -119,11 +117,7 @@ contract ValidatorSelectionTestBase is DecoderBase { uint256 attesterPrivateKey = uint256(keccak256(abi.encode("attester", _keySalt))); address attester = vm.addr(attesterPrivateKey); attesterPrivateKeys[attester] = attesterPrivateKey; - uint256 proposerPrivateKey = uint256(keccak256(abi.encode("proposer", _keySalt))); - address proposer = vm.addr(proposerPrivateKey); - proposerPrivateKeys[proposer] = proposerPrivateKey; - proposerToAttester[proposer] = attester; - return CheatDepositArgs({attester: attester, proposer: proposer, withdrawer: address(this)}); + return CheatDepositArgs({attester: attester, withdrawer: address(this)}); } } diff --git a/yarn-project/cli/src/cmds/infrastructure/sequencers.ts b/yarn-project/cli/src/cmds/infrastructure/sequencers.ts index 3af8bbb42f36..1f0bcb07edf9 100644 --- a/yarn-project/cli/src/cmds/infrastructure/sequencers.ts +++ b/yarn-project/cli/src/cmds/infrastructure/sequencers.ts @@ -79,7 +79,7 @@ export async function sequencers(opts: { ].map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })), ); - const hash = await writeableRollup.write.deposit([who, who, who, true]); + const hash = await writeableRollup.write.deposit([who, who, true]); await publicClient.waitForTransactionReceipt({ hash }); log(`Added in tx ${hash}`); } else if (command === 'remove') { diff --git a/yarn-project/cli/src/cmds/l1/index.ts b/yarn-project/cli/src/cmds/l1/index.ts index e99f3ef9ab18..e6ed25325cf5 100644 --- a/yarn-project/cli/src/cmds/l1/index.ts +++ b/yarn-project/cli/src/cmds/l1/index.ts @@ -289,7 +289,6 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: Logger ) .addOption(l1ChainIdOption) .option('--attester
', 'ethereum address of the attester', parseEthereumAddress) - .option('--proposer-eoa ', 'ethereum address of the proposer EOA', parseEthereumAddress) .option('--staking-asset-handler ', 'ethereum address of the staking asset handler', parseEthereumAddress) .action(async options => { const { addL1Validator } = await import('./update_l1_validators.js'); @@ -299,7 +298,6 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: Logger privateKey: options.privateKey, mnemonic: options.mnemonic, attesterAddress: options.attester, - proposerEOAAddress: options.proposerEoa, stakingAssetHandlerAddress: options.stakingAssetHandler, log, debugLogger, diff --git a/yarn-project/cli/src/cmds/l1/update_l1_validators.ts b/yarn-project/cli/src/cmds/l1/update_l1_validators.ts index ee7ffd57684c..741347dd9fe8 100644 --- a/yarn-project/cli/src/cmds/l1/update_l1_validators.ts +++ b/yarn-project/cli/src/cmds/l1/update_l1_validators.ts @@ -4,14 +4,13 @@ import { RollupContract, createEthereumChain, createExtendedL1Client, - getExpectedAddress, getL1ContractsConfigEnvVars, getPublicClient, isAnvilTestChain, } from '@aztec/ethereum'; import type { EthAddress } from '@aztec/foundation/eth-address'; import type { LogFn, Logger } from '@aztec/foundation/log'; -import { ForwarderAbi, ForwarderBytecode, RollupAbi, StakingAssetHandlerAbi } from '@aztec/l1-artifacts'; +import { RollupAbi, StakingAssetHandlerAbi } from '@aztec/l1-artifacts'; import { encodeFunctionData, formatEther, getContract } from 'viem'; import { generatePrivateKey, mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; @@ -54,11 +53,10 @@ export async function addL1Validator({ privateKey, mnemonic, attesterAddress, - proposerEOAAddress, stakingAssetHandlerAddress, log, debugLogger, -}: StakingAssetHandlerCommandArgs & LoggerArgs & { attesterAddress: EthAddress; proposerEOAAddress: EthAddress }) { +}: StakingAssetHandlerCommandArgs & LoggerArgs & { attesterAddress: EthAddress }) { const dualLog = makeDualLog(log, debugLogger); const account = getAccount(privateKey, mnemonic); const chain = createEthereumChain(rpcUrls, chainId); @@ -71,17 +69,7 @@ export async function addL1Validator({ }); const rollup = await stakingAssetHandler.read.getRollup(); - - const forwarderAddress = getExpectedAddress( - ForwarderAbi, - ForwarderBytecode, - [proposerEOAAddress.toString()], - proposerEOAAddress.toString(), - ).address; - - dualLog( - `Adding validator (${attesterAddress}, ${proposerEOAAddress} [forwarder: ${forwarderAddress}]) to rollup ${rollup.toString()}`, - ); + dualLog(`Adding validator (${attesterAddress} to rollup ${rollup.toString()}`); const l1TxUtils = new L1TxUtils(l1Client, debugLogger); @@ -90,7 +78,7 @@ export async function addL1Validator({ data: encodeFunctionData({ abi: StakingAssetHandlerAbi, functionName: 'addValidator', - args: [attesterAddress.toString(), forwarderAddress], + args: [attesterAddress.toString()], }), abi: StakingAssetHandlerAbi, }); @@ -99,10 +87,10 @@ export async function addL1Validator({ if (isAnvilTestChain(chainId)) { dualLog(`Funding validator on L1`); const cheatCodes = new EthCheatCodes(rpcUrls, debugLogger); - await cheatCodes.setBalance(proposerEOAAddress, 10n ** 20n); + await cheatCodes.setBalance(attesterAddress, 10n ** 20n); } else { - const balance = await l1Client.getBalance({ address: proposerEOAAddress.toString() }); - dualLog(`Proposer balance: ${formatEther(balance)} ETH`); + const balance = await l1Client.getBalance({ address: attesterAddress.toString() }); + dualLog(`Validator balance: ${formatEther(balance)} ETH`); if (balance === 0n) { dualLog(`WARNING: Proposer has no balance. Remember to fund it!`); } diff --git a/yarn-project/cli/src/utils/aztec.ts b/yarn-project/cli/src/utils/aztec.ts index 8a7fb9a9ca0f..302e3311d5f2 100644 --- a/yarn-project/cli/src/utils/aztec.ts +++ b/yarn-project/cli/src/utils/aztec.ts @@ -114,7 +114,7 @@ export async function deployNewRollupContracts( if (!initialValidators || initialValidators.length === 0) { // initialize the new rollup with Amin's validator address. const amin = EthAddress.fromString('0x3b218d0F26d15B36C715cB06c949210a0d630637'); - initialValidators = [{ attester: amin, proposerEOA: amin, withdrawer: amin }]; + initialValidators = [{ attester: amin, withdrawer: amin }]; logger.info('Initializing new rollup with old attesters', { initialValidators }); } diff --git a/yarn-project/end-to-end/src/e2e_l1_with_wall_time.test.ts b/yarn-project/end-to-end/src/e2e_l1_with_wall_time.test.ts index e21c42ef0c70..c778f60f811d 100644 --- a/yarn-project/end-to-end/src/e2e_l1_with_wall_time.test.ts +++ b/yarn-project/end-to-end/src/e2e_l1_with_wall_time.test.ts @@ -25,7 +25,6 @@ describe('e2e_l1_with_wall_time', () => { const initialValidators = [ { attester: EthAddress.fromString(account.address), - proposerEOA: EthAddress.fromString(account.address), withdrawer: EthAddress.fromString(account.address), privateKey, }, diff --git a/yarn-project/end-to-end/src/e2e_multi_validator_node.test.ts b/yarn-project/end-to-end/src/e2e_multi_validator_node.test.ts index 95d362bbf137..273cbef8becf 100644 --- a/yarn-project/end-to-end/src/e2e_multi_validator_node.test.ts +++ b/yarn-project/end-to-end/src/e2e_multi_validator_node.test.ts @@ -14,7 +14,6 @@ import { type ExtendedViemWalletClient, RollupContract, createExtendedL1Client, - getAddressFromPrivateKey, getL1ContractsConfigEnvVars, } from '@aztec/ethereum'; import { EthCheatCodesWithState } from '@aztec/ethereum/test'; @@ -65,9 +64,8 @@ describe('e2e_multi_validator_node', () => { const initialValidators = initialValidatorPrivateKeys.map(pk => { const account = privateKeyToAccount(pk); return { - attester: account.address, - proposerEOA: getAddressFromPrivateKey(publisherPrivateKey), - withdrawer: account.address, + attester: EthAddress.fromString(account.address), + withdrawer: EthAddress.fromString(account.address), privateKey: pk, }; }); 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 29894e7ea74f..35eb19e89141 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 @@ -87,7 +87,7 @@ describe('e2e_p2p_network', () => { expect(t.ctx.deployL1ContractsValues.l1ContractAddresses.stakingAssetHandlerAddress).toBeDefined(); - const { validators, proposerEOAs } = t.getValidators(); + const { validators } = t.getValidators(); const rollup = getContract({ address: t.ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), @@ -106,14 +106,12 @@ describe('e2e_p2p_network', () => { // Add the validators to the rollup using the same function as the CLI for (let i = 0; i < validators.length; i++) { const validator = validators[i]; - const proposerEOA = proposerEOAs[i]; await addL1Validator({ rpcUrls: t.ctx.aztecNodeConfig.l1RpcUrls, chainId: t.ctx.aztecNodeConfig.l1ChainId, privateKey: t.baseAccountPrivateKey, mnemonic: undefined, - attesterAddress: EthAddress.fromString(validator.attester), - proposerEOAAddress: EthAddress.fromString(proposerEOA), + attesterAddress: EthAddress.fromString(validator.attester.toString()), stakingAssetHandlerAddress: t.ctx.deployL1ContractsValues.l1ContractAddresses.stakingAssetHandlerAddress!, log: t.logger.info, debugLogger: t.logger, @@ -126,8 +124,7 @@ describe('e2e_p2p_network', () => { // Check that the validators are added correctly const withdrawer = await stakingAssetHandler.read.withdrawer(); for (const validator of validators) { - const info = await rollup.read.getAttesterView([validator.attester]); - expect(info.config.proposer).toBe(validator.proposer); + const info = await rollup.read.getAttesterView([validator.attester.toString()]); expect(info.config.withdrawer).toBe(withdrawer); } 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 5306d1f4293f..292b6911bd42 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 @@ -1,19 +1,19 @@ 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 { type AccountWalletWithSecretKey, EthAddress } from '@aztec/aztec.js'; import { type ExtendedViemWalletClient, L1TxUtils, + type Operator, RollupContract, deployL1Contract, - getExpectedAddress, getL1ContractsConfigEnvVars, l1Artifacts, } from '@aztec/ethereum'; import { ChainMonitor } from '@aztec/ethereum/test'; import { type Logger, createLogger } from '@aztec/foundation/log'; -import { ForwarderAbi, ForwarderBytecode, RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; +import { RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; import { SpamContract } from '@aztec/noir-test-contracts.js/Spam'; import type { BootstrapNode } from '@aztec/p2p/bootstrap'; import { createBootstrapNodeFromPrivateKey, getBootstrapNodeEnr } from '@aztec/p2p/test-helpers'; @@ -26,7 +26,6 @@ import { privateKeyToAccount } from 'viem/accounts'; import { ATTESTER_PRIVATE_KEYS_START_INDEX, - PROPOSER_PRIVATE_KEYS_START_INDEX, createValidatorConfig, generatePrivateKeys, } from '../fixtures/setup_p2p_test.js'; @@ -61,10 +60,8 @@ export class P2PNetworkTest { public ctx!: SubsystemsContext; public attesterPrivateKeys: `0x${string}`[] = []; public attesterPublicKeys: string[] = []; - public proposerPrivateKeys: `0x${string}`[] = []; public peerIdPrivateKeys: string[] = []; - public validators: { attester: `0x${string}`; proposer: `0x${string}`; withdrawer: `0x${string}`; amount: bigint }[] = - []; + public validators: Operator[] = []; public deployedAccounts: InitialAccountData[] = []; public prefilledPublicData: PublicDataTreeLeaf[] = []; @@ -89,7 +86,6 @@ export class P2PNetworkTest { // Set up the base account and node private keys for the initial network deployment this.baseAccountPrivateKey = `0x${getPrivateKeyFromIndex(1)!.toString('hex')}`; this.baseAccount = privateKeyToAccount(this.baseAccountPrivateKey); - this.proposerPrivateKeys = generatePrivateKeys(PROPOSER_PRIVATE_KEYS_START_INDEX, numberOfNodes); this.attesterPrivateKeys = generatePrivateKeys(ATTESTER_PRIVATE_KEYS_START_INDEX, numberOfNodes); this.attesterPublicKeys = this.attesterPrivateKeys.map(privateKey => privateKeyToAccount(privateKey).address); @@ -178,29 +174,19 @@ export class P2PNetworkTest { } getValidators() { - const validators = []; - const proposerEOAs = []; + const validators: Operator[] = []; for (let i = 0; i < this.numberOfNodes; i++) { const attester = privateKeyToAccount(this.attesterPrivateKeys[i]!); - const proposerEOA = privateKeyToAccount(this.proposerPrivateKeys[i]!); - proposerEOAs.push(proposerEOA.address); - const forwarder = getExpectedAddress( - ForwarderAbi, - ForwarderBytecode, - [proposerEOA.address], - proposerEOA.address, - ).address; + validators.push({ - attester: attester.address, - proposer: forwarder, - withdrawer: attester.address, - amount: l1ContractsConfig.minimumStake, - } as const); + attester: EthAddress.fromString(attester.address), + withdrawer: EthAddress.fromString(attester.address), + }); - this.logger.info(`Adding attester ${attester.address} proposer ${forwarder} as validator`); + this.logger.info(`Adding attester ${attester.address} as validator`); } - return { validators, proposerEOAs }; + return { validators }; } async applyBaseSnapshots() { @@ -246,7 +232,15 @@ export class P2PNetworkTest { this.validators = validators; await deployL1ContractsValues.l1Client.waitForTransactionReceipt({ - hash: await multiAdder.write.addValidators([this.validators]), + hash: await multiAdder.write.addValidators([ + this.validators.map( + v => + ({ + attester: v.attester.toString() as `0x${string}`, + withdrawer: v.withdrawer.toString() as `0x${string}`, + }) as const, + ), + ]), }); const timestamp = await cheatCodes.rollup.advanceToEpoch(2n); diff --git a/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts b/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts index f9da0316e5d0..3bc0cb330d43 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/reex.test.ts @@ -88,7 +88,7 @@ describe('e2e_p2p_reex', () => { // into the validator nodes to cause them to fail in different ways. t.logger.info('Submitting txs'); txs = await submitComplexTxsTo(t.logger, t.spamContract!, NUM_TXS_PER_NODE, { callPublic: true }); - }); + }, 360 * 1000); afterAll(async () => { // shutdown all nodes. diff --git a/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts b/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts index 0231dd826c29..296acd599251 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts @@ -144,9 +144,6 @@ describe('e2e_p2p_reqresp_tx', () => { ); const attesters = await rollupContract.getAttesters(); - const mappedProposers = await Promise.all( - attesters.map(async attester => await rollupContract.getProposerForAttester(attester)), - ); const currentTime = await t.ctx.cheatCodes.eth.timestamp(); const slotDuration = await rollupContract.getSlotDuration(); @@ -159,7 +156,7 @@ describe('e2e_p2p_reqresp_tx', () => { proposers.push(proposer); } // Get the indexes of the nodes that are responsible for the next two slots - const proposerIndexes = proposers.map(proposer => mappedProposers.indexOf(proposer as `0x${string}`)); + const proposerIndexes = proposers.map(proposer => attesters.indexOf(proposer as `0x${string}`)); if (proposerIndexes.some(i => i === -1)) { throw new Error( diff --git a/yarn-project/end-to-end/src/e2e_p2p/validators_sentinel.test.ts b/yarn-project/end-to-end/src/e2e_p2p/validators_sentinel.test.ts index 8199b938aa98..8de5e9b274f6 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/validators_sentinel.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/validators_sentinel.test.ts @@ -118,7 +118,7 @@ describe('e2e_p2p_validators_sentinel', () => { }); it('collects stats on offline validator', () => { - const offlineValidator = t.validators.at(-1)!.attester.toLowerCase(); + const offlineValidator = t.validators.at(-1)!.attester.toString().toLowerCase(); t.logger.info(`Asserting stats for offline validator ${offlineValidator}`); const offlineStats = stats.stats[offlineValidator]; const historyLength = offlineStats.history.length; @@ -135,7 +135,7 @@ describe('e2e_p2p_validators_sentinel', () => { )!; t.logger.info(`Asserting stats for proposer validator ${proposerValidator}`); expect(proposerStats).toBeDefined(); - expect(t.validators.map(v => v.attester.toLowerCase())).toContain(proposerValidator); + expect(t.validators.map(v => v.attester.toString().toLowerCase())).toContain(proposerValidator); expect(proposerStats.history.length).toBeGreaterThanOrEqual(1); expect(proposerStats.missedProposals.rate).toBeLessThan(1); }); @@ -146,7 +146,7 @@ describe('e2e_p2p_validators_sentinel', () => { )!; t.logger.info(`Asserting stats for attestor validator ${attestorValidator}`); expect(attestorStats).toBeDefined(); - expect(t.validators.map(v => v.attester.toLowerCase())).toContain(attestorValidator); + expect(t.validators.map(v => v.attester.toString().toLowerCase())).toContain(attestorValidator); expect(attestorStats.history.length).toBeGreaterThanOrEqual(1); expect(attestorStats.missedAttestations.rate).toBeLessThan(1); }); @@ -183,7 +183,7 @@ describe('e2e_p2p_validators_sentinel', () => { const stats = await newNode.getValidatorsStats(); t.logger.info(`Collected validator stats from new node at block ${t.monitor.l2BlockNumber}`, { stats }); - const newNodeValidator = t.validators.at(-1)!.attester.toLowerCase(); + const newNodeValidator = t.validators.at(-1)!.attester.toString().toLowerCase(); expect(stats.stats[newNodeValidator]).toBeDefined(); expect(stats.stats[newNodeValidator].history.length).toBeGreaterThanOrEqual(1); expect(Object.keys(stats.stats).length).toBeGreaterThan(1); @@ -238,7 +238,7 @@ describe('e2e_p2p_validators_sentinel', () => { const slashEvents = await rollupRaw.getEvents.Slashed(); const { attester, amount } = slashEvents[0].args; expect(slashEvents.length).toBe(1); - expect(attester?.toLowerCase()).toBe(t.validators.at(-1)!.attester.toLowerCase()); + expect(attester?.toLowerCase()).toBe(t.validators.at(-1)!.attester.toString().toLowerCase()); expect(amount).toBe(SLASH_AMOUNT); }); }); diff --git a/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.test.ts b/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.test.ts index 99a1b57005ae..17b1587e1af5 100644 --- a/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.test.ts +++ b/yarn-project/end-to-end/src/e2e_sequencer/gov_proposal.test.ts @@ -32,7 +32,6 @@ describe('e2e_gov_proposal', () => { const initialValidators = [ { attester: EthAddress.fromString(account.address), - proposerEOA: EthAddress.fromString(account.address), withdrawer: EthAddress.fromString(account.address), privateKey, }, diff --git a/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts b/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts index c55f105ac1fa..433099b87200 100644 --- a/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts +++ b/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts @@ -17,8 +17,7 @@ import { getEndToEndTestTelemetryClient } from './with_telemetry_utils.js'; // Setup snapshots will create a node with index 0, and run extra bootstrap with // index 1, so all of our loops here need to start from 2 to avoid running validators with the same key -export const PROPOSER_PRIVATE_KEYS_START_INDEX = 2; -export const ATTESTER_PRIVATE_KEYS_START_INDEX = 1001; +export const ATTESTER_PRIVATE_KEYS_START_INDEX = 2; export interface NodeContext { node: AztecNodeService; @@ -106,12 +105,9 @@ export async function createValidatorConfig( const attesterPrivateKey: `0x${string}` = `0x${getPrivateKeyFromIndex( ATTESTER_PRIVATE_KEYS_START_INDEX + addressIndex, )!.toString('hex')}`; - const proposerPrivateKey: `0x${string}` = `0x${getPrivateKeyFromIndex( - PROPOSER_PRIVATE_KEYS_START_INDEX + addressIndex, - )!.toString('hex')}`; config.validatorPrivateKeys = [attesterPrivateKey]; - config.publisherPrivateKey = proposerPrivateKey; + config.publisherPrivateKey = attesterPrivateKey; const nodeConfig: AztecNodeConfig = { ...config, diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 468031f7ae05..193dee47609d 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -845,11 +845,6 @@ export async function createForwarderContract( rollupAddress: Hex, ) { const l1Client = createExtendedL1Client(aztecNodeConfig.l1RpcUrls, privateKey, foundry); - const forwarderContract = await ForwarderContract.create( - l1Client.account.address, - l1Client, - createLogger('forwarder'), - rollupAddress, - ); + const forwarderContract = await ForwarderContract.create(l1Client, createLogger('forwarder'), rollupAddress); return forwarderContract; } diff --git a/yarn-project/ethereum/src/cli/forwarder_address.ts b/yarn-project/ethereum/src/cli/forwarder_address.ts deleted file mode 100644 index cf5173fe8866..000000000000 --- a/yarn-project/ethereum/src/cli/forwarder_address.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ForwarderContract } from '../contracts/index.js'; - -const ownerAddress = process.argv[2]; -if (!ownerAddress) { - process.stderr.write('Please provide an owner address as an argument\n'); - process.exit(1); -} - -// Ensure the address starts with 0x -const formattedAddress = ownerAddress.startsWith('0x') ? ownerAddress : `0x${ownerAddress}`; -const address = ForwarderContract.expectedAddress(formattedAddress as `0x${string}`); -process.stdout.write(`${address}\n`); diff --git a/yarn-project/ethereum/src/contracts/empire_base.ts b/yarn-project/ethereum/src/contracts/empire_base.ts index 180e0923b1a9..6660b1d4c97f 100644 --- a/yarn-project/ethereum/src/contracts/empire_base.ts +++ b/yarn-project/ethereum/src/contracts/empire_base.ts @@ -1,13 +1,16 @@ +import { Signature } from '@aztec/foundation/eth-signature'; import { EmpireBaseAbi } from '@aztec/l1-artifacts/EmpireBaseAbi'; -import { type Hex, encodeFunctionData } from 'viem'; +import { type Hex, type WalletClient, encodeFunctionData } from 'viem'; import type { L1TxRequest } from '../l1_tx_utils.js'; +import type { ExtendedViemWalletClient } from '../types.js'; export interface IEmpireBase { getRoundInfo(rollupAddress: Hex, round: bigint): Promise<{ lastVote: bigint; leader: Hex; executed: boolean }>; computeRound(slot: bigint): Promise