Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface IProposalValidator is ISemver {
error ProposalValidator_ExceedsDistributionThreshold();
error ProposalValidator_InvalidOptionsLength();
error ProposalValidator_AttestationRevoked();
error ProposalValidator_AttestationExpired();
error ProposalValidator_InvalidAttestationSchema();
error ProposalValidator_InvalidCriteriaValue();
error ProposalValidator_InvalidAgainstThreshold();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,11 @@
"name": "VotingCycleDataSet",
"type": "event"
},
{
"inputs": [],
"name": "ProposalValidator_AttestationExpired",
"type": "error"
},
{
"inputs": [],
"name": "ProposalValidator_AttestationRevoked",
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@
"sourceCodeHash": "0x18f43b227decd0f2a895b8b55e23fa6a47706697c272bbf2482b3f912be446e1"
},
"src/governance/ProposalValidator.sol:ProposalValidator": {
"initCodeHash": "0x0dfc44cf456909f524602c8d2b3e5793e04b59da994a8268bc59071aad567c77",
"sourceCodeHash": "0xbe31385272e8ecf4fd0bf52e768efcf9b6b4199f23707d74949c11ee6578e6de"
"initCodeHash": "0xbac284f6ec21a5d65d5b86d7e6406e0805d77e15dc4bd66397f0111701110e0e",
"sourceCodeHash": "0x58048692d1da18d17958b2dc950055ae2a58ab645385b0aed3528b289dfee21d"
},
"src/legacy/DeployerWhitelist.sol:DeployerWhitelist": {
"initCodeHash": "0x53099379ed48b87f027d55712dbdd1da7d7099925426eb0531da9c0012e02c29",
Expand Down
14 changes: 11 additions & 3 deletions packages/contracts-bedrock/src/governance/ProposalValidator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ contract ProposalValidator is OwnableUpgradeable, ReinitializableBase, ISemver {
/// @notice Thrown when an attestation is revoked.
error ProposalValidator_AttestationRevoked();

/// @notice Thrown when the attestation is expired.
error ProposalValidator_AttestationExpired();

/// @notice Thrown when an attestation schema is invalid.
error ProposalValidator_InvalidAttestationSchema();

Expand Down Expand Up @@ -210,7 +213,7 @@ contract ProposalValidator is OwnableUpgradeable, ReinitializableBase, ISemver {

/// @notice The schema UID for attestations in the Ethereum Attestation Service for checking if the caller
/// is an approved proposer.
/// @dev Schema format: { approvedProposer: address, proposalType: uint8 }
/// @dev Schema format: { proposalType: uint8, date: string }
bytes32 public immutable APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID;

/// @notice The schema UID for attestations in the Ethereum Attestation Service for checking if the caller
Expand Down Expand Up @@ -911,11 +914,16 @@ contract ProposalValidator is OwnableUpgradeable, ReinitializableBase, ISemver {
revert ProposalValidator_AttestationRevoked();
}

(address approvedDelegate, uint8 proposalType) = abi.decode(attestation.data, (address, uint8));
// check if the attestation is expired
if (attestation.expirationTime != 0 && attestation.expirationTime < block.timestamp) {
revert ProposalValidator_AttestationExpired();
}

(uint8 proposalType,) = abi.decode(attestation.data, (uint8, string));

if (
attestation.attester != owner() || attestation.schema != APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID
|| approvedDelegate != _msgSender() || proposalType != uint8(_expectedProposalType)
|| attestation.recipient != _msgSender() || proposalType != uint8(_expectedProposalType)
) {
revert ProposalValidator_InvalidAttestation();
}
Expand Down
46 changes: 36 additions & 10 deletions packages/contracts-bedrock/test/governance/ProposalValidator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ contract ProposalValidator_Init is CommonTest {
uint256 public constant OPTIMISTIC_MODULE_PERCENT_DIVISOR = 10_000;
uint8 public constant APPROVAL_VOTING_MODULE_ID = 1;
uint8 public constant OPTIMISTIC_VOTING_MODULE_ID = 2;
uint64 public constant ATT_EXPIRATION_TIME = 10 days;

address owner;
address user;
Expand Down Expand Up @@ -561,7 +562,7 @@ contract ProposalValidator_Init is CommonTest {
// Create schemas
vm.prank(owner);
APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID = ISchemaRegistry(Predeploys.SCHEMA_REGISTRY).register(
"address approvedAddress,uint8 proposalType", ISchemaResolver(address(0)), true
"uint8 proposalType,string date", ISchemaResolver(address(0)), true
);

vm.prank(owner);
Expand All @@ -588,11 +589,11 @@ contract ProposalValidator_Init is CommonTest {
AttestationRequest({
schema: APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID,
data: AttestationRequestData({
recipient: address(0),
expirationTime: 0,
recipient: _delegate,
expirationTime: uint64(block.timestamp + ATT_EXPIRATION_TIME),
revocable: true,
refUID: bytes32(0),
data: abi.encode(_delegate, _proposalType),
data: abi.encode(_proposalType, "2000-01-01"),
value: 0
})
})
Expand Down Expand Up @@ -948,6 +949,21 @@ contract ProposalValidator_SubmitUpgradeProposal_TestFail is ProposalValidator_I
);
}

function testFuzz_submitUpgradeProposal_attestationExpired_reverts(uint8 proposalTypeValue) public {
proposalTypeValue = uint8(bound(proposalTypeValue, 0, 1));
ProposalValidator.ProposalType proposalType = ProposalValidator.ProposalType(proposalTypeValue);
uint248 againstThreshold = 5000;
bytes32 attestationUid = _createApprovedProposerAttestation(topDelegate_A, proposalType);

// warp the time to after the attestation expiration time
vm.warp(block.timestamp + ATT_EXPIRATION_TIME + 1);
vm.expectRevert(ProposalValidator.ProposalValidator_AttestationExpired.selector);
vm.prank(topDelegate_A);
validator.submitUpgradeProposal(
againstThreshold, proposalDescription, attestationUid, proposalType, CYCLE_NUMBER
);
}

function test_submitUpgradeProposal_zeroAgainstThreshold_reverts() public {
uint248 zeroThreshold = 0;
ProposalValidator.ProposalType proposalType = ProposalValidator.ProposalType.ProtocolOrGovernorUpgrade;
Expand Down Expand Up @@ -1083,11 +1099,11 @@ contract ProposalValidator_SubmitUpgradeProposal_TestFail is ProposalValidator_I
AttestationRequest({
schema: APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID,
data: AttestationRequestData({
recipient: address(0),
expirationTime: 0,
recipient: topDelegate_A,
expirationTime: uint64(block.timestamp + ATT_EXPIRATION_TIME),
revocable: false,
refUID: bytes32(0),
data: abi.encode(topDelegate_A, proposalType),
data: abi.encode(proposalType, "2000-01-01"),
value: 0
})
})
Expand Down Expand Up @@ -1292,6 +1308,16 @@ contract ProposalValidator_SubmitCouncilMemberElectionsProposal_TestFail is Prop
);
}

function testFuzz_submitCouncilMemberElectionsProposal_attestationExpired_reverts() public {
// warp the time to after the attestation expiration time
vm.warp(block.timestamp + ATT_EXPIRATION_TIME + 1);
vm.expectRevert(ProposalValidator.ProposalValidator_AttestationExpired.selector);
vm.prank(topDelegate_A);
validator.submitCouncilMemberElectionsProposal(
criteriaValue, optionDescriptions, proposalDescription, attestationUid, CYCLE_NUMBER
);
}

function test_submitCouncilMemberElectionsProposal_zeroOptions_reverts() public {
string[] memory emptyOptions = new string[](0);

Expand Down Expand Up @@ -1370,11 +1396,11 @@ contract ProposalValidator_SubmitCouncilMemberElectionsProposal_TestFail is Prop
AttestationRequest({
schema: APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID,
data: AttestationRequestData({
recipient: address(0),
expirationTime: 0,
recipient: topDelegate_A,
expirationTime: uint64(block.timestamp + ATT_EXPIRATION_TIME),
revocable: false,
refUID: bytes32(0),
data: abi.encode(topDelegate_A, ProposalValidator.ProposalType.CouncilMemberElections),
data: abi.encode(ProposalValidator.ProposalType.CouncilMemberElections, "2000-01-01"),
value: 0
})
})
Expand Down