Skip to content

Commit eea4750

Browse files
authored
fix: address tmnt 152 (#16530)
The `packAttestations` that were in the `AttestationLib` and suggest optimised were only used by tests, so I have pulled it out entirely. Also, Fixing the issue from [TMNT-151: Spearbit Gov Finding 20: 33.4% can veto](https://linear.app/aztec-labs/issue/TMNT-151/spearbit-gov-finding-20-334percent-can-veto) as part of this hence it is just a comment issue.
2 parents 037bee5 + a3da5c9 commit eea4750

File tree

16 files changed

+144
-110
lines changed

16 files changed

+144
-110
lines changed

l1-contracts/src/core/libraries/Errors.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ library Errors {
126126
error Staking__InsufficientStake(uint256, uint256); // 0x903aee24
127127
error Staking__NoOneToSlash(address); // 0x7e2f7f1c
128128
error Staking__NotExiting(address); // 0xef566ee0
129+
error Staking__InitiateWithdrawNeeded(address);
129130
error Staking__NotSlasher(address, address); // 0x23a6f432
130131
error Staking__NotWithdrawer(address, address); // 0x8e668e5d
131132
error Staking__NothingToExit(address); // 0xd2aac9b6

l1-contracts/src/core/libraries/rollup/AttestationLib.sol

Lines changed: 3 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ pragma solidity ^0.8.27;
55
import {Errors} from "@aztec/core/libraries/Errors.sol";
66
import {Signature, SignatureLib} from "@aztec/shared/libraries/SignatureLib.sol";
77

8+
uint256 constant SIGNATURE_LENGTH = 65; // v (1) + r (32) + s (32)
9+
uint256 constant ADDRESS_LENGTH = 20;
10+
811
/**
912
* @notice The domain separator for the signatures
1013
*/
@@ -31,9 +34,6 @@ struct CommitteeAttestations {
3134
library AttestationLib {
3235
using SignatureLib for Signature;
3336

34-
uint256 private constant SIGNATURE_LENGTH = 65; // v (1) + r (32) + s (32)
35-
uint256 private constant ADDRESS_LENGTH = 20;
36-
3737
/**
3838
* @notice Checks if the given CommitteeAttestations is empty
3939
* Wll return true if either component is empty as they are needed together.
@@ -255,75 +255,4 @@ library AttestationLib {
255255

256256
return addresses;
257257
}
258-
259-
/**
260-
* @notice Converts an array of CommitteeAttestation into packed CommitteeAttestations format
261-
* @param _attestations Array of individual committee attestations
262-
* @return Packed committee attestations with bitmap and tightly packed data
263-
*/
264-
function packAttestations(CommitteeAttestation[] memory _attestations)
265-
internal
266-
pure
267-
returns (CommitteeAttestations memory)
268-
{
269-
uint256 length = _attestations.length;
270-
271-
// Calculate bitmap size (1 bit per attestation, rounded up to nearest byte)
272-
uint256 bitmapSize = (length + 7) / 8;
273-
bytes memory signatureIndices = new bytes(bitmapSize);
274-
275-
// Calculate total size needed for packed data
276-
uint256 totalDataSize = 0;
277-
for (uint256 i = 0; i < length; i++) {
278-
if (!_attestations[i].signature.isEmpty()) {
279-
totalDataSize += SIGNATURE_LENGTH;
280-
} else {
281-
totalDataSize += ADDRESS_LENGTH;
282-
}
283-
}
284-
285-
bytes memory signaturesOrAddresses = new bytes(totalDataSize);
286-
uint256 dataIndex = 0;
287-
288-
// Pack the data
289-
for (uint256 i = 0; i < length; i++) {
290-
bool hasSignature = !_attestations[i].signature.isEmpty();
291-
292-
// Set bit in bitmap
293-
if (hasSignature) {
294-
uint256 byteIndex = i / 8;
295-
uint256 bitIndex = 7 - (i % 8);
296-
signatureIndices[byteIndex] |= bytes1(uint8(1 << bitIndex));
297-
298-
// Pack signature: v + r + s
299-
signaturesOrAddresses[dataIndex] = bytes1(_attestations[i].signature.v);
300-
dataIndex++;
301-
302-
// Pack r
303-
bytes32 r = _attestations[i].signature.r;
304-
assembly {
305-
mstore(add(add(signaturesOrAddresses, 0x20), dataIndex), r)
306-
}
307-
dataIndex += 32;
308-
309-
// Pack s
310-
bytes32 s = _attestations[i].signature.s;
311-
assembly {
312-
mstore(add(add(signaturesOrAddresses, 0x20), dataIndex), s)
313-
}
314-
dataIndex += 32;
315-
} else {
316-
// Pack address only
317-
address addr = _attestations[i].addr;
318-
assembly {
319-
// Store address in the next 20 bytes
320-
let dataPtr := add(add(signaturesOrAddresses, 0x20), dataIndex)
321-
mstore(dataPtr, shl(96, addr))
322-
}
323-
dataIndex += ADDRESS_LENGTH;
324-
}
325-
}
326-
327-
return CommitteeAttestations({signatureIndices: signatureIndices, signaturesOrAddresses: signaturesOrAddresses});
328-
}
329258
}

l1-contracts/src/core/libraries/rollup/StakingLib.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ library StakingLib {
172172
// We load it into memory to cache it, as we will delete it before we use it.
173173
Exit memory exit = store.exits[_attester];
174174
require(exit.exists, Errors.Staking__NotExiting(_attester));
175-
require(exit.isRecipient, Errors.Staking__NotExiting(_attester));
175+
require(exit.isRecipient, Errors.Staking__InitiateWithdrawNeeded(_attester));
176176
require(
177177
exit.exitableAt <= Timestamp.wrap(block.timestamp),
178178
Errors.Staking__WithdrawalNotUnlockedYet(Timestamp.wrap(block.timestamp), exit.exitableAt)

l1-contracts/src/governance/GSEPayload.sol

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ import {IProposerPayload} from "./interfaces/IProposerPayload.sol";
2222
* If/when the GSE payload is executed, Governance calls `getActions`, which copies the actions of the original
2323
* payload, and appends a call to `amIValid` to it.
2424
*
25-
* NB `amIValid` will fail if the 2/3 of the total stake is not "following canonical", irrespective
26-
* of what the original proposal does. Note that the GSE is used to perform these checks, hence the name.
25+
* NB `amIValid` will fail if the 2/3 of the total stake is not "following latest", irrespective
26+
* of what the original proposal does.
27+
* Note that the GSE is used to perform these checks, hence the name.
28+
* Note this check is skipped if the canonical rollup does not match the latest to avoid livelock cases.
2729
*
2830
* For example, if the original proposal is just to update a configuration parameter, but in the meantime
29-
* half of the stake has exited the canonical rollup in the GSE, `amIValid` will fail.
31+
* half of the stake has exited the latest rollup in the GSE, `amIValid` will fail.
3032
*
3133
* In such an event, your recourse is either:
32-
* - wait for the canonical rollup to have at least 2/3 of the total stake
34+
* - wait for the latest rollup to have at least 2/3 of the total stake
3335
* - `GSE.proposeWithLock`, which bypasses the GovernanceProposer
3436
*/
3537
contract GSEPayload is IProposerPayload {
@@ -78,6 +80,8 @@ contract GSEPayload is IProposerPayload {
7880
* 1. The latest rollup (plus bonus instance) has >2/3 of total stake, OR
7981
* 2. A Registry/GSE mismatch is detected (fail-open to prevent governance livelock)
8082
*
83+
* @dev Beware that the >2/3 support means that 1/3 of the stake can be used to reject proposals.
84+
*
8185
* @dev The "bonus instance" is a special GSE mechanism where attesters automatically
8286
* follow the latest rollup without re-depositing. Their stake counts toward
8387
* the latest rollup's total for this validation.

l1-contracts/src/governance/Governance.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ contract Governance is IGovernance {
396396
* @dev this is intended to only be used in an emergency, where the governanceProposer is compromised.
397397
*
398398
* @dev We don't actually need to check available power here, since if the msg.sender does not have
399-
* sufficient balance, the .
399+
* sufficient balance, the `_initiateWithdraw` would revert with an underflow.
400400
*
401401
* @param _proposal The IPayload address, which is a contract that contains the proposed actions to be executed by
402402
* the governance.
@@ -501,6 +501,8 @@ contract Governance is IGovernance {
501501
require(getProposalState(_proposalId) == ProposalState.Droppable, Errors.Governance__ProposalCannotBeDropped());
502502

503503
self.cachedState = ProposalState.Dropped;
504+
505+
emit ProposalDropped(_proposalId);
504506
return true;
505507
}
506508

l1-contracts/src/governance/interfaces/IGovernance.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ interface IGovernance {
6262
event Proposed(uint256 indexed proposalId, address indexed proposal);
6363
event VoteCast(uint256 indexed proposalId, address indexed voter, bool support, uint256 amount);
6464
event ProposalExecuted(uint256 indexed proposalId);
65+
event ProposalDropped(uint256 indexed proposalId);
6566
event GovernanceProposerUpdated(address indexed governanceProposer);
6667
event ConfigurationUpdated(Timestamp indexed time);
6768

l1-contracts/src/governance/proposer/GovernanceProposer.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import {EmpireBase} from "./EmpireBase.sol";
1717
*
1818
* Note: any payload which passes through this contract will have a call to GSEPayload.amIValid appended to the
1919
* list of actions before it is proposed to Governance.
20-
* This will cause the proposal to revert if 2/3 of all stake in the GSE are not staked on the canonical rollup after
21-
* the *original* payload is executed by Governance.
20+
* This will cause the proposal to revert if >2/3 of all stake in the GSE are not staked on the latest rollup after
21+
* the *original* payload is executed by Governance. Unless the latest and canonical rollup diverge, as that indicates
22+
* a misconfiguration issue (see GSEPayload for more details).
2223
*/
2324
contract GovernanceProposer is IGovernanceProposer, EmpireBase {
2425
IRegistry public immutable REGISTRY;

l1-contracts/test/AttestationLib.t.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import {TestBase} from "@test/base/Base.sol";
55
import {
66
AttestationLib, CommitteeAttestations, CommitteeAttestation
77
} from "@aztec/core/libraries/rollup/AttestationLib.sol";
8+
import {AttestationLibHelper} from "@test/helper_libraries/AttestationLibHelper.sol";
89
import {Signature} from "@aztec/shared/libraries/SignatureLib.sol";
910
import {Errors} from "@aztec/core/libraries/Errors.sol";
1011

1112
contract AttestationLibWrapper {
1213
using AttestationLib for CommitteeAttestations;
14+
using AttestationLibHelper for CommitteeAttestations;
1315

1416
function isEmpty(CommitteeAttestations memory _attestations) external pure returns (bool) {
1517
return AttestationLib.isEmpty(_attestations);
@@ -32,7 +34,7 @@ contract AttestationLibWrapper {
3234
pure
3335
returns (CommitteeAttestations memory)
3436
{
35-
return AttestationLib.packAttestations(_attestations);
37+
return AttestationLibHelper.packAttestations(_attestations);
3638
}
3739
}
3840

l1-contracts/test/Rollup.t.sol

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {stdStorage, StdStorage} from "forge-std/StdStorage.sol";
3838
import {RollupBuilder} from "./builder/RollupBuilder.sol";
3939
import {Ownable} from "@oz/access/Ownable.sol";
4040
import {AttestationLib, CommitteeAttestations} from "@aztec/core/libraries/rollup/AttestationLib.sol";
41+
import {AttestationLibHelper} from "@test/helper_libraries/AttestationLibHelper.sol";
4142
// solhint-disable comprehensive-interface
4243

4344
/**
@@ -241,7 +242,7 @@ contract RollupTest is RollupBase {
241242
});
242243
bytes32 realBlobHash = this.getBlobHashes(data.blobCommitments)[0];
243244
vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidBlobHash.selector, blobHashes[0], realBlobHash));
244-
rollup.propose(args, AttestationLib.packAttestations(attestations), signers, data.blobCommitments);
245+
rollup.propose(args, AttestationLibHelper.packAttestations(attestations), signers, data.blobCommitments);
245246
}
246247

247248
function testExtraBlobs() public setUpFor("mixed_block_1") {
@@ -323,7 +324,7 @@ contract RollupTest is RollupBase {
323324
stateReference: EMPTY_STATE_REFERENCE,
324325
oracleInput: OracleInput(0)
325326
});
326-
rollup.propose(args, AttestationLib.packAttestations(attestations), signers, data.blobCommitments);
327+
rollup.propose(args, AttestationLibHelper.packAttestations(attestations), signers, data.blobCommitments);
327328
}
328329

329330
function testInvalidL2Fee() public setUpFor("mixed_block_1") {
@@ -349,7 +350,7 @@ contract RollupTest is RollupBase {
349350
stateReference: EMPTY_STATE_REFERENCE,
350351
oracleInput: OracleInput(0)
351352
});
352-
rollup.propose(args, AttestationLib.packAttestations(attestations), signers, data.blobCommitments);
353+
rollup.propose(args, AttestationLibHelper.packAttestations(attestations), signers, data.blobCommitments);
353354
}
354355

355356
function testProvingFeeUpdates() public setUpFor("mixed_block_1") {
@@ -455,7 +456,7 @@ contract RollupTest is RollupBase {
455456
stateReference: EMPTY_STATE_REFERENCE,
456457
oracleInput: OracleInput(0)
457458
});
458-
rollup.propose(args, AttestationLib.packAttestations(attestations), signers, data.blobCommitments);
459+
rollup.propose(args, AttestationLibHelper.packAttestations(attestations), signers, data.blobCommitments);
459460
assertEq(testERC20.balanceOf(header.coinbase), 0, "invalid coinbase balance");
460461
}
461462

@@ -674,7 +675,7 @@ contract RollupTest is RollupBase {
674675
stateReference: EMPTY_STATE_REFERENCE,
675676
oracleInput: OracleInput(0)
676677
});
677-
rollup.propose(args, AttestationLib.packAttestations(attestations), signers, new bytes(144));
678+
rollup.propose(args, AttestationLibHelper.packAttestations(attestations), signers, new bytes(144));
678679
}
679680

680681
function testRevertInvalidCoinbase() public setUpFor("empty_block_1") {
@@ -697,7 +698,7 @@ contract RollupTest is RollupBase {
697698
stateReference: EMPTY_STATE_REFERENCE,
698699
oracleInput: OracleInput(0)
699700
});
700-
rollup.propose(args, AttestationLib.packAttestations(attestations), signers, new bytes(144));
701+
rollup.propose(args, AttestationLibHelper.packAttestations(attestations), signers, new bytes(144));
701702
}
702703

703704
function testSubmitProofNonExistentBlock() public setUpFor("empty_block_1") {

l1-contracts/test/base/RollupBase.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {ProposeArgs, OracleInput, ProposeLib} from "@aztec/core/libraries/rollup
1818
import {
1919
CommitteeAttestation, CommitteeAttestations, AttestationLib
2020
} from "@aztec/core/libraries/rollup/AttestationLib.sol";
21+
import {AttestationLibHelper} from "@test/helper_libraries/AttestationLibHelper.sol";
2122

2223
import {Inbox} from "@aztec/core/messagebridge/Inbox.sol";
2324
import {Outbox} from "@aztec/core/messagebridge/Outbox.sol";
@@ -183,7 +184,7 @@ contract RollupBase is DecoderBase {
183184
if (_revertMsg.length > 0) {
184185
vm.expectRevert(_revertMsg);
185186
}
186-
rollup.propose(args, AttestationLib.packAttestations(attestations), signers, blobCommitments);
187+
rollup.propose(args, AttestationLibHelper.packAttestations(attestations), signers, blobCommitments);
187188

188189
if (_revertMsg.length > 0) {
189190
return;

0 commit comments

Comments
 (0)