From ed94cc64153c89bf2c11844f0f3be0ec0749b61d Mon Sep 17 00:00:00 2001 From: OneTony Date: Fri, 9 May 2025 17:49:50 +0300 Subject: [PATCH 01/19] feat: proposal validator spec --- specs/experimental/gov-proposal-validator.md | 444 +++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 specs/experimental/gov-proposal-validator.md diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md new file mode 100644 index 000000000..1e8ac6c95 --- /dev/null +++ b/specs/experimental/gov-proposal-validator.md @@ -0,0 +1,444 @@ +# Governance Proposal Validator + + + +**Table of Contents** + +- [Overview](#overview) +- [Design](#design) +- [Roles](#roles) +- [Interface](#interface) + - [Public Functions](#public-functions) + - [Properties](#properties) + - [Structs](#structs) + - [Enums](#enums) + - [Events](#events) +- [EAS Integration](#eas-integration) + - [Why EAS?](#why-eas) + - [Implementation Details](#implementation-details) +- [Proposal uniqueness](#proposal-uniqueness) +- [Invariants](#invariants) +- [Security Considerations](#security-considerations) + + + +## Overview + +This document specifies the `ProposalValidator` contract, designed to enable permissionless proposals in the Optimism governance system. The contract allows proposal submissions based on predefined rules and automated checks, removing the need for manual gatekeeping. + +## Design + +The `ProposalValidator` manages the proposal lifecycle through three main functions: + +- `submitProposal`: Records new proposals +- `approveProposal`: Handles proposal approvals +- `moveToVote`: Transitions approved proposals to voting phase + +The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers for specific proposal types. For detailed flows of each proposal, see [Design Document Link]. + +## Roles + +The contract has a single `owner` role (Optimism Foundation) with permissions to: + +- Set minimum voting power threshold for delegate approvals +- Configure voting cycle parameters +- Set maximum token distribution limits for proposals + +## Interface + +### Public Functions + +`submitProposal` + +Submits a proposal for approval and voting. Based on the `ProposalType` provided this will require different validation checks. + +For `ProtocolOrGovernorUpgrade` , `MaintenanceUpgradeProposals`, and `CouncilMemberElections` types: + +- MUST be called by an approved address +- MUST check if the proposal is a duplicate +- MUST provide a valid proposal type configurator +- MUST provide a valid attestation UID +- MUST NOT transfer any tokens or change any allowances +- MUST emit `ProposalSubmitted` event +- MUST store proposal data + +For `GovernanceFund` and `CouncilBudget` types: + +- The user MUST use the `submitFundingProposal` that uses specific `calldata` pre-defined by the owner + +Note: `MaintenanceUpgradeProposals`type can move straight to voting if all submission checks pass, unlike the rest of the proposal where they need to collect a number of approvals by top delegates in order to move to vote. This call should be atomic. + +```solidity + function submitProposal( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldata, + string memory _description, + ProposalType _proposalType, + uint8 _proposalTypeConfiguration, + bytes32 _attestationUid +) external returns (bytes32 proposalHash_); +``` + +`submitFundingProposal` + +Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP tokens. + +- CAN be called by anyone +- MUST check if the proposal is a duplicate +- MUST provide a valid proposal type configurator +- MUST use the `Predeploys.GOVERNANCE_TOKEN` and `TRANSFER_SIGNATURE`to create the `calldata` +- MUST NOT request to transfer more than `distributionThreshold` tokens +- MUST emit `ProposalSubmitted` event +- MUST store proposal data + +```solidity +function submitFundingProposal( + address _to, + uint256 _amount, + string memory _description, + ProposalType _proposalType, + uint8 _proposalTypeConfiguration +) external returns (bytes32 proposalHash_); +``` + +`approveProposal` + +Approves a proposal before being moved for voting, used by the top delegates. + +- MUST check if proposal hash corresponds to a valid proposal +- MUST check if caller is has enough voting power to call the function and approve a proposal +- MUST check if caller has already approved the same proposal +- MUST store the approval vote +- MUST emit `ProposalApproved` when successfully called + +```solidity +function approveProposal(bytes32 _proposalHash) external +``` + +`moveToVote` + +Checks if the provided proposal is ready to move for voting. Based on the Proposal Type different checks are being validated. If all checks pass then `OptimismGovernor.propose` is being called to forward the proposal for voting. This function can be called by anyone + +For `ProtocolOrGovernorUpgrade`: + +- MUST check if provided data produce the same `proposalHash` +- proposal MUST have gathered X amount of approvals by top delegates +- MUST check if proposal has already moved for voting +- MUST emit `ProposalMovedToVote` event + +For `MaintenanceUpgradeProposals`: + +- This type does not require any checks and is being forwarded to the Governor contracts, this should happen atomic. +- MUST emit `ProposalMovedToVote` event + +For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget`: + +- MUST check if provided data produce the same `proposalHash` +- proposal MUST have gathered X amount of approvals by top delegates +- proposal MUST be moved to vote during a valid voting cycle +- MUST check if proposal has already moved for voting +- MUST check if the total amount of tokens that can possible be distributed during this voting cycle does not go over the `VotingCycleData.votingCycleDistributionLimit` +- MUST emit `ProposalMovedToVote` event + +```solidity +function moveToVote( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldata, + string memory description +) + external + returns (uint256 governorProposalId_) +``` + +`canSignOff` + +Returns true if a delegate address has enough voting power to approve a proposal. + +- Can be called by anyone +- MUST return TRUE if the delegates’ voting power is above the `minimumVotingPower` + +```solidity +function canSignOff(address _delegate) public view returns (bool canSignOff_) +``` + +`setMinimumVotingPower` + +Sets the minimum voting power a delegate must have in order to be eligible to approve a proposal. + +- MUST only be called by the owner of the contract +- MUST change the existing minimum voting power to the new +- MUST emit `MinimumVotingPowerSet` event + +```solidity +function setMinimumVotingPower(uint256 _minimumVotingPower) external +``` + +`setVotingCycleData` + +Sets the start and the duration of a voting cycle. + +- MUST only be called by the owner of the contract +- MUST NOT change an existing voting cycle +- MUST emit `VotingCycleSet` event + +`setDistributionThreshold` + +```solidity +function setVotingCycleData(uint256 _cycleNumber, uint256 _startBlock, uint256 _duration, uint256 _distributionLimit) external +``` + +Sets the maximum distribution threshold a proposal can request. + +- MUST only be called by the owner of the contract +- MUST change the previous threshold to the new +- MUST emit `DistributionThresholdSet` event + +```solidity +function setDistributionThreshold(uint256 _threshold) external +``` + +`setProposalTypeApprovalThreshold` + +Sets the number of approvals a specific proposal type should have before being able to move for voting. + +- MUST only be called by the owner of the contract +- MUST change the previous value to the new +- MUST emit `ProposalTypeApprovalThresholdSet` event + +```solidity +function setProposalTypeApprovalThreshold(uint8 _proposalTypeId, uint256 _approvalThreshold) external +``` + +### Properties + +`ATTESTATION_SCHEMA_UID` + +The schema UID that is used to verify attestation for approved addresses that can submit proposals for specific `ProposalTypes`. + +```solidity +/// Schema { approvedProposer: address, proposalType: uint8 } +bytes32 public immutable ATTESTATION_SCHEMA_UID; +``` + +`TRANSFER_SIGNATURE` + +The 4byte signature of ERC20.transfer, will be used for creating the calldata for funding proposals + +```solidity +bytes4 public constant TRANSFER_SIGNATURE = 0xa9059cbb; +``` + +`GOVERNOR` + +The address of the Optimism Governor contract + +```solidity +IOptimismGovernor public immutable GOVERNOR; +``` + +`minimumVotingPower` + +The minimum voting power a delegate must have in order to be eligible for approving a proposal + +```solidity +uint256 public minimumVotingPower; +``` + +`distributionThreshold` + +The maximum amount of tokens a proposal can request + +```solidity +uint256 public distributionThreshold; +``` + +`votingCycles` + +A mapping that stores the data for each voting cycle. + +```solidity +mapping(uint256 => VotingCycleData) public votingCycles; +``` + +`_proposals` + +A mapping that stores each submitted proposals’ data based on its `proposalHash`. The proposal hash is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array and the `description` + +```solidity +mapping(bytes32 => ProposalData) private _proposals; +``` + +`_proposalRequiredApprovals` + +A mapping that stores the number of approvals each proposal type requires in order to be able to move for voting. + +```solidity +mapping(ProposalType => uint256) private _proposalRequiredApprovals; +``` + +### Structs + +`ProposalData` + +A struct that holds all the data for a single proposal. Consists of: + +- `proposer`: The address that submitted the proposal +- `proposalType`: The type of the proposal +- `proposalTypeConfigurator`: The voting type for the proposal +- `inVoting`: Returns true if the proposal has already been submitted for voting +- `delegateApprovals`: Mapping of addresses that approved the specific proposal +- `approvalsCounter`: The number of approvals the specific proposal has received + +```solidity +struct ProposalData { + address proposer; + ProposalType proposalType; + uint8 proposalTypeConfigurator; + bool inVoting; + mapping(address => bool) delegateApprovals; + uint256 approvalsCounter; +} +``` + +`VotingCycleData` + +A struct that stores the start block of a voting cycle and it’s duration. + +- `startingBlock`: The block number/timestamp that the voting cycle starts +- `duration`: The duration of the specific voting cycle +- `votingCycleDistributionLimit`: A more general distribution amount limit tied to the voting cycle + +```solidity +struct VotingCycleData { + uint256 startingBlock; + uint256 duration; + uint256 votingCycleDistributionLimit; +} +``` + +### Enums + +`ProposalType` + +Defines the different types of proposals that can be submitted. Based on each type it will be determined which validation checks should be run when submitting and moving to vote a proposal. + +The proposal types that are supported are: + +- Protocol or Governor Upgrades +- Maintenance Upgrades +- Council Member Elections +- Governance Funding +- Council Budget + +```solidity + enum ProposalType { + ProtocolOrGovernorUpgrade, + MaintenanceUpgrade, + CouncilMemberElections, + GovernanceFund, + CouncilBudget + } +``` + +### Events + +`ProposalSubmitted` + +MUST be triggered when `submitProposal` is successfully called. + +```solidity +event ProposalSubmitted( + uint256 indexed proposalHash, + address indexed proposer, + address[] targets, + uint256[] values, + bytes[] calldatas, + string description, + ProposalType proposalType +); +``` + +`ProposalApproved` + +MUST be triggered when `approveProposal` is successfully called. + +```solidity +event ProposalApproved(uint256 indexed proposalHash, address indexed approver); +``` + +`ProposalMovedToVote` + +MUST be triggered when `moveToVote` is successfully called. + +```solidity +event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor); +``` + +## EAS Integration + +`ProposalValidator` integrates Ethereum Attestation Service (EAS) to handle proposer authorization for specific `ProposalType`s. This removes the need for maintaining separate allowlists or custom registries, reducing contract complexity and offloading identity tracking to a proven system. + +### Why EAS? + +- **Decentralized trust**: Instead of custom logic, Optimism uses attestations signed by the Foundation to authorize proposers. +- **Low integration overhead**: `EAS` and `SchemaRegistry` are predeploys on Optimism, requiring no additional deployments or infrastructure. +- **Schema validation**: Ensures attestations follow strict data formats (`address approvedDelegate, uint8 proposalType`). +- **Revocability and expiration**: EAS supports expiration and revocation semantics natively, allowing dynamic control over authorized proposers. + +### Implementation Details + +- On setup, the contract registers a schema in the predeployed `SchemaRegistry`. +- The `submitProposal` function validates attestations by: + - Ensuring the attestation UID matches the registered schema. + - Verifying the attester is the contract owner (Optimism Foundation). + - Decoding the attestation to check the proposer’s address and proposal type. +- Only proposals of type `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` require valid EAS attestations. + +**Technical implementation docs: [EAS Integration](https://www.notion.so/EAS-Integration-1de9a4c092c780478e19cc8175aa054e?pvs=21)** + +## Proposal uniqueness + +To prevent duplicate proposals, the contract enforces uniqueness by hashing the defining parameters of each proposal and checking against a registry of previously submitted proposals. + +A proposal is uniquely identified by the tuple: + +- `targets[]`: array of addresses the proposal will call +- `values[]`: array of ETH values to send with each call +- `calldatas[]`: array of calldata payloads for each call +- `description`: a string describing the proposal + +These elements are ABI-encoded and hashed: + +```solidity +keccak256(abi.encode(targets, values, calldatas, keccak256(bytes(description)))); +``` + +This hash serves as a unique identifier for the proposal. The contract stores submitted proposals in: + +```solidity +mapping(bytes32 => ProposalData) private _proposals; +``` + +When a new proposal is submitted, the contract checks that `_proposals[proposalHash]` is empty (e.g., `proposer == address(0)`). If data exists at that key, the proposal is rejected as a duplicate. + +This mechanism guarantees that proposals with the same intent and execution logic cannot be submitted multiple times, maintaining proposal integrity and preventing spam. + +## Invariants + +- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycle` and `distributionThreshold` +- It MUST allow only eligible addresses to approve a proposal +- It MUST allow only authorizeds addresses to submit proposals for types `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` +- It MUST NOT transfer any tokens or ETH for `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` proposal types +- It MUST emit the following events: + - `ProposalSubmitted` + - `ProposalApproved` + - `ProposalMovedToVote` + +## Security Considerations + +- **Role-Based Restrictions:** The `owner` role should be securely managed, as it holds critical permissions such as setting voting power thresholds, configuring voting cycles, and distribution limits. Any compromise could significantly impact governance. +- **Attestation Validation:** Two key aspects need consideration: + - The Optimism Foundation must have a secure and thorough process for validating addresses before issuing attestations for specific proposal types + - The contract must properly verify attestation expiration and revocation status to prevent the use of outdated or invalid attestations \ No newline at end of file From 1b3be9924700a234c3d7be17b952423b5389c982 Mon Sep 17 00:00:00 2001 From: OneTony Date: Fri, 9 May 2025 18:18:44 +0300 Subject: [PATCH 02/19] feat: add proposal type data struct --- specs/experimental/gov-proposal-validator.md | 28 +++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 1e8ac6c95..a13f28844 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -267,20 +267,20 @@ mapping(uint256 => VotingCycleData) public votingCycles; A mapping that stores each submitted proposals’ data based on its `proposalHash`. The proposal hash is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array and the `description` ```solidity -mapping(bytes32 => ProposalData) private _proposals; +mapping(bytes32 => ProposalSubmissionData) private _proposals; ``` -`_proposalRequiredApprovals` +`_proposaTypesData` -A mapping that stores the number of approvals each proposal type requires in order to be able to move for voting. +A mapping that stores data related to each proposal type. ```solidity -mapping(ProposalType => uint256) private _proposalRequiredApprovals; +mapping(ProposalType => ProposalTypeData) private _proposaTypesData; ``` ### Structs -`ProposalData` +`ProposalSubmissionData` A struct that holds all the data for a single proposal. Consists of: @@ -292,7 +292,7 @@ A struct that holds all the data for a single proposal. Consists of: - `approvalsCounter`: The number of approvals the specific proposal has received ```solidity -struct ProposalData { +struct ProposalSubmissionData { address proposer; ProposalType proposalType; uint8 proposalTypeConfigurator; @@ -302,6 +302,20 @@ struct ProposalData { } ``` +`ProposalTypeData` + +A struct that holds data for each proposal type. + +- `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. +- `proposalTypeConfigurator`: The accepted proposal type configurators that can be used for each proposal type. + +```solidity +struct ProposalTypeData { + uint256 requiredApprovals; + uint8[] proposalTypeConfigurator; +} +``` + `VotingCycleData` A struct that stores the start block of a voting cycle and it’s duration. @@ -418,7 +432,7 @@ keccak256(abi.encode(targets, values, calldatas, keccak256(bytes(description)))) This hash serves as a unique identifier for the proposal. The contract stores submitted proposals in: ```solidity -mapping(bytes32 => ProposalData) private _proposals; +mapping(bytes32 => ProposalSubmissionData) private _proposals; ``` When a new proposal is submitted, the contract checks that `_proposals[proposalHash]` is empty (e.g., `proposer == address(0)`). If data exists at that key, the proposal is rejected as a duplicate. From 06a2df62693e371cded449b93fd061f8909027aa Mon Sep 17 00:00:00 2001 From: OneTony Date: Fri, 9 May 2025 22:31:56 +0300 Subject: [PATCH 03/19] fix: comments --- specs/experimental/gov-proposal-validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index a13f28844..25cd9cb1d 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -307,12 +307,12 @@ struct ProposalSubmissionData { A struct that holds data for each proposal type. - `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. -- `proposalTypeConfigurator`: The accepted proposal type configurators that can be used for each proposal type. +- `validProposalTypeConfigurators`: The accepted proposal type configurators that can be used for each proposal type. ```solidity struct ProposalTypeData { uint256 requiredApprovals; - uint8[] proposalTypeConfigurator; + mapping(uint8 => bool) validProposalTypeConfigurators; } ``` From a64f15997f2f933c0c4504110522bf403f6b8acd Mon Sep 17 00:00:00 2001 From: OneTony Date: Mon, 12 May 2025 18:12:01 +0300 Subject: [PATCH 04/19] fix: nit improvements --- specs/experimental/gov-proposal-validator.md | 48 ++++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 25cd9cb1d..14fe7df48 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -34,7 +34,7 @@ The `ProposalValidator` manages the proposal lifecycle through three main functi - `approveProposal`: Handles proposal approvals - `moveToVote`: Transitions approved proposals to voting phase -The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers for specific proposal types. For detailed flows of each proposal, see [Design Document Link]. +The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers for specific proposal types. For detailed flows of each proposal, see https://github.com/ethereum-optimism/design-docs/pull/260. ## Roles @@ -50,10 +50,9 @@ The contract has a single `owner` role (Optimism Foundation) with permissions to `submitProposal` -Submits a proposal for approval and voting. Based on the `ProposalType` provided this will require different validation checks. - -For `ProtocolOrGovernorUpgrade` , `MaintenanceUpgradeProposals`, and `CouncilMemberElections` types: +Submits a proposal for approval and voting. Based on the `ProposalType` provided this will require different validation checks and actions. +- MUST only be called for `ProtocolOrGovernorUpgrade` , `MaintenanceUpgradeProposals`, or `CouncilMemberElections` types - MUST be called by an approved address - MUST check if the proposal is a duplicate - MUST provide a valid proposal type configurator @@ -82,8 +81,9 @@ Note: `MaintenanceUpgradeProposals`type can move straight to voting if all submi `submitFundingProposal` -Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP tokens. +Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP tokens for approval and voting. +- MUST only be called for `GovernanceFund` or `CouncilBudget` proposal type - CAN be called by anyone - MUST check if the proposal is a duplicate - MUST provide a valid proposal type configurator @@ -122,21 +122,21 @@ Checks if the provided proposal is ready to move for voting. Based on the Propos For `ProtocolOrGovernorUpgrade`: -- MUST check if provided data produce the same `proposalHash` -- proposal MUST have gathered X amount of approvals by top delegates +- MUST check if provided data produces a valid `proposalHash` +- Proposal MUST have gathered X amount of approvals by top delegates - MUST check if proposal has already moved for voting - MUST emit `ProposalMovedToVote` event For `MaintenanceUpgradeProposals`: -- This type does not require any checks and is being forwarded to the Governor contracts, this should happen atomic. +- This type does not require any checks and is being forwarded to the Governor contracts, this should happen atomically. - MUST emit `ProposalMovedToVote` event For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget`: - MUST check if provided data produce the same `proposalHash` -- proposal MUST have gathered X amount of approvals by top delegates -- proposal MUST be moved to vote during a valid voting cycle +- Proposal MUST have gathered X amount of approvals by top delegates +- Proposal MUST be moved to vote during a valid voting cycle - MUST check if proposal has already moved for voting - MUST check if the total amount of tokens that can possible be distributed during this voting cycle does not go over the `VotingCycleData.votingCycleDistributionLimit` - MUST emit `ProposalMovedToVote` event @@ -183,16 +183,16 @@ Sets the start and the duration of a voting cycle. - MUST NOT change an existing voting cycle - MUST emit `VotingCycleSet` event -`setDistributionThreshold` - ```solidity function setVotingCycleData(uint256 _cycleNumber, uint256 _startBlock, uint256 _duration, uint256 _distributionLimit) external ``` +`setDistributionThreshold` + Sets the maximum distribution threshold a proposal can request. - MUST only be called by the owner of the contract -- MUST change the previous threshold to the new +- MUST change the previous threshold to the new one - MUST emit `DistributionThresholdSet` event ```solidity @@ -204,7 +204,7 @@ function setDistributionThreshold(uint256 _threshold) external Sets the number of approvals a specific proposal type should have before being able to move for voting. - MUST only be called by the owner of the contract -- MUST change the previous value to the new +- MUST change the previous value to the new one - MUST emit `ProposalTypeApprovalThresholdSet` event ```solidity @@ -215,7 +215,7 @@ function setProposalTypeApprovalThreshold(uint8 _proposalTypeId, uint256 _approv `ATTESTATION_SCHEMA_UID` -The schema UID that is used to verify attestation for approved addresses that can submit proposals for specific `ProposalTypes`. +The EAS' schema UID that is used to verify attestation for approved addresses that can submit proposals for specific `ProposalTypes`. ```solidity /// Schema { approvedProposer: address, proposalType: uint8 } @@ -224,7 +224,7 @@ bytes32 public immutable ATTESTATION_SCHEMA_UID; `TRANSFER_SIGNATURE` -The 4byte signature of ERC20.transfer, will be used for creating the calldata for funding proposals +The 4bytes signature of ERC20.transfer, will be used for creating the calldata for funding proposals ```solidity bytes4 public constant TRANSFER_SIGNATURE = 0xa9059cbb; @@ -270,12 +270,12 @@ A mapping that stores each submitted proposals’ data based on its `proposalHas mapping(bytes32 => ProposalSubmissionData) private _proposals; ``` -`_proposaTypesData` +`_proposalTypesData` A mapping that stores data related to each proposal type. ```solidity -mapping(ProposalType => ProposalTypeData) private _proposaTypesData; +mapping(ProposalType => ProposalTypeData) private _proposalTypesData; ``` ### Structs @@ -307,7 +307,7 @@ struct ProposalSubmissionData { A struct that holds data for each proposal type. - `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. -- `validProposalTypeConfigurators`: The accepted proposal type configurators that can be used for each proposal type. +- `validProposalTypeConfigurators` : The accepted proposal type configurators that can be used for each proposal type. ```solidity struct ProposalTypeData { @@ -392,7 +392,7 @@ event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor ## EAS Integration -`ProposalValidator` integrates Ethereum Attestation Service (EAS) to handle proposer authorization for specific `ProposalType`s. This removes the need for maintaining separate allowlists or custom registries, reducing contract complexity and offloading identity tracking to a proven system. +`ProposalValidator` integrates Ethereum Attestation Service (EAS) to handle proposer authorization for specific `ProposalType`s, removing the need for adding custom logic to the contract. ### Why EAS? @@ -426,7 +426,7 @@ A proposal is uniquely identified by the tuple: These elements are ABI-encoded and hashed: ```solidity -keccak256(abi.encode(targets, values, calldatas, keccak256(bytes(description)))); +keccak256(abi.encode(targets, values, calldatas, description)); ``` This hash serves as a unique identifier for the proposal. The contract stores submitted proposals in: @@ -441,9 +441,9 @@ This mechanism guarantees that proposals with the same intent and execution logi ## Invariants -- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycle` and `distributionThreshold` -- It MUST allow only eligible addresses to approve a proposal -- It MUST allow only authorizeds addresses to submit proposals for types `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` +- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycleData,` `distributionThreshold`, and `proposalTypeApprovalThreshold` +- It MUST only allow eligible addresses to approve a proposal +- It MUST only allow authorizeds addresses to submit proposals for types `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` - It MUST NOT transfer any tokens or ETH for `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` proposal types - It MUST emit the following events: - `ProposalSubmitted` From 2465cca9fc3b0ef018d7ec78e40d93f67ae99e84 Mon Sep 17 00:00:00 2001 From: OneTony Date: Mon, 12 May 2025 19:56:10 +0300 Subject: [PATCH 05/19] fix: linting --- specs/experimental/gov-proposal-validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 14fe7df48..439fccf63 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -71,7 +71,7 @@ Note: `MaintenanceUpgradeProposals`type can move straight to voting if all submi function submitProposal( address[] memory _targets, uint256[] memory _values, - bytes[] memory _calldata, + bytes[] memory _calldatas, string memory _description, ProposalType _proposalType, uint8 _proposalTypeConfiguration, @@ -97,7 +97,7 @@ function submitFundingProposal( address _to, uint256 _amount, string memory _description, - ProposalType _proposalType, + ProposalType _proposalType, uint8 _proposalTypeConfiguration ) external returns (bytes32 proposalHash_); ``` From 18b9c600fd8563c50bbc048a29e19a45a219da7e Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Mon, 19 May 2025 17:44:16 +0300 Subject: [PATCH 06/19] fix: linting (#32) --- specs/experimental/gov-proposal-validator.md | 166 +++++++++++-------- 1 file changed, 101 insertions(+), 65 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 439fccf63..c56e14112 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -24,7 +24,9 @@ ## Overview -This document specifies the `ProposalValidator` contract, designed to enable permissionless proposals in the Optimism governance system. The contract allows proposal submissions based on predefined rules and automated checks, removing the need for manual gatekeeping. +This document specifies the `ProposalValidator` contract, designed to enable permissionless proposals in the Optimism +governance system. The contract allows proposal submissions based on predefined rules and automated checks, removing the +need for manual gatekeeping. ## Design @@ -34,7 +36,8 @@ The `ProposalValidator` manages the proposal lifecycle through three main functi - `approveProposal`: Handles proposal approvals - `moveToVote`: Transitions approved proposals to voting phase -The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers for specific proposal types. For detailed flows of each proposal, see https://github.com/ethereum-optimism/design-docs/pull/260. +The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers for specific proposal +types. For detailed flows of each proposal, see [design docs](https://github.com/ethereum-optimism/design-docs/pull/260). ## Roles @@ -50,9 +53,10 @@ The contract has a single `owner` role (Optimism Foundation) with permissions to `submitProposal` -Submits a proposal for approval and voting. Based on the `ProposalType` provided this will require different validation checks and actions. +Submits a proposal for approval and voting. Based on the `ProposalType` provided this will require different validation +checks and actions. -- MUST only be called for `ProtocolOrGovernorUpgrade` , `MaintenanceUpgradeProposals`, or `CouncilMemberElections` types +- MUST only be called for `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, or `CouncilMemberElections` types - MUST be called by an approved address - MUST check if the proposal is a duplicate - MUST provide a valid proposal type configurator @@ -65,10 +69,12 @@ For `GovernanceFund` and `CouncilBudget` types: - The user MUST use the `submitFundingProposal` that uses specific `calldata` pre-defined by the owner -Note: `MaintenanceUpgradeProposals`type can move straight to voting if all submission checks pass, unlike the rest of the proposal where they need to collect a number of approvals by top delegates in order to move to vote. This call should be atomic. +Note: `MaintenanceUpgradeProposals` type can move straight to voting if all submission checks pass, unlike the rest of the +proposal where they need to collect a number of approvals by top delegates in order to move to vote. This call should be +atomic. ```solidity - function submitProposal( +function submitProposal( address[] memory _targets, uint256[] memory _values, bytes[] memory _calldatas, @@ -87,18 +93,18 @@ Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP to - CAN be called by anyone - MUST check if the proposal is a duplicate - MUST provide a valid proposal type configurator -- MUST use the `Predeploys.GOVERNANCE_TOKEN` and `TRANSFER_SIGNATURE`to create the `calldata` +- MUST use the `Predeploys.GOVERNANCE_TOKEN` and `TRANSFER_SIGNATURE` to create the `calldata` - MUST NOT request to transfer more than `distributionThreshold` tokens - MUST emit `ProposalSubmitted` event - MUST store proposal data ```solidity function submitFundingProposal( - address _to, - uint256 _amount, - string memory _description, + address _to, + uint256 _amount, + string memory _description, ProposalType _proposalType, - uint8 _proposalTypeConfiguration + uint8 _proposalTypeConfiguration ) external returns (bytes32 proposalHash_); ``` @@ -118,7 +124,9 @@ function approveProposal(bytes32 _proposalHash) external `moveToVote` -Checks if the provided proposal is ready to move for voting. Based on the Proposal Type different checks are being validated. If all checks pass then `OptimismGovernor.propose` is being called to forward the proposal for voting. This function can be called by anyone +Checks if the provided proposal is ready to move for voting. Based on the Proposal Type different checks are being +validated. If all checks pass then `OptimismGovernor.propose` is being called to forward the proposal for voting. This +function can be called by anyone. For `ProtocolOrGovernorUpgrade`: @@ -138,7 +146,8 @@ For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget`: - Proposal MUST have gathered X amount of approvals by top delegates - Proposal MUST be moved to vote during a valid voting cycle - MUST check if proposal has already moved for voting -- MUST check if the total amount of tokens that can possible be distributed during this voting cycle does not go over the `VotingCycleData.votingCycleDistributionLimit` +- MUST check if the total amount of tokens that can possible be distributed during this voting cycle does not go over the + `VotingCycleData.votingCycleDistributionLimit` - MUST emit `ProposalMovedToVote` event ```solidity @@ -157,7 +166,7 @@ function moveToVote( Returns true if a delegate address has enough voting power to approve a proposal. - Can be called by anyone -- MUST return TRUE if the delegates’ voting power is above the `minimumVotingPower` +- MUST return TRUE if the delegates' voting power is above the `minimumVotingPower` ```solidity function canSignOff(address _delegate) public view returns (bool canSignOff_) @@ -184,7 +193,12 @@ Sets the start and the duration of a voting cycle. - MUST emit `VotingCycleSet` event ```solidity -function setVotingCycleData(uint256 _cycleNumber, uint256 _startBlock, uint256 _duration, uint256 _distributionLimit) external +function setVotingCycleData( + uint256 _cycleNumber, + uint256 _startBlock, + uint256 _duration, + uint256 _distributionLimit +) external ``` `setDistributionThreshold` @@ -208,14 +222,18 @@ Sets the number of approvals a specific proposal type should have before being a - MUST emit `ProposalTypeApprovalThresholdSet` event ```solidity -function setProposalTypeApprovalThreshold(uint8 _proposalTypeId, uint256 _approvalThreshold) external +function setProposalTypeApprovalThreshold( + uint8 _proposalTypeId, + uint256 _approvalThreshold +) external ``` ### Properties `ATTESTATION_SCHEMA_UID` -The EAS' schema UID that is used to verify attestation for approved addresses that can submit proposals for specific `ProposalTypes`. +The EAS' schema UID that is used to verify attestation for approved addresses that can submit proposals for specific +`ProposalTypes`. ```solidity /// Schema { approvedProposer: address, proposalType: uint8 } @@ -224,7 +242,7 @@ bytes32 public immutable ATTESTATION_SCHEMA_UID; `TRANSFER_SIGNATURE` -The 4bytes signature of ERC20.transfer, will be used for creating the calldata for funding proposals +The 4bytes signature of ERC20.transfer, will be used for creating the calldata for funding proposals. ```solidity bytes4 public constant TRANSFER_SIGNATURE = 0xa9059cbb; @@ -232,7 +250,7 @@ bytes4 public constant TRANSFER_SIGNATURE = 0xa9059cbb; `GOVERNOR` -The address of the Optimism Governor contract +The address of the Optimism Governor contract. ```solidity IOptimismGovernor public immutable GOVERNOR; @@ -240,7 +258,7 @@ IOptimismGovernor public immutable GOVERNOR; `minimumVotingPower` -The minimum voting power a delegate must have in order to be eligible for approving a proposal +The minimum voting power a delegate must have in order to be eligible for approving a proposal. ```solidity uint256 public minimumVotingPower; @@ -248,7 +266,7 @@ uint256 public minimumVotingPower; `distributionThreshold` -The maximum amount of tokens a proposal can request +The maximum amount of tokens a proposal can request. ```solidity uint256 public distributionThreshold; @@ -264,7 +282,8 @@ mapping(uint256 => VotingCycleData) public votingCycles; `_proposals` -A mapping that stores each submitted proposals’ data based on its `proposalHash`. The proposal hash is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array and the `description` +A mapping that stores each submitted proposals' data based on its `proposalHash`. The proposal hash is produced by hashing +the ABI encoded `targets` array, the `values` array, the `calldatas` array and the `description`. ```solidity mapping(bytes32 => ProposalSubmissionData) private _proposals; @@ -293,12 +312,12 @@ A struct that holds all the data for a single proposal. Consists of: ```solidity struct ProposalSubmissionData { - address proposer; - ProposalType proposalType; - uint8 proposalTypeConfigurator; - bool inVoting; - mapping(address => bool) delegateApprovals; - uint256 approvalsCounter; + address proposer; + ProposalType proposalType; + uint8 proposalTypeConfigurator; + bool inVoting; + mapping(address => bool) delegateApprovals; + uint256 approvalsCounter; } ``` @@ -306,19 +325,19 @@ struct ProposalSubmissionData { A struct that holds data for each proposal type. -- `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. -- `validProposalTypeConfigurators` : The accepted proposal type configurators that can be used for each proposal type. +- `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. +- `validProposalTypeConfigurators`: The accepted proposal type configurators that can be used for each proposal type. ```solidity struct ProposalTypeData { - uint256 requiredApprovals; - mapping(uint8 => bool) validProposalTypeConfigurators; + uint256 requiredApprovals; + mapping(uint8 => bool) validProposalTypeConfigurators; } ``` `VotingCycleData` -A struct that stores the start block of a voting cycle and it’s duration. +A struct that stores the start block of a voting cycle and it's duration. - `startingBlock`: The block number/timestamp that the voting cycle starts - `duration`: The duration of the specific voting cycle @@ -326,9 +345,9 @@ A struct that stores the start block of a voting cycle and it’s duration. ```solidity struct VotingCycleData { - uint256 startingBlock; - uint256 duration; - uint256 votingCycleDistributionLimit; + uint256 startingBlock; + uint256 duration; + uint256 votingCycleDistributionLimit; } ``` @@ -336,7 +355,8 @@ struct VotingCycleData { `ProposalType` -Defines the different types of proposals that can be submitted. Based on each type it will be determined which validation checks should be run when submitting and moving to vote a proposal. +Defines the different types of proposals that can be submitted. Based on each type it will be determined which validation +checks should be run when submitting and moving to vote a proposal. The proposal types that are supported are: @@ -347,13 +367,13 @@ The proposal types that are supported are: - Council Budget ```solidity - enum ProposalType { - ProtocolOrGovernorUpgrade, - MaintenanceUpgrade, - CouncilMemberElections, - GovernanceFund, - CouncilBudget - } +enum ProposalType { + ProtocolOrGovernorUpgrade, + MaintenanceUpgrade, + CouncilMemberElections, + GovernanceFund, + CouncilBudget +} ``` ### Events @@ -392,29 +412,36 @@ event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor ## EAS Integration -`ProposalValidator` integrates Ethereum Attestation Service (EAS) to handle proposer authorization for specific `ProposalType`s, removing the need for adding custom logic to the contract. +`ProposalValidator` integrates Ethereum Attestation Service (EAS) to handle proposer authorization for specific +`ProposalType`s, removing the need for adding custom logic to the contract. ### Why EAS? -- **Decentralized trust**: Instead of custom logic, Optimism uses attestations signed by the Foundation to authorize proposers. -- **Low integration overhead**: `EAS` and `SchemaRegistry` are predeploys on Optimism, requiring no additional deployments or infrastructure. +- **Decentralized trust**: Instead of custom logic, Optimism uses attestations signed by the Foundation to authorize + proposers. +- **Low integration overhead**: `EAS` and `SchemaRegistry` are predeploys on Optimism, requiring no additional deployments + or infrastructure. - **Schema validation**: Ensures attestations follow strict data formats (`address approvedDelegate, uint8 proposalType`). -- **Revocability and expiration**: EAS supports expiration and revocation semantics natively, allowing dynamic control over authorized proposers. +- **Revocability and expiration**: EAS supports expiration and revocation semantics natively, allowing dynamic control over + authorized proposers. ### Implementation Details - On setup, the contract registers a schema in the predeployed `SchemaRegistry`. - The `submitProposal` function validates attestations by: - - Ensuring the attestation UID matches the registered schema. - - Verifying the attester is the contract owner (Optimism Foundation). - - Decoding the attestation to check the proposer’s address and proposal type. -- Only proposals of type `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` require valid EAS attestations. + - Ensuring the attestation UID matches the registered schema. + - Verifying the attester is the contract owner (Optimism Foundation). + - Decoding the attestation to check the proposer's address and proposal type. +- Only proposals of type `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and +`CouncilMemberElections` require + valid EAS attestations. -**Technical implementation docs: [EAS Integration](https://www.notion.so/EAS-Integration-1de9a4c092c780478e19cc8175aa054e?pvs=21)** +**Technical implementation docs: [EAS Integration](https://www.notion.so/EAS-Integration-1de9a4c092c780478e19cc8175aa054e?pvs=21)** ## Proposal uniqueness -To prevent duplicate proposals, the contract enforces uniqueness by hashing the defining parameters of each proposal and checking against a registry of previously submitted proposals. +To prevent duplicate proposals, the contract enforces uniqueness by hashing the defining parameters of each proposal and +checking against a registry of previously submitted proposals. A proposal is uniquely identified by the tuple: @@ -435,24 +462,33 @@ This hash serves as a unique identifier for the proposal. The contract stores su mapping(bytes32 => ProposalSubmissionData) private _proposals; ``` -When a new proposal is submitted, the contract checks that `_proposals[proposalHash]` is empty (e.g., `proposer == address(0)`). If data exists at that key, the proposal is rejected as a duplicate. +When a new proposal is submitted, the contract checks that `_proposals[proposalHash]` is empty (e.g., `proposer == +address(0)`). If data exists at that key, the proposal is rejected as a duplicate. -This mechanism guarantees that proposals with the same intent and execution logic cannot be submitted multiple times, maintaining proposal integrity and preventing spam. +This mechanism guarantees that proposals with the same intent and execution logic cannot be submitted multiple times, +maintaining proposal integrity and preventing spam. ## Invariants -- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycleData,` `distributionThreshold`, and `proposalTypeApprovalThreshold` +- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycleData`, `distributionThreshold`, and + `proposalTypeApprovalThreshold` - It MUST only allow eligible addresses to approve a proposal -- It MUST only allow authorizeds addresses to submit proposals for types `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` -- It MUST NOT transfer any tokens or ETH for `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` proposal types +- It MUST only allow authorized addresses to submit proposals for types `ProtocolOrGovernorUpgrade`, + `MaintenanceUpgradeProposals`, and `CouncilMemberElections` +- It MUST NOT transfer any tokens or ETH for `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and + `CouncilMemberElections` proposal types - It MUST emit the following events: - - `ProposalSubmitted` - - `ProposalApproved` - - `ProposalMovedToVote` + - `ProposalSubmitted` + - `ProposalApproved` + - `ProposalMovedToVote` ## Security Considerations -- **Role-Based Restrictions:** The `owner` role should be securely managed, as it holds critical permissions such as setting voting power thresholds, configuring voting cycles, and distribution limits. Any compromise could significantly impact governance. +- **Role-Based Restrictions:** The `owner` role should be securely managed, as it holds critical permissions such as + setting voting power thresholds, configuring voting cycles, and distribution limits. Any compromise could significantly + impact governance. - **Attestation Validation:** Two key aspects need consideration: - - The Optimism Foundation must have a secure and thorough process for validating addresses before issuing attestations for specific proposal types - - The contract must properly verify attestation expiration and revocation status to prevent the use of outdated or invalid attestations \ No newline at end of file + - The Optimism Foundation must have a secure and thorough process for validating addresses before issuing attestations + for specific proposal types + - The contract must properly verify attestation expiration and revocation status to prevent the use of outdated or + invalid attestations From c2d7c2e5e1842406ed27ed7fcdda7f107aa5be00 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Mon, 19 May 2025 17:49:49 +0300 Subject: [PATCH 07/19] fix: remove type configurator as input (#33) * fix: linting * fix: remove type configurator as input --- specs/experimental/gov-proposal-validator.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index c56e14112..6baefef1e 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -59,7 +59,7 @@ checks and actions. - MUST only be called for `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, or `CouncilMemberElections` types - MUST be called by an approved address - MUST check if the proposal is a duplicate -- MUST provide a valid proposal type configurator +- MUST use the correct proposal type configurator from the `_proposalTypesData` - MUST provide a valid attestation UID - MUST NOT transfer any tokens or change any allowances - MUST emit `ProposalSubmitted` event @@ -70,7 +70,7 @@ For `GovernanceFund` and `CouncilBudget` types: - The user MUST use the `submitFundingProposal` that uses specific `calldata` pre-defined by the owner Note: `MaintenanceUpgradeProposals` type can move straight to voting if all submission checks pass, unlike the rest of the -proposal where they need to collect a number of approvals by top delegates in order to move to vote. This call should be +proposals where they need to collect a number of approvals by top delegates in order to move to vote. This call should be atomic. ```solidity @@ -80,7 +80,6 @@ function submitProposal( bytes[] memory _calldatas, string memory _description, ProposalType _proposalType, - uint8 _proposalTypeConfiguration, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); ``` @@ -92,7 +91,7 @@ Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP to - MUST only be called for `GovernanceFund` or `CouncilBudget` proposal type - CAN be called by anyone - MUST check if the proposal is a duplicate -- MUST provide a valid proposal type configurator +- MUST use the correct proposal type configurator from the `_proposalTypesData` - MUST use the `Predeploys.GOVERNANCE_TOKEN` and `TRANSFER_SIGNATURE` to create the `calldata` - MUST NOT request to transfer more than `distributionThreshold` tokens - MUST emit `ProposalSubmitted` event @@ -104,7 +103,6 @@ function submitFundingProposal( uint256 _amount, string memory _description, ProposalType _proposalType, - uint8 _proposalTypeConfiguration ) external returns (bytes32 proposalHash_); ``` @@ -305,7 +303,6 @@ A struct that holds all the data for a single proposal. Consists of: - `proposer`: The address that submitted the proposal - `proposalType`: The type of the proposal -- `proposalTypeConfigurator`: The voting type for the proposal - `inVoting`: Returns true if the proposal has already been submitted for voting - `delegateApprovals`: Mapping of addresses that approved the specific proposal - `approvalsCounter`: The number of approvals the specific proposal has received @@ -314,7 +311,6 @@ A struct that holds all the data for a single proposal. Consists of: struct ProposalSubmissionData { address proposer; ProposalType proposalType; - uint8 proposalTypeConfigurator; bool inVoting; mapping(address => bool) delegateApprovals; uint256 approvalsCounter; @@ -326,12 +322,13 @@ struct ProposalSubmissionData { A struct that holds data for each proposal type. - `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. -- `validProposalTypeConfigurators`: The accepted proposal type configurators that can be used for each proposal type. +- `proposalTypeConfigurator`: The proposal type configurator that can be used for each proposal type. This +is set by the owner on initiallize. ```solidity struct ProposalTypeData { uint256 requiredApprovals; - mapping(uint8 => bool) validProposalTypeConfigurators; + uint8 proposalTypeConfigurator; } ``` @@ -443,7 +440,7 @@ event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor To prevent duplicate proposals, the contract enforces uniqueness by hashing the defining parameters of each proposal and checking against a registry of previously submitted proposals. -A proposal is uniquely identified by the tuple: +A proposal is uniquely identified by a tuple: - `targets[]`: array of addresses the proposal will call - `values[]`: array of ETH values to send with each call From 1d82078c684a9fa3f8da70f8e1deedfd6d6a8f4d Mon Sep 17 00:00:00 2001 From: OneTony Date: Mon, 19 May 2025 17:56:25 +0300 Subject: [PATCH 08/19] fix: linting --- specs/experimental/gov-proposal-validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 6baefef1e..7caa3b499 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -26,7 +26,7 @@ This document specifies the `ProposalValidator` contract, designed to enable permissionless proposals in the Optimism governance system. The contract allows proposal submissions based on predefined rules and automated checks, removing the -need for manual gatekeeping. +need for manual gate keeping. ## Design @@ -323,7 +323,7 @@ A struct that holds data for each proposal type. - `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. - `proposalTypeConfigurator`: The proposal type configurator that can be used for each proposal type. This -is set by the owner on initiallize. +is set by the owner on initialize. ```solidity struct ProposalTypeData { From 3326453768b1b1296b857bceeb804b635ec0a68a Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Thu, 29 May 2025 20:15:36 +0300 Subject: [PATCH 09/19] fix: submit functions (#34) * fix: linting * fix: remove type configurator as input * fix: submit functions * fix: define approval timing * feat: add voting module info * fix: proposal uniquness * fix: move to vote * feat: add new event for voting module data * fix: proposals mapping description * fix: namings * fix: descriptions --- specs/experimental/gov-proposal-validator.md | 208 ++++++++++++++----- 1 file changed, 157 insertions(+), 51 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 7caa3b499..284f0c31a 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -26,15 +26,15 @@ This document specifies the `ProposalValidator` contract, designed to enable permissionless proposals in the Optimism governance system. The contract allows proposal submissions based on predefined rules and automated checks, removing the -need for manual gate keeping. +need for manual gatekeeping. ## Design -The `ProposalValidator` manages the proposal lifecycle through three main functions: +The `ProposalValidator` manages the proposal lifecycle through three main actions: -- `submitProposal`: Records new proposals -- `approveProposal`: Handles proposal approvals -- `moveToVote`: Transitions approved proposals to voting phase +- `Submit Proposal`: Records new proposals +- `Approve Proposal`: Handles proposal approvals +- `Move to Vote`: Transitions approved proposals to voting phase The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers for specific proposal types. For detailed flows of each proposal, see [design docs](https://github.com/ethereum-optimism/design-docs/pull/260). @@ -51,39 +51,114 @@ The contract has a single `owner` role (Optimism Foundation) with permissions to ### Public Functions -`submitProposal` +`submitProtocolOrGovernorUpgradeProposal` -Submits a proposal for approval and voting. Based on the `ProposalType` provided this will require different validation -checks and actions. +Submits a Protocol or Governor Upgrade proposal for approval and voting. -- MUST only be called for `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, or `CouncilMemberElections` types - MUST be called by an approved address - MUST check if the proposal is a duplicate -- MUST use the correct proposal type configurator from the `_proposalTypesData` +- MUST use the `Approval` Voting Module - MUST provide a valid attestation UID -- MUST NOT transfer any tokens or change any allowances -- MUST emit `ProposalSubmitted` event -- MUST store proposal data +- MUST NOT do any operations +- MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events +- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct +- MUST store the timestamp of submission +- MUST use "Threshold" criteria type + +```solidity +function submitProtocolOrGovernorUpgradeProposal( + uint128 _criteriaValue, + string[] memory _optionDescriptions, + string memory _description, + ProposalType _proposalType, + bytes32 _attestationUid +) external returns (bytes32 proposalHash_); +``` + +Approval Voting Module -For `GovernanceFund` and `CouncilBudget` types: +Protocol or Governor Upgrade proposals use the `Approval` voting module. +This requires the user who submits the proposal to provide some additional data related to the proposal. -- The user MUST use the `submitFundingProposal` that uses specific `calldata` pre-defined by the owner +For the `ProposalSettings` of the voting module, these are: +- `uint8 criteria`: The Propocol or Governor Upgrade proposal accepts only "Threshold" passing criteria type +for the approval voting module. This will be value will be fixed and defined in the contract. +- `uint128 criteriaValue`: Based on the passing criteria type this can either be a threshold percentage that +the proposal needs to reach to pass or the number of top choices that can pass the voting. -Note: `MaintenanceUpgradeProposals` type can move straight to voting if all submission checks pass, unlike the rest of the -proposals where they need to collect a number of approvals by top delegates in order to move to vote. This call should be +For the `ProposalOptions` of the voting module, these are: +- `string[] descriptions`: The strings of the different options that can be voted. + +`submitMaintenanceUpgradeProposal` + +Submits a Maintenance Upgrade proposal to move for voting. `MaintenanceUpgradeProposals` type can move +straight to voting if all submission checks pass, unlike the rest of the proposals where they +need to collect a number of approvals by top delegates in order to move to vote. This call should be atomic. +- MUST be called by an approved address +- MUST check if the proposal is a duplicate +- MUST use the `Optimistic` Voting Module +- MUST provide a valid attestation UID +- MUST NOT do any operations +- MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events +- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct + ```solidity -function submitProposal( - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas, +function submitMaintenanceUpgradeProposal( + uint248 _againstThreshold, + bool _isRelativeToVotableSupply, string memory _description, ProposalType _proposalType, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); ``` +Optimistic Voting Module + +Maintenance Upgrade proposals use the `Optimistic` voting module. +This requires the user who submits the proposal to provide some additional data related to the proposal. + +For the `ProposalSettings` of the voting module, these are: +- `uint248 againstThreshold`: The threshold of the against option. +- `bool isRelativeToVotableSupply`: True if the against threshold should be relative to the votable supply. + +`submitCouncilMemberElectionsProposal` + +Submits a Council Member Elections proposal for approval and voting. + +- MUST be called by an approved address +- MUST check if the proposal is a duplicate +- MUST use the `Approval` Voting Module +- MUST provide a valid attestation UID +- MUST NOT do any operations +- MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events +- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct +- MUST store the timestamp of submission +- MUST use "TopChoices" criteria type + +```solidity +function submitCouncilMemberElectionsProposal( + uint128 _criteriaValue, + string[] _optionDescriptions, + string memory _description, + ProposalType _proposalType, + bytes32 _attestationUid +) external returns (bytes32 proposalHash_); +``` + +Approval Voting Module + +Council Member Elections proposals use the `Approval` voting module. +This requires the user who submits the proposal to provide some additional data related to the proposal. + +For the `ProposalSettings` of the voting module, these are: +- `uint8 criteria`: The passing criteria type for this proposal type is always "TopChoices". +- `uint128 criteriaValue`: The number of top choices that can pass the voting. + +For the `ProposalOptions` of the voting module, these are: +- `string[] descriptions`: The strings of the different options that can be voted. + `submitFundingProposal` Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP tokens for approval and voting. @@ -91,27 +166,51 @@ Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP to - MUST only be called for `GovernanceFund` or `CouncilBudget` proposal type - CAN be called by anyone - MUST check if the proposal is a duplicate -- MUST use the correct proposal type configurator from the `_proposalTypesData` -- MUST use the `Predeploys.GOVERNANCE_TOKEN` and `TRANSFER_SIGNATURE` to create the `calldata` +- MUST use the `Approval` Voting Module +- MUST use the `Predeploys.GOVERNANCE_TOKEN` and `IERC20.transfer` signature to create the `calldata` - MUST NOT request to transfer more than `distributionThreshold` tokens - MUST emit `ProposalSubmitted` event -- MUST store proposal data +- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct +- MUST store the timestamp of submission +- MUST use "Threshold" criteria type ```solidity function submitFundingProposal( - address _to, - uint256 _amount, + uint128 _criteriaValue, + string[] _optionDescriptions, + address[] _recipients, + uint256[] _amounts, string memory _description, ProposalType _proposalType, ) external returns (bytes32 proposalHash_); ``` +Approval Voting Module + +Funding proposals use the `Approval` voting module but unlike the Protocol or Governor upgrade proposals, +funding proposals need to execute token transfers. +This requires the user who submits the proposal to provide some additional data related to the proposal. + +For the `ProposalSettings` of the voting module, these are: +- `uint8 criteria`: The funding proposals accept only "Threshold" passing criteria type for the approval +voting module. +for the approval voting module. This will be value will be fixed and defined in the contract. +- `uint128 criteriaValue`: Based on the passing criteria type this can either be a threshold percentage that +the proposal needs to reach to pass or the number of top choices that can pass the voting. + +For the `ProposalOptions` of the voting module, these are: +- `string[] descriptions`: The strings of the different options that can be voted. +- `address[] recipients`: An address for each option to transfer funds to in case the option passes the voting. +- `uint256[] amounts`: The amount to transfer for each option. + `approveProposal` Approves a proposal before being moved for voting, used by the top delegates. - MUST check if proposal hash corresponds to a valid proposal -- MUST check if caller is has enough voting power to call the function and approve a proposal +- MUST check if caller has enough voting power to call the function and approve a proposal + The voting power of the delegate is checked against the end of the last voting cycle from when + the proposal was submitted. - MUST check if caller has already approved the same proposal - MUST store the approval vote - MUST emit `ProposalApproved` when successfully called @@ -120,6 +219,10 @@ Approves a proposal before being moved for voting, used by the top delegates. function approveProposal(bytes32 _proposalHash) external ``` +Approving a funding proposal type requires extra attention to the budget amount and options, of the +approval voting module, that were provided on the submission of the proposal. This should be handled +by the Agora's UI. + `moveToVote` Checks if the provided proposal is ready to move for voting. Based on the Proposal Type different checks are being @@ -128,7 +231,7 @@ function can be called by anyone. For `ProtocolOrGovernorUpgrade`: -- MUST check if provided data produces a valid `proposalHash` +- MUST check if the `proposalHash` exists and is valid - Proposal MUST have gathered X amount of approvals by top delegates - MUST check if proposal has already moved for voting - MUST emit `ProposalMovedToVote` event @@ -140,7 +243,7 @@ For `MaintenanceUpgradeProposals`: For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget`: -- MUST check if provided data produce the same `proposalHash` +- MUST check if the `proposalHash` exists and is valid - Proposal MUST have gathered X amount of approvals by top delegates - Proposal MUST be moved to vote during a valid voting cycle - MUST check if proposal has already moved for voting @@ -149,14 +252,7 @@ For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget`: - MUST emit `ProposalMovedToVote` event ```solidity -function moveToVote( - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldata, - string memory description -) - external - returns (uint256 governorProposalId_) +function moveToVote(bytes memory proposalHash_) external returns (uint256 governorProposalId_) ``` `canSignOff` @@ -280,8 +376,8 @@ mapping(uint256 => VotingCycleData) public votingCycles; `_proposals` -A mapping that stores each submitted proposals' data based on its `proposalHash`. The proposal hash is produced by hashing -the ABI encoded `targets` array, the `values` array, the `calldatas` array and the `description`. +A mapping that stores each submitted proposal's data based on its `proposalHash`. The proposal hash is produced by hashing +the ABI encoded values of specific proposal params, see [Proposal uniqueness](#proposal-uniqueness). ```solidity mapping(bytes32 => ProposalSubmissionData) private _proposals; @@ -306,6 +402,8 @@ A struct that holds all the data for a single proposal. Consists of: - `inVoting`: Returns true if the proposal has already been submitted for voting - `delegateApprovals`: Mapping of addresses that approved the specific proposal - `approvalsCounter`: The number of approvals the specific proposal has received +- `votingModuleData`: Encoded data that are required for the voting modules +- `timestamp`: The timestamp of the proposal submission ```solidity struct ProposalSubmissionData { @@ -314,6 +412,8 @@ struct ProposalSubmissionData { bool inVoting; mapping(address => bool) delegateApprovals; uint256 approvalsCounter; + bytes votingModuleData; + uint256 timestamp; } ``` @@ -323,7 +423,7 @@ A struct that holds data for each proposal type. - `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. - `proposalTypeConfigurator`: The proposal type configurator that can be used for each proposal type. This -is set by the owner on initialize. +is set by the owner on initiallize. ```solidity struct ProposalTypeData { @@ -377,15 +477,12 @@ enum ProposalType { `ProposalSubmitted` -MUST be triggered when `submitProposal` is successfully called. +MUST be triggered when a proposal submission is successfully called. ```solidity event ProposalSubmitted( uint256 indexed proposalHash, address indexed proposer, - address[] targets, - uint256[] values, - bytes[] calldatas, string description, ProposalType proposalType ); @@ -407,6 +504,14 @@ MUST be triggered when `moveToVote` is successfully called. event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor); ``` +`ProposalVotingModuleData` + +MUST be triggered with `ProposalSubmitted` event. + +```solidity +event ProposalVotingModuleData(uint256 indexed proposalHash, bytes encodedVotingModuleData); +``` + ## EAS Integration `ProposalValidator` integrates Ethereum Attestation Service (EAS) to handle proposer authorization for specific @@ -425,7 +530,7 @@ event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor ### Implementation Details - On setup, the contract registers a schema in the predeployed `SchemaRegistry`. -- The `submitProposal` function validates attestations by: +- The submit proposal functions validates attestations by: - Ensuring the attestation UID matches the registered schema. - Verifying the attester is the contract owner (Optimism Foundation). - Decoding the attestation to check the proposer's address and proposal type. @@ -438,19 +543,20 @@ event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor ## Proposal uniqueness To prevent duplicate proposals, the contract enforces uniqueness by hashing the defining parameters of each proposal and -checking against a registry of previously submitted proposals. +checking against a registry of previously submitted proposals, creating a proposalId. The proposal ID should be the same +as the one created on `Governor.hashProposalWithModule` and used by the `Voting Modules`. A proposal is uniquely identified by a tuple: -- `targets[]`: array of addresses the proposal will call -- `values[]`: array of ETH values to send with each call -- `calldatas[]`: array of calldata payloads for each call -- `description`: a string describing the proposal +- `proposalValidator`: The address of the proposal validator +- `module`: The address of the voting module the proposal uses +- `proposalVotingModuleData`: The encoded voting module data +- `descriptionHash`: The hash of the description These elements are ABI-encoded and hashed: ```solidity -keccak256(abi.encode(targets, values, calldatas, description)); +keccak256(abi.encode(proposalValidator, module, proposalVotingModuleData, descriptionHash)); ``` This hash serves as a unique identifier for the proposal. The contract stores submitted proposals in: From e87c473eb43943020e7eb5df8484254d36e95145 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Fri, 30 May 2025 16:46:46 +0300 Subject: [PATCH 10/19] feat: improvements (#36) * fix: improvements * feat: improve move to vote * fix: naming * fix: remove storage of encoded data --- specs/experimental/gov-proposal-validator.md | 134 ++++++++----------- 1 file changed, 59 insertions(+), 75 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 284f0c31a..1cb3b9491 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -26,7 +26,7 @@ This document specifies the `ProposalValidator` contract, designed to enable permissionless proposals in the Optimism governance system. The contract allows proposal submissions based on predefined rules and automated checks, removing the -need for manual gatekeeping. +need for manual gate-keeping. ## Design @@ -44,6 +44,7 @@ types. For detailed flows of each proposal, see [design docs](https://github.com The contract has a single `owner` role (Optimism Foundation) with permissions to: - Set minimum voting power threshold for delegate approvals +- Set the minimum approvals for each supported proposal type - Configure voting cycle parameters - Set maximum token distribution limits for proposals @@ -58,19 +59,17 @@ Submits a Protocol or Governor Upgrade proposal for approval and voting. - MUST be called by an approved address - MUST check if the proposal is a duplicate - MUST use the `Approval` Voting Module +- MUST use "Threshold" criteria type for the Voting Module - MUST provide a valid attestation UID -- MUST NOT do any operations +- MUST NOT execute any operations - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events - MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct -- MUST store the timestamp of submission -- MUST use "Threshold" criteria type ```solidity function submitProtocolOrGovernorUpgradeProposal( uint128 _criteriaValue, string[] memory _optionDescriptions, - string memory _description, - ProposalType _proposalType, + string memory _proposalDescription, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); ``` @@ -81,13 +80,11 @@ Protocol or Governor Upgrade proposals use the `Approval` voting module. This requires the user who submits the proposal to provide some additional data related to the proposal. For the `ProposalSettings` of the voting module, these are: -- `uint8 criteria`: The Propocol or Governor Upgrade proposal accepts only "Threshold" passing criteria type -for the approval voting module. This will be value will be fixed and defined in the contract. -- `uint128 criteriaValue`: Based on the passing criteria type this can either be a threshold percentage that -the proposal needs to reach to pass or the number of top choices that can pass the voting. +- `uint128 criteriaValue`: Since the passing criteria type is always "Threshold" for this proposal type +this will be the amount of votes that the proposal need to gather to pass. For the `ProposalOptions` of the voting module, these are: -- `string[] descriptions`: The strings of the different options that can be voted. +- `string[] optionDescriptions`: The strings of the different options that can be voted. `submitMaintenanceUpgradeProposal` @@ -100,7 +97,7 @@ atomic. - MUST check if the proposal is a duplicate - MUST use the `Optimistic` Voting Module - MUST provide a valid attestation UID -- MUST NOT do any operations +- MUST NOT execute any operations - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events - MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct @@ -108,8 +105,7 @@ atomic. function submitMaintenanceUpgradeProposal( uint248 _againstThreshold, bool _isRelativeToVotableSupply, - string memory _description, - ProposalType _proposalType, + string memory _proposalDescription, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); ``` @@ -130,19 +126,17 @@ Submits a Council Member Elections proposal for approval and voting. - MUST be called by an approved address - MUST check if the proposal is a duplicate - MUST use the `Approval` Voting Module +- MUST use "TopChoices" criteria type for the Voting Module - MUST provide a valid attestation UID -- MUST NOT do any operations +- MUST NOT execute any operations - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events - MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct -- MUST store the timestamp of submission -- MUST use "TopChoices" criteria type ```solidity function submitCouncilMemberElectionsProposal( uint128 _criteriaValue, string[] _optionDescriptions, - string memory _description, - ProposalType _proposalType, + string memory _proposalDescription, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); ``` @@ -153,11 +147,11 @@ Council Member Elections proposals use the `Approval` voting module. This requires the user who submits the proposal to provide some additional data related to the proposal. For the `ProposalSettings` of the voting module, these are: -- `uint8 criteria`: The passing criteria type for this proposal type is always "TopChoices". -- `uint128 criteriaValue`: The number of top choices that can pass the voting. +- `uint128 criteriaValue`: Since the passing criteria type is "TopChoices" this number represents the amount +of top choices that can pass the voting. For the `ProposalOptions` of the voting module, these are: -- `string[] descriptions`: The strings of the different options that can be voted. +- `string[] optionDescriptions`: The strings of the different options that can be voted. `submitFundingProposal` @@ -167,19 +161,19 @@ Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP to - CAN be called by anyone - MUST check if the proposal is a duplicate - MUST use the `Approval` Voting Module +- MUST use "Threshold" criteria type for the Voting Module - MUST use the `Predeploys.GOVERNANCE_TOKEN` and `IERC20.transfer` signature to create the `calldata` -- MUST NOT request to transfer more than `distributionThreshold` tokens +for each option +- MUST NOT request to transfer more than `proposalDistributionThreshold` tokens - MUST emit `ProposalSubmitted` event - MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct -- MUST store the timestamp of submission -- MUST use "Threshold" criteria type ```solidity function submitFundingProposal( uint128 _criteriaValue, - string[] _optionDescriptions, - address[] _recipients, - uint256[] _amounts, + string[] _optionsDescriptions, + address[] _optionsRecipients, + uint256[] _optionsAmounts, string memory _description, ProposalType _proposalType, ) external returns (bytes32 proposalHash_); @@ -192,16 +186,13 @@ funding proposals need to execute token transfers. This requires the user who submits the proposal to provide some additional data related to the proposal. For the `ProposalSettings` of the voting module, these are: -- `uint8 criteria`: The funding proposals accept only "Threshold" passing criteria type for the approval -voting module. -for the approval voting module. This will be value will be fixed and defined in the contract. -- `uint128 criteriaValue`: Based on the passing criteria type this can either be a threshold percentage that -the proposal needs to reach to pass or the number of top choices that can pass the voting. +- `uint128 criteriaValue`: Since the passing criteria type is always "Threshold" for this proposal type +this will be the amount of votes that the proposal need to gather to pass. For the `ProposalOptions` of the voting module, these are: -- `string[] descriptions`: The strings of the different options that can be voted. -- `address[] recipients`: An address for each option to transfer funds to in case the option passes the voting. -- `uint256[] amounts`: The amount to transfer for each option. +- `string[] optionsDescriptions`: The strings of the different options that can be voted. +- `address[] optionsRecipients`: An address for each option to transfer funds to in case the option passes the voting. +- `uint256[] optionsAmounts`: The amount to transfer for each option in case the option passes the voting. `approveProposal` @@ -226,36 +217,39 @@ by the Agora's UI. `moveToVote` Checks if the provided proposal is ready to move for voting. Based on the Proposal Type different checks are being -validated. If all checks pass then `OptimismGovernor.propose` is being called to forward the proposal for voting. This -function can be called by anyone. - -For `ProtocolOrGovernorUpgrade`: +validated. If all checks pass then `OptimismGovernor.proposeWithModule` is being called to forward the proposal for voting. -- MUST check if the `proposalHash` exists and is valid -- Proposal MUST have gathered X amount of approvals by top delegates +- MUST create the `proposalHash` and check if it exists and is valid +- Proposal MUST have gathered equal or more than the `requiredApprovals` defined for that type - MUST check if proposal has already moved for voting - MUST emit `ProposalMovedToVote` event -For `MaintenanceUpgradeProposals`: +For `ProtocolOrGovernorUpgrade` type: + +- MUST also check that is called by the proposer that submitted the proposal +- `_optionsRecipients` and `_optionsAmounts` MUST be empty + +For `MaintenanceUpgradeProposals` type: - This type does not require any checks and is being forwarded to the Governor contracts, this should happen atomically. -- MUST emit `ProposalMovedToVote` event -For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget`: +For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget` types: -- MUST check if the `proposalHash` exists and is valid -- Proposal MUST have gathered X amount of approvals by top delegates - Proposal MUST be moved to vote during a valid voting cycle -- MUST check if proposal has already moved for voting - MUST check if the total amount of tokens that can possible be distributed during this voting cycle does not go over the `VotingCycleData.votingCycleDistributionLimit` -- MUST emit `ProposalMovedToVote` event ```solidity -function moveToVote(bytes memory proposalHash_) external returns (uint256 governorProposalId_) +function moveToVote( + uint128 _criteriaValue, + address[] memory _optionsRecipients, + uint256[] memory _optionsAmounts, + string[] memory _optionDescriptions, + string memory _proposalDescription +) external returns (uint256 proposalHash_) ``` -`canSignOff` +`canApproveProposal` Returns true if a delegate address has enough voting power to approve a proposal. @@ -263,7 +257,7 @@ Returns true if a delegate address has enough voting power to approve a proposal - MUST return TRUE if the delegates' voting power is above the `minimumVotingPower` ```solidity -function canSignOff(address _delegate) public view returns (bool canSignOff_) +function canApproveProposal(address _delegate) public view returns (bool canSignOff_) ``` `setMinimumVotingPower` @@ -280,7 +274,7 @@ function setMinimumVotingPower(uint256 _minimumVotingPower) external `setVotingCycleData` -Sets the start and the duration of a voting cycle. +Sets the voting cycle data. - MUST only be called by the owner of the contract - MUST NOT change an existing voting cycle @@ -295,16 +289,16 @@ function setVotingCycleData( ) external ``` -`setDistributionThreshold` +`setProposalDistributionThreshold` -Sets the maximum distribution threshold a proposal can request. +Sets the maximum distribution amount a proposal can request. - MUST only be called by the owner of the contract -- MUST change the previous threshold to the new one -- MUST emit `DistributionThresholdSet` event +- MUST change the previous amount to the new one +- MUST emit `ProposalDistributionThresholdSet` event ```solidity -function setDistributionThreshold(uint256 _threshold) external +function setProposalDistributionThreshold(uint256 _threshold) external ``` `setProposalTypeApprovalThreshold` @@ -334,14 +328,6 @@ The EAS' schema UID that is used to verify attestation for approved addresses th bytes32 public immutable ATTESTATION_SCHEMA_UID; ``` -`TRANSFER_SIGNATURE` - -The 4bytes signature of ERC20.transfer, will be used for creating the calldata for funding proposals. - -```solidity -bytes4 public constant TRANSFER_SIGNATURE = 0xa9059cbb; -``` - `GOVERNOR` The address of the Optimism Governor contract. @@ -358,12 +344,12 @@ The minimum voting power a delegate must have in order to be eligible for approv uint256 public minimumVotingPower; ``` -`distributionThreshold` +`proposalDistributionThreshold` The maximum amount of tokens a proposal can request. ```solidity -uint256 public distributionThreshold; +uint256 public proposalDistributionThreshold; ``` `votingCycles` @@ -402,7 +388,6 @@ A struct that holds all the data for a single proposal. Consists of: - `inVoting`: Returns true if the proposal has already been submitted for voting - `delegateApprovals`: Mapping of addresses that approved the specific proposal - `approvalsCounter`: The number of approvals the specific proposal has received -- `votingModuleData`: Encoded data that are required for the voting modules - `timestamp`: The timestamp of the proposal submission ```solidity @@ -412,7 +397,6 @@ struct ProposalSubmissionData { bool inVoting; mapping(address => bool) delegateApprovals; uint256 approvalsCounter; - bytes votingModuleData; uint256 timestamp; } ``` @@ -422,19 +406,19 @@ struct ProposalSubmissionData { A struct that holds data for each proposal type. - `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. -- `proposalTypeConfigurator`: The proposal type configurator that can be used for each proposal type. This -is set by the owner on initiallize. +- `proposalVotingModule`: The voting module (proposal type configurator) that can be used for each proposal type. This +is set by the owner on initialize. ```solidity struct ProposalTypeData { uint256 requiredApprovals; - uint8 proposalTypeConfigurator; + uint8 proposalVotingModule; } ``` `VotingCycleData` -A struct that stores the start block of a voting cycle and it's duration. +A struct that stores data related to the voting cycle. - `startingBlock`: The block number/timestamp that the voting cycle starts - `duration`: The duration of the specific voting cycle @@ -573,7 +557,7 @@ maintaining proposal integrity and preventing spam. ## Invariants -- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycleData`, `distributionThreshold`, and +- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycleData`, `proposalDistributionThreshold`, and `proposalTypeApprovalThreshold` - It MUST only allow eligible addresses to approve a proposal - It MUST only allow authorized addresses to submit proposals for types `ProtocolOrGovernorUpgrade`, From 13c6f0441520d21650f8304138e9377214722313 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Thu, 5 Jun 2025 21:35:31 +0300 Subject: [PATCH 11/19] fix: proposal validator improvements (#37) * fix: submit protocol or governor upgrade function name * fix: remove options from upgrade proposals * fix: bold for modules info title * fix: specify proposer approved address must be attested by the owner * fix: optimistic voting module description * fix: move to vote cases description * fix: wording on approval module criteria value --- specs/experimental/gov-proposal-validator.md | 51 +++++++++++--------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 1cb3b9491..bc12ce086 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -47,16 +47,17 @@ The contract has a single `owner` role (Optimism Foundation) with permissions to - Set the minimum approvals for each supported proposal type - Configure voting cycle parameters - Set maximum token distribution limits for proposals +- Checks the address of the attester for the submit proposal attestations with the address of the owner ## Interface ### Public Functions -`submitProtocolOrGovernorUpgradeProposal` +`submitUpgradeProposal` Submits a Protocol or Governor Upgrade proposal for approval and voting. -- MUST be called by an approved address +- MUST be called by an `owner` approved address - MUST check if the proposal is a duplicate - MUST use the `Approval` Voting Module - MUST use "Threshold" criteria type for the Voting Module @@ -66,25 +67,25 @@ Submits a Protocol or Governor Upgrade proposal for approval and voting. - MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct ```solidity -function submitProtocolOrGovernorUpgradeProposal( +function submitUpgradeProposal( uint128 _criteriaValue, - string[] memory _optionDescriptions, string memory _proposalDescription, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); ``` -Approval Voting Module +**Approval Voting Module** Protocol or Governor Upgrade proposals use the `Approval` voting module. This requires the user who submits the proposal to provide some additional data related to the proposal. For the `ProposalSettings` of the voting module, these are: -- `uint128 criteriaValue`: Since the passing criteria type is always "Threshold" for this proposal type -this will be the amount of votes that the proposal need to gather to pass. +- `uint128 criteriaValue`: Since the passing criteria type is always "Threshold", for this proposal type, +this value will be the percentage that will be used to calculate the fraction of the votable supply that +the proposal will need in votes in order to pass. -For the `ProposalOptions` of the voting module, these are: -- `string[] optionDescriptions`: The strings of the different options that can be voted. +For the `ProposalOptions` of the voting module, the specific proposal type only accepts "Yes"/"No" options +which are defined in the contract. `submitMaintenanceUpgradeProposal` @@ -93,7 +94,7 @@ straight to voting if all submission checks pass, unlike the rest of the proposa need to collect a number of approvals by top delegates in order to move to vote. This call should be atomic. -- MUST be called by an approved address +- MUST be called by an `owner` approved address - MUST check if the proposal is a duplicate - MUST use the `Optimistic` Voting Module - MUST provide a valid attestation UID @@ -103,27 +104,26 @@ atomic. ```solidity function submitMaintenanceUpgradeProposal( - uint248 _againstThreshold, - bool _isRelativeToVotableSupply, string memory _proposalDescription, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); ``` -Optimistic Voting Module +**Optimistic Voting Module** Maintenance Upgrade proposals use the `Optimistic` voting module. -This requires the user who submits the proposal to provide some additional data related to the proposal. For the `ProposalSettings` of the voting module, these are: -- `uint248 againstThreshold`: The threshold of the against option. -- `bool isRelativeToVotableSupply`: True if the against threshold should be relative to the votable supply. +- `uint248 againstThreshold`: Should always be `12%`. +- `bool isRelativeToVotableSupply`: Should always be `true`. + +Since these values are static they are defined in the contract and does not need a user to provide them. `submitCouncilMemberElectionsProposal` Submits a Council Member Elections proposal for approval and voting. -- MUST be called by an approved address +- MUST be called by an `owner` approved address - MUST check if the proposal is a duplicate - MUST use the `Approval` Voting Module - MUST use "TopChoices" criteria type for the Voting Module @@ -141,7 +141,7 @@ function submitCouncilMemberElectionsProposal( ) external returns (bytes32 proposalHash_); ``` -Approval Voting Module +**Approval Voting Module** Council Member Elections proposals use the `Approval` voting module. This requires the user who submits the proposal to provide some additional data related to the proposal. @@ -179,15 +179,16 @@ function submitFundingProposal( ) external returns (bytes32 proposalHash_); ``` -Approval Voting Module +**Approval Voting Module** Funding proposals use the `Approval` voting module but unlike the Protocol or Governor upgrade proposals, funding proposals need to execute token transfers. This requires the user who submits the proposal to provide some additional data related to the proposal. For the `ProposalSettings` of the voting module, these are: -- `uint128 criteriaValue`: Since the passing criteria type is always "Threshold" for this proposal type -this will be the amount of votes that the proposal need to gather to pass. +- `uint128 criteriaValue`: Since the passing criteria type is always "Threshold", for this proposal type, +this value will be the percentage that will be used to calculate the fraction of the votable supply that +the proposal will need in votes in order to pass. For the `ProposalOptions` of the voting module, these are: - `string[] optionsDescriptions`: The strings of the different options that can be voted. @@ -233,7 +234,13 @@ For `MaintenanceUpgradeProposals` type: - This type does not require any checks and is being forwarded to the Governor contracts, this should happen atomically. -For `CouncilMemberElections`, `GovernanceFund` and `CouncilBudget` types: +For `CouncilMemberElections` type: + +- MUST also check that is called by the proposer that submitted the proposal +- `_optionsRecipients` and `_optionsAmounts` MUST be empty +- Proposal MUST be moved to vote during a valid voting cycle + +For `GovernanceFund` and `CouncilBudget` types: - Proposal MUST be moved to vote during a valid voting cycle - MUST check if the total amount of tokens that can possible be distributed during this voting cycle does not go over the From 63e75da487ca3ae7b2e5230e852ebf13deba0ea8 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Tue, 10 Jun 2025 17:49:09 +0300 Subject: [PATCH 12/19] fix: submit upgrade function (#38) --- specs/experimental/gov-proposal-validator.md | 49 ++++---------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index bc12ce086..76624f7ff 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -55,48 +55,16 @@ The contract has a single `owner` role (Optimism Foundation) with permissions to `submitUpgradeProposal` -Submits a Protocol or Governor Upgrade proposal for approval and voting. +Submits a Protocol/Governor Upgrade or a Maintenance Upgrade proposal to move for voting. -- MUST be called by an `owner` approved address -- MUST check if the proposal is a duplicate -- MUST use the `Approval` Voting Module -- MUST use "Threshold" criteria type for the Voting Module -- MUST provide a valid attestation UID -- MUST NOT execute any operations -- MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events -- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct - -```solidity -function submitUpgradeProposal( - uint128 _criteriaValue, - string memory _proposalDescription, - bytes32 _attestationUid -) external returns (bytes32 proposalHash_); -``` - -**Approval Voting Module** - -Protocol or Governor Upgrade proposals use the `Approval` voting module. -This requires the user who submits the proposal to provide some additional data related to the proposal. - -For the `ProposalSettings` of the voting module, these are: -- `uint128 criteriaValue`: Since the passing criteria type is always "Threshold", for this proposal type, -this value will be the percentage that will be used to calculate the fraction of the votable supply that -the proposal will need in votes in order to pass. - -For the `ProposalOptions` of the voting module, the specific proposal type only accepts "Yes"/"No" options -which are defined in the contract. - -`submitMaintenanceUpgradeProposal` - -Submits a Maintenance Upgrade proposal to move for voting. `MaintenanceUpgradeProposals` type can move -straight to voting if all submission checks pass, unlike the rest of the proposals where they -need to collect a number of approvals by top delegates in order to move to vote. This call should be -atomic. +`MaintenanceUpgradeProposals` type can move straight to voting if all submission checks pass, unlike +the rest of the proposals where they need to collect a number of approvals by top delegates in order +to move to vote. This call should be atomic. - MUST be called by an `owner` approved address - MUST check if the proposal is a duplicate - MUST use the `Optimistic` Voting Module +- MUST provide a valid againstThreshold - MUST provide a valid attestation UID - MUST NOT execute any operations - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events @@ -104,6 +72,7 @@ atomic. ```solidity function submitMaintenanceUpgradeProposal( + uint248 _againstThreshold, string memory _proposalDescription, bytes32 _attestationUid ) external returns (bytes32 proposalHash_); @@ -114,11 +83,11 @@ function submitMaintenanceUpgradeProposal( Maintenance Upgrade proposals use the `Optimistic` voting module. For the `ProposalSettings` of the voting module, these are: -- `uint248 againstThreshold`: Should always be `12%`. +- `uint248 againstThreshold`: Should be provided by the proposer. This value will be the percentage +that will be used to calculate the fraction of the votable supply that the proposal will need in votes in order +to pass. - `bool isRelativeToVotableSupply`: Should always be `true`. -Since these values are static they are defined in the contract and does not need a user to provide them. - `submitCouncilMemberElectionsProposal` Submits a Council Member Elections proposal for approval and voting. From 5e8ee3a2a4a63732a97cbe0ea9b53351d808350c Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Mon, 16 Jun 2025 11:24:29 +0300 Subject: [PATCH 13/19] fix: top100 delegates calculation to use EAS (#39) * fix: top100 delegate calculation to use EAS * fix: function seperation * fix: function seperation * fix: headers --- specs/experimental/gov-proposal-validator.md | 92 +++++++++++--------- 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 76624f7ff..e7c2f6cfe 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -16,6 +16,8 @@ - [EAS Integration](#eas-integration) - [Why EAS?](#why-eas) - [Implementation Details](#implementation-details) +- [Submit Proposal](#submit-proposal) +- [Approve Proposal](#approve-proposal) - [Proposal uniqueness](#proposal-uniqueness) - [Invariants](#invariants) - [Security Considerations](#security-considerations) @@ -43,7 +45,6 @@ types. For detailed flows of each proposal, see [design docs](https://github.com The contract has a single `owner` role (Optimism Foundation) with permissions to: -- Set minimum voting power threshold for delegate approvals - Set the minimum approvals for each supported proposal type - Configure voting cycle parameters - Set maximum token distribution limits for proposals @@ -87,6 +88,7 @@ For the `ProposalSettings` of the voting module, these are: that will be used to calculate the fraction of the votable supply that the proposal will need in votes in order to pass. - `bool isRelativeToVotableSupply`: Should always be `true`. +--- `submitCouncilMemberElectionsProposal` @@ -121,6 +123,7 @@ of top choices that can pass the voting. For the `ProposalOptions` of the voting module, these are: - `string[] optionDescriptions`: The strings of the different options that can be voted. +--- `submitFundingProposal` @@ -163,6 +166,7 @@ For the `ProposalOptions` of the voting module, these are: - `string[] optionsDescriptions`: The strings of the different options that can be voted. - `address[] optionsRecipients`: An address for each option to transfer funds to in case the option passes the voting. - `uint256[] optionsAmounts`: The amount to transfer for each option in case the option passes the voting. +--- `approveProposal` @@ -172,17 +176,21 @@ Approves a proposal before being moved for voting, used by the top delegates. - MUST check if caller has enough voting power to call the function and approve a proposal The voting power of the delegate is checked against the end of the last voting cycle from when the proposal was submitted. +- MUST provide a valid attestation UID +- MUST check if the attestation has been revoked +- The attestation MUST reffer to non partial delegation - MUST check if caller has already approved the same proposal - MUST store the approval vote - MUST emit `ProposalApproved` when successfully called ```solidity -function approveProposal(bytes32 _proposalHash) external +function approveProposal(bytes32 _proposalHash, bytes32 _attestationUid) external ``` Approving a funding proposal type requires extra attention to the budget amount and options, of the approval voting module, that were provided on the submission of the proposal. This should be handled by the Agora's UI. +--- `moveToVote` @@ -224,29 +232,19 @@ function moveToVote( string memory _proposalDescription ) external returns (uint256 proposalHash_) ``` +--- `canApproveProposal` -Returns true if a delegate address has enough voting power to approve a proposal. +Returns true if a delegate is part of the top100 delegates based on the dynamic attestation service. - Can be called by anyone -- MUST return TRUE if the delegates' voting power is above the `minimumVotingPower` +- MUST return TRUE if the delegate is part of the top100 and can approve a proposal ```solidity -function canApproveProposal(address _delegate) public view returns (bool canSignOff_) -``` - -`setMinimumVotingPower` - -Sets the minimum voting power a delegate must have in order to be eligible to approve a proposal. - -- MUST only be called by the owner of the contract -- MUST change the existing minimum voting power to the new -- MUST emit `MinimumVotingPowerSet` event - -```solidity -function setMinimumVotingPower(uint256 _minimumVotingPower) external +function canApproveProposal(address _delegate, bytes32 _attestationUid) public view returns (bool canApprove_) ``` +--- `setVotingCycleData` @@ -264,6 +262,7 @@ function setVotingCycleData( uint256 _distributionLimit ) external ``` +--- `setProposalDistributionThreshold` @@ -276,6 +275,7 @@ Sets the maximum distribution amount a proposal can request. ```solidity function setProposalDistributionThreshold(uint256 _threshold) external ``` +--- `setProposalTypeApprovalThreshold` @@ -294,30 +294,31 @@ function setProposalTypeApprovalThreshold( ### Properties -`ATTESTATION_SCHEMA_UID` +`ATTESTATION_SCHEMA_UID_APPROVED_PROPOSERS` The EAS' schema UID that is used to verify attestation for approved addresses that can submit proposals for specific `ProposalTypes`. ```solidity /// Schema { approvedProposer: address, proposalType: uint8 } -bytes32 public immutable ATTESTATION_SCHEMA_UID; +bytes32 public immutable ATTESTATION_SCHEMA_UID_APPROVED_PROPOSERS; ``` -`GOVERNOR` +`ATTESTATION_SCHEMA_UID_TOP100_DELEGATES` -The address of the Optimism Governor contract. +The EAS' schema UID that is used to verify attestation for a top100 delegate that can approve a proposal submission. ```solidity -IOptimismGovernor public immutable GOVERNOR; +/// Schema { ranl: string, includePartialDelegation: bool, date: string } +bytes32 public immutable ATTESTATION_SCHEMA_UID_TOP100_DELEGATES; ``` -`minimumVotingPower` +`GOVERNOR` -The minimum voting power a delegate must have in order to be eligible for approving a proposal. +The address of the Optimism Governor contract. ```solidity -uint256 public minimumVotingPower; +IOptimismGovernor public immutable GOVERNOR; ``` `proposalDistributionThreshold` @@ -475,28 +476,38 @@ event ProposalVotingModuleData(uint256 indexed proposalHash, bytes encodedVoting ## EAS Integration `ProposalValidator` integrates Ethereum Attestation Service (EAS) to handle proposer authorization for specific -`ProposalType`s, removing the need for adding custom logic to the contract. +`ProposalType`s, and authentication of the top100 delegates for approving a proposal, removing the need for adding +custom logic to the contract. -### Why EAS? +## Why EAS? - **Decentralized trust**: Instead of custom logic, Optimism uses attestations signed by the Foundation to authorize proposers. - **Low integration overhead**: `EAS` and `SchemaRegistry` are predeploys on Optimism, requiring no additional deployments or infrastructure. -- **Schema validation**: Ensures attestations follow strict data formats (`address approvedDelegate, uint8 proposalType`). +- **Schema validation**: Ensures attestations follow strict data formats (e.g. `address approvedDelegate, uint8 proposalType`). - **Revocability and expiration**: EAS supports expiration and revocation semantics natively, allowing dynamic control over authorized proposers. -### Implementation Details +## Implementation Details + +### Submit Proposal + +For the submit proposals we will need to register a new schema as described at `ATTESTATION_SCHEMA_UID_APPROVED_PROPOSERS`. +The submit proposal functions validates attestations by: +- Ensuring the attestation UID matches the registered schema. +- Verifying the attester is the contract owner (Optimism Foundation). +- Decoding the attestation to check the proposer's address and proposal type. +- Only proposals of type `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and `CouncilMemberElections` +require valid EAS attestations. + +### Approve Proposal -- On setup, the contract registers a schema in the predeployed `SchemaRegistry`. -- The submit proposal functions validates attestations by: - - Ensuring the attestation UID matches the registered schema. - - Verifying the attester is the contract owner (Optimism Foundation). - - Decoding the attestation to check the proposer's address and proposal type. -- Only proposals of type `ProtocolOrGovernorUpgrade`, `MaintenanceUpgradeProposals`, and -`CouncilMemberElections` require - valid EAS attestations. +For the top100 delegates we will be using an existing schema that was created by the [dynamic attestation service](https://github.com/CuriaLab/dynamic_attestation_mvp). +The approve proposal function validates attestations by: +- Ensuring the attestation UID matches the registered schema. +- Verifying the attester is the contract owner (Optimism Foundation). +- Decoding the attestation to check the targets address. **Technical implementation docs: [EAS Integration](https://www.notion.so/EAS-Integration-1de9a4c092c780478e19cc8175aa054e?pvs=21)** @@ -533,7 +544,7 @@ maintaining proposal integrity and preventing spam. ## Invariants -- It MUST allow only the `owner` to set the `minimumVotingPower`, `votingCycleData`, `proposalDistributionThreshold`, and +- It MUST allow only the `owner` to set the `votingCycleData`, `proposalDistributionThreshold`, and `proposalTypeApprovalThreshold` - It MUST only allow eligible addresses to approve a proposal - It MUST only allow authorized addresses to submit proposals for types `ProtocolOrGovernorUpgrade`, @@ -548,10 +559,11 @@ maintaining proposal integrity and preventing spam. ## Security Considerations - **Role-Based Restrictions:** The `owner` role should be securely managed, as it holds critical permissions such as - setting voting power thresholds, configuring voting cycles, and distribution limits. Any compromise could significantly - impact governance. + configuring voting cycles, and distribution limits. Any compromise could significantly impact governance. - **Attestation Validation:** Two key aspects need consideration: - The Optimism Foundation must have a secure and thorough process for validating addresses before issuing attestations for specific proposal types - The contract must properly verify attestation expiration and revocation status to prevent the use of outdated or invalid attestations +- **Dynamic Attestation Service:** Since we rely on the dynamic attestation service for updating the top100 delegates + we need to ensure that the service will be running and updating the attestations at least during the governance cycles. From de95558ff710a53a5a96379b3994fb384aadf612 Mon Sep 17 00:00:00 2001 From: OneTony Date: Mon, 16 Jun 2025 14:08:17 +0300 Subject: [PATCH 14/19] fix: linting --- specs/experimental/gov-proposal-validator.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index e7c2f6cfe..e7179dc4c 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -14,10 +14,10 @@ - [Enums](#enums) - [Events](#events) - [EAS Integration](#eas-integration) - - [Why EAS?](#why-eas) - - [Implementation Details](#implementation-details) -- [Submit Proposal](#submit-proposal) -- [Approve Proposal](#approve-proposal) +- [Why EAS?](#why-eas) +- [Implementation Details](#implementation-details) + - [Submit Proposal](#submit-proposal) + - [Approve Proposal](#approve-proposal) - [Proposal uniqueness](#proposal-uniqueness) - [Invariants](#invariants) - [Security Considerations](#security-considerations) @@ -190,6 +190,7 @@ function approveProposal(bytes32 _proposalHash, bytes32 _attestationUid) externa Approving a funding proposal type requires extra attention to the budget amount and options, of the approval voting module, that were provided on the submission of the proposal. This should be handled by the Agora's UI. + --- `moveToVote` @@ -232,6 +233,7 @@ function moveToVote( string memory _proposalDescription ) external returns (uint256 proposalHash_) ``` + --- `canApproveProposal` @@ -244,6 +246,7 @@ Returns true if a delegate is part of the top100 delegates based on the dynamic ```solidity function canApproveProposal(address _delegate, bytes32 _attestationUid) public view returns (bool canApprove_) ``` + --- `setVotingCycleData` @@ -262,6 +265,7 @@ function setVotingCycleData( uint256 _distributionLimit ) external ``` + --- `setProposalDistributionThreshold` @@ -275,6 +279,7 @@ Sets the maximum distribution amount a proposal can request. ```solidity function setProposalDistributionThreshold(uint256 _threshold) external ``` + --- `setProposalTypeApprovalThreshold` From 4af7e01be543694c2094609399dd9bef2e2331cf Mon Sep 17 00:00:00 2001 From: OneTony Date: Mon, 16 Jun 2025 14:17:57 +0300 Subject: [PATCH 15/19] fix: spelling --- specs/experimental/gov-proposal-validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index e7179dc4c..ff9372b9c 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -178,7 +178,7 @@ Approves a proposal before being moved for voting, used by the top delegates. the proposal was submitted. - MUST provide a valid attestation UID - MUST check if the attestation has been revoked -- The attestation MUST reffer to non partial delegation +- The attestation MUST refer to non partial delegation - MUST check if caller has already approved the same proposal - MUST store the approval vote - MUST emit `ProposalApproved` when successfully called @@ -314,7 +314,7 @@ bytes32 public immutable ATTESTATION_SCHEMA_UID_APPROVED_PROPOSERS; The EAS' schema UID that is used to verify attestation for a top100 delegate that can approve a proposal submission. ```solidity -/// Schema { ranl: string, includePartialDelegation: bool, date: string } +/// Schema { rank: string, includePartialDelegation: bool, date: string } bytes32 public immutable ATTESTATION_SCHEMA_UID_TOP100_DELEGATES; ``` From 396130618c16837e22e1b51c9c7855ad759ff022 Mon Sep 17 00:00:00 2001 From: OneTony Date: Mon, 16 Jun 2025 14:35:41 +0300 Subject: [PATCH 16/19] fix: approve proposal description --- specs/experimental/gov-proposal-validator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index ff9372b9c..0d2684059 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -174,8 +174,8 @@ Approves a proposal before being moved for voting, used by the top delegates. - MUST check if proposal hash corresponds to a valid proposal - MUST check if caller has enough voting power to call the function and approve a proposal - The voting power of the delegate is checked against the end of the last voting cycle from when - the proposal was submitted. + The voting power of the delegate is checked against a dynamic attestation service that + updates the top100 delegates every day. - MUST provide a valid attestation UID - MUST check if the attestation has been revoked - The attestation MUST refer to non partial delegation From d23b08240930ee3f9f7fe4a8669431c725230145 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Tue, 17 Jun 2025 21:17:37 +0300 Subject: [PATCH 17/19] fix: submit upgrade proposal (#40) * fix: submit upgrade proposal * fix: text --- specs/experimental/gov-proposal-validator.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 0d2684059..529be5e35 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -58,7 +58,11 @@ The contract has a single `owner` role (Optimism Foundation) with permissions to Submits a Protocol/Governor Upgrade or a Maintenance Upgrade proposal to move for voting. -`MaintenanceUpgradeProposals` type can move straight to voting if all submission checks pass, unlike +`ProtocolOrGovernorUpgrade`: If all submission checks pass for this proposal type, the `ProposalValidator` +will store the submission proposal data and will be able to accept approvals by top delegates before being +able to move for voting. + +`MaintenanceUpgrade`: This proposal type can move straight to voting if all submission checks pass, unlike the rest of the proposals where they need to collect a number of approvals by top delegates in order to move to vote. This call should be atomic. @@ -70,12 +74,14 @@ to move to vote. This call should be atomic. - MUST NOT execute any operations - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events - MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct +- MUST move straight to vote IF `_proposalType` is `MaintenanceUpgrade` ```solidity -function submitMaintenanceUpgradeProposal( +function submitUpgradeProposal( uint248 _againstThreshold, string memory _proposalDescription, - bytes32 _attestationUid + bytes32 _attestationUid, + ProposalType _proposalType ) external returns (bytes32 proposalHash_); ``` From 46af87565847dd9d8b86c944b4168a7e3a81e95f Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:18:58 +0300 Subject: [PATCH 18/19] fix: pp spec inconsistencies (#41) * fix: spec inconsistencies * fix: linting --- specs/experimental/gov-proposal-validator.md | 196 ++++++++++++------- 1 file changed, 121 insertions(+), 75 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 529be5e35..6ae633b17 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -36,10 +36,10 @@ The `ProposalValidator` manages the proposal lifecycle through three main action - `Submit Proposal`: Records new proposals - `Approve Proposal`: Handles proposal approvals -- `Move to Vote`: Transitions approved proposals to voting phase +- `Move to Vote`: Transitions approved proposals to the Governor for voting -The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers for specific proposal -types. For detailed flows of each proposal, see [design docs](https://github.com/ethereum-optimism/design-docs/pull/260). +The contract also integrates with EAS (Ethereum Attestation Service) to verify authorized proposers and approvals for specific +proposal types. For detailed flows of each proposal, see [design docs](https://github.com/ethereum-optimism/design-docs/pull/260). ## Roles @@ -60,40 +60,42 @@ Submits a Protocol/Governor Upgrade or a Maintenance Upgrade proposal to move fo `ProtocolOrGovernorUpgrade`: If all submission checks pass for this proposal type, the `ProposalValidator` will store the submission proposal data and will be able to accept approvals by top delegates before being -able to move for voting. +able to move to the Governor for voting. `MaintenanceUpgrade`: This proposal type can move straight to voting if all submission checks pass, unlike the rest of the proposals where they need to collect a number of approvals by top delegates in order to move to vote. This call should be atomic. - MUST be called by an `owner` approved address +- MUST only be called for `ProtocolOrGovernorUpgrade` or `MaintenanceUpgrade` proposal type - MUST check if the proposal is a duplicate - MUST use the `Optimistic` Voting Module - MUST provide a valid againstThreshold - MUST provide a valid attestation UID - MUST NOT execute any operations - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events -- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct -- MUST move straight to vote IF `_proposalType` is `MaintenanceUpgrade` +- MUST store submission proposal data which are defined by the `ProposalData` struct +- MUST call `Governor.proposeWithModule` IF `_proposalType` is `MaintenanceUpgrade` ```solidity function submitUpgradeProposal( uint248 _againstThreshold, string memory _proposalDescription, bytes32 _attestationUid, - ProposalType _proposalType + ProposalType _proposalType, + uint256 _votingCycle ) external returns (bytes32 proposalHash_); ``` **Optimistic Voting Module** -Maintenance Upgrade proposals use the `Optimistic` voting module. +Both proposals use the `Optimistic` voting module. For the `ProposalSettings` of the voting module, these are: -- `uint248 againstThreshold`: Should be provided by the proposer. This value will be the percentage +- `uint248 againstThreshold`: Should be provided by the caller. This value will be the percentage that will be used to calculate the fraction of the votable supply that the proposal will need in votes in order to pass. -- `bool isRelativeToVotableSupply`: Should always be `true`. +- `bool isRelativeToVotableSupply`: If voting power should be relative to the votable supply. Should always be `true`. --- `submitCouncilMemberElectionsProposal` @@ -107,14 +109,15 @@ Submits a Council Member Elections proposal for approval and voting. - MUST provide a valid attestation UID - MUST NOT execute any operations - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events -- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct +- MUST store submission proposal data which are defined by the `ProposalData` struct ```solidity function submitCouncilMemberElectionsProposal( uint128 _criteriaValue, string[] _optionDescriptions, string memory _proposalDescription, - bytes32 _attestationUid + bytes32 _attestationUid, + uint256 _votingCycle ) external returns (bytes32 proposalHash_); ``` @@ -133,7 +136,7 @@ For the `ProposalOptions` of the voting module, these are: `submitFundingProposal` -Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP tokens for approval and voting. +Submits a `GovernanceFund` or `CouncilBudget` proposal type, for approval and voting, that transfers OP tokens. - MUST only be called for `GovernanceFund` or `CouncilBudget` proposal type - CAN be called by anyone @@ -143,8 +146,8 @@ Submits a `GovernanceFund` or `CouncilBudget` proposal type that transfers OP to - MUST use the `Predeploys.GOVERNANCE_TOKEN` and `IERC20.transfer` signature to create the `calldata` for each option - MUST NOT request to transfer more than `proposalDistributionThreshold` tokens -- MUST emit `ProposalSubmitted` event -- MUST store submission proposal data which are defined by the `ProposalSubmissionData` struct +- MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events +- MUST store submission proposal data which are defined by the `ProposalData` struct ```solidity function submitFundingProposal( @@ -154,13 +157,13 @@ function submitFundingProposal( uint256[] _optionsAmounts, string memory _description, ProposalType _proposalType, + uint256 _votingCycle ) external returns (bytes32 proposalHash_); ``` **Approval Voting Module** -Funding proposals use the `Approval` voting module but unlike the Protocol or Governor upgrade proposals, -funding proposals need to execute token transfers. +Funding proposals use the `Approval` voting module. This requires the user who submits the proposal to provide some additional data related to the proposal. For the `ProposalSettings` of the voting module, these are: @@ -176,12 +179,12 @@ For the `ProposalOptions` of the voting module, these are: `approveProposal` -Approves a proposal before being moved for voting, used by the top delegates. +Approves a proposal before being moved to Governor for voting, used by the top delegates. - MUST check if proposal hash corresponds to a valid proposal -- MUST check if caller has enough voting power to call the function and approve a proposal - The voting power of the delegate is checked against a dynamic attestation service that - updates the top100 delegates every day. +- MUST check if the caller is one of the top100 delegates + The top100 delegates is checked against a dynamic attestation service that updates the +top100 delegates every day. - MUST provide a valid attestation UID - MUST check if the attestation has been revoked - The attestation MUST refer to non partial delegation @@ -199,45 +202,77 @@ by the Agora's UI. --- -`moveToVote` +`moveToVoteProtocolOrGovernorUpgradeProposal` -Checks if the provided proposal is ready to move for voting. Based on the Proposal Type different checks are being -validated. If all checks pass then `OptimismGovernor.proposeWithModule` is being called to forward the proposal for voting. +Moves a Protocol or Governor Upgrade proposal to vote by proposing it on the Governor. +If all checks pass then `OptimismGovernor.proposeWithModule` is being called to forward the proposal. - MUST create the `proposalHash` and check if it exists and is valid - Proposal MUST have gathered equal or more than the `requiredApprovals` defined for that type - MUST check if proposal has already moved for voting +- MUST check that is called by the proposer that submitted the proposal +- `_optionsRecipients` and `_optionsAmounts` MUST be empty - MUST emit `ProposalMovedToVote` event -For `ProtocolOrGovernorUpgrade` type: - -- MUST also check that is called by the proposer that submitted the proposal -- `_optionsRecipients` and `_optionsAmounts` MUST be empty +```solidity +function moveToVoteProtocolOrGovernorUpgradeProposal( + uint248 _againstThreshold, + string memory _proposalDescription +) + external + returns (bytes32 proposalHash_) +``` -For `MaintenanceUpgradeProposals` type: +--- -- This type does not require any checks and is being forwarded to the Governor contracts, this should happen atomically. +`moveToVoteCouncilMemberElectionsProposal` -For `CouncilMemberElections` type: +Moves a council member elections proposal to vote by proposing it on the Governor. +If all checks pass then `OptimismGovernor.proposeWithModule` is being called to forward the proposal. +- MUST create the `proposalHash` and check if it exists and is valid +- Proposal MUST have gathered equal or more than the `requiredApprovals` defined for that type +- MUST check if proposal has already moved for voting - MUST also check that is called by the proposer that submitted the proposal - `_optionsRecipients` and `_optionsAmounts` MUST be empty - Proposal MUST be moved to vote during a valid voting cycle +- MUST emit `ProposalMovedToVote` event -For `GovernanceFund` and `CouncilBudget` types: +```solidity +function moveToVoteCouncilMemberElectionsProposal( + uint128 _criteriaValue, + string[] memory _optionsDescriptions, + string memory _proposalDescription +) + external + returns (bytes32 proposalHash_) +``` + +--- +`moveToVoteFundingProposal` + +Moves a funding proposal to vote by proposing it on the Governor. +If all checks pass then `OptimismGovernor.proposeWithModule` is being called to forward the proposal. + +- MUST create the `proposalHash` and check if it exists and is valid +- Proposal MUST have gathered equal or more than the `requiredApprovals` defined for that type +- MUST check if proposal has already moved for voting - Proposal MUST be moved to vote during a valid voting cycle - MUST check if the total amount of tokens that can possible be distributed during this voting cycle does not go over the `VotingCycleData.votingCycleDistributionLimit` ```solidity -function moveToVote( +function moveToVoteFundingProposal( uint128 _criteriaValue, + string[] memory _optionsDescriptions, address[] memory _optionsRecipients, uint256[] memory _optionsAmounts, - string[] memory _optionDescriptions, - string memory _proposalDescription -) external returns (uint256 proposalHash_) + string memory _description, + ProposalType _proposalType +) + external + returns (bytes32 proposalHash_) ``` --- @@ -250,7 +285,7 @@ Returns true if a delegate is part of the top100 delegates based on the dynamic - MUST return TRUE if the delegate is part of the top100 and can approve a proposal ```solidity -function canApproveProposal(address _delegate, bytes32 _attestationUid) public view returns (bool canApprove_) +function canApproveProposal(bytes32 _attestationUid, address _delegate) public view returns (bool canApprove_) ``` --- @@ -266,9 +301,9 @@ Sets the voting cycle data. ```solidity function setVotingCycleData( uint256 _cycleNumber, - uint256 _startBlock, + uint256 _startingTimestamp, uint256 _duration, - uint256 _distributionLimit + uint256 _votingCycleDistributionLimit ) external ``` @@ -288,40 +323,41 @@ function setProposalDistributionThreshold(uint256 _threshold) external --- -`setProposalTypeApprovalThreshold` +`setProposalTypeData` -Sets the number of approvals a specific proposal type should have before being able to move for voting. +Sets the data for a proposal type. The amount of approvals and the voting module id each type has. - MUST only be called by the owner of the contract - MUST change the previous value to the new one -- MUST emit `ProposalTypeApprovalThresholdSet` event +- MUST emit `ProposalTypeDataSet` event ```solidity -function setProposalTypeApprovalThreshold( - uint8 _proposalTypeId, - uint256 _approvalThreshold +function setProposalTypeData( + ProposalType _proposalType, + ProposalTypeData memory _proposalTypeData ) external ``` ### Properties -`ATTESTATION_SCHEMA_UID_APPROVED_PROPOSERS` +`APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID` -The EAS' schema UID that is used to verify attestation for approved addresses that can submit proposals for specific -`ProposalTypes`. +The schema UID for attestations in the Ethereum Attestation Service for checking if the caller +is an approved proposer. ```solidity /// Schema { approvedProposer: address, proposalType: uint8 } -bytes32 public immutable ATTESTATION_SCHEMA_UID_APPROVED_PROPOSERS; +bytes32 public immutable APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID; ``` -`ATTESTATION_SCHEMA_UID_TOP100_DELEGATES` +`TOP_DELEGATES_ATTESTATION_SCHEMA_UID` -The EAS' schema UID that is used to verify attestation for a top100 delegate that can approve a proposal submission. +The schema UID for attestations in the Ethereum Attestation Service for checking if the caller +is part of the top100 delegates. ```solidity /// Schema { rank: string, includePartialDelegation: bool, date: string } -bytes32 public immutable ATTESTATION_SCHEMA_UID_TOP100_DELEGATES; +bytes32 public immutable TOP_DELEGATES_ATTESTATION_SCHEMA_UID; ``` `GOVERNOR` @@ -332,6 +368,14 @@ The address of the Optimism Governor contract. IOptimismGovernor public immutable GOVERNOR; ``` +`proposalTypesConfigurator` + +The proposal types configurator contract. + +```solidity +IProposalTypesConfigurator public proposalTypesConfigurator; +``` + `proposalDistributionThreshold` The maximum amount of tokens a proposal can request. @@ -348,26 +392,26 @@ A mapping that stores the data for each voting cycle. mapping(uint256 => VotingCycleData) public votingCycles; ``` -`_proposals` +`proposalTypesData` -A mapping that stores each submitted proposal's data based on its `proposalHash`. The proposal hash is produced by hashing -the ABI encoded values of specific proposal params, see [Proposal uniqueness](#proposal-uniqueness). +A mapping that stores data related to each proposal type. ```solidity -mapping(bytes32 => ProposalSubmissionData) private _proposals; +mapping(ProposalType => ProposalTypeData) public proposalTypesData; ``` -`_proposalTypesData` +`_proposals` -A mapping that stores data related to each proposal type. +A mapping that stores each submitted proposal's data based on its `proposalHash`. The proposal hash is produced by hashing +the ABI encoded values of specific proposal params, see [Proposal uniqueness](#proposal-uniqueness). ```solidity -mapping(ProposalType => ProposalTypeData) private _proposalTypesData; +mapping(bytes32 => ProposalData) private _proposals; ``` ### Structs -`ProposalSubmissionData` +`ProposalData` A struct that holds all the data for a single proposal. Consists of: @@ -376,16 +420,16 @@ A struct that holds all the data for a single proposal. Consists of: - `inVoting`: Returns true if the proposal has already been submitted for voting - `delegateApprovals`: Mapping of addresses that approved the specific proposal - `approvalsCounter`: The number of approvals the specific proposal has received -- `timestamp`: The timestamp of the proposal submission +- `votingCycle`: The voting cycle number the proposal is targetted for. ```solidity -struct ProposalSubmissionData { +struct ProposalData { address proposer; ProposalType proposalType; - bool inVoting; + bool movedToVote; mapping(address => bool) delegateApprovals; uint256 approvalsCounter; - uint256 timestamp; + uint256 votingCycle; } ``` @@ -394,8 +438,8 @@ struct ProposalSubmissionData { A struct that holds data for each proposal type. - `requiredApprovals`: The number of approvals each proposal type requires in order to be able to move for voting. -- `proposalVotingModule`: The voting module (proposal type configurator) that can be used for each proposal type. This -is set by the owner on initialize. +- `proposalVotingModule`: The proposal type ID used to get the voting module from the configurator. +This is set by the owner on initialize. ```solidity struct ProposalTypeData { @@ -408,15 +452,17 @@ struct ProposalTypeData { A struct that stores data related to the voting cycle. -- `startingBlock`: The block number/timestamp that the voting cycle starts +- `startingTimestamp`: The starting timestamp of the voting cycle. - `duration`: The duration of the specific voting cycle - `votingCycleDistributionLimit`: A more general distribution amount limit tied to the voting cycle +- `movedToVoteTokenCount` The total amount of tokens to possibly be distributed in the voting cycle ```solidity struct VotingCycleData { - uint256 startingBlock; + uint256 startingTimestamp; uint256 duration; uint256 votingCycleDistributionLimit; + movedToVoteTokenCount; } ``` @@ -453,7 +499,7 @@ MUST be triggered when a proposal submission is successfully called. ```solidity event ProposalSubmitted( - uint256 indexed proposalHash, + bytes32 indexed proposalHash, address indexed proposer, string description, ProposalType proposalType @@ -465,7 +511,7 @@ event ProposalSubmitted( MUST be triggered when `approveProposal` is successfully called. ```solidity -event ProposalApproved(uint256 indexed proposalHash, address indexed approver); +event ProposalApproved(bytes32 indexed proposalHash, address indexed approver); ``` `ProposalMovedToVote` @@ -473,7 +519,7 @@ event ProposalApproved(uint256 indexed proposalHash, address indexed approver); MUST be triggered when `moveToVote` is successfully called. ```solidity -event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor); +event ProposalMovedToVote(bytes32 indexed proposalHash, address indexed executor); ``` `ProposalVotingModuleData` @@ -481,7 +527,7 @@ event ProposalMovedToVote(uint256 indexed proposalHash, address indexed executor MUST be triggered with `ProposalSubmitted` event. ```solidity -event ProposalVotingModuleData(uint256 indexed proposalHash, bytes encodedVotingModuleData); +event ProposalVotingModuleData(bytes32 indexed proposalHash, bytes encodedVotingModuleData); ``` ## EAS Integration @@ -530,7 +576,7 @@ as the one created on `Governor.hashProposalWithModule` and used by the `Voting A proposal is uniquely identified by a tuple: -- `proposalValidator`: The address of the proposal validator +- `governor`: The address of the Governor - `module`: The address of the voting module the proposal uses - `proposalVotingModuleData`: The encoded voting module data - `descriptionHash`: The hash of the description @@ -538,13 +584,13 @@ A proposal is uniquely identified by a tuple: These elements are ABI-encoded and hashed: ```solidity -keccak256(abi.encode(proposalValidator, module, proposalVotingModuleData, descriptionHash)); +keccak256(abi.encode(governor, module, proposalVotingModuleData, descriptionHash)); ``` This hash serves as a unique identifier for the proposal. The contract stores submitted proposals in: ```solidity -mapping(bytes32 => ProposalSubmissionData) private _proposals; +mapping(bytes32 => ProposalData) private _proposals; ``` When a new proposal is submitted, the contract checks that `_proposals[proposalHash]` is empty (e.g., `proposer == From edc57caa7e6667f401619a5a7c30e617ef97cb66 Mon Sep 17 00:00:00 2001 From: 0xOneTony <112496816+0xOneTony@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:46:43 +0300 Subject: [PATCH 19/19] fix: proposal validator spec discrepancies (#42) * fix: can approve proposal visibility * fix: event name * fix: proposal data field name * fix: voting cycle data field name * fix: submit funding proposal invariant * fix: criteria value description * fix: spelling * fix: approved proposer schema * fix: against threshold description --- specs/experimental/gov-proposal-validator.md | 27 ++++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/specs/experimental/gov-proposal-validator.md b/specs/experimental/gov-proposal-validator.md index 6ae633b17..1d6283005 100644 --- a/specs/experimental/gov-proposal-validator.md +++ b/specs/experimental/gov-proposal-validator.md @@ -93,8 +93,8 @@ Both proposals use the `Optimistic` voting module. For the `ProposalSettings` of the voting module, these are: - `uint248 againstThreshold`: Should be provided by the caller. This value will be the percentage -that will be used to calculate the fraction of the votable supply that the proposal will need in votes in order -to pass. +that will be used to calculate the fraction of against votes relative to the votable supply that the proposal will need in +against votes in order to not pass. - `bool isRelativeToVotableSupply`: If voting power should be relative to the votable supply. Should always be `true`. --- @@ -145,7 +145,7 @@ Submits a `GovernanceFund` or `CouncilBudget` proposal type, for approval and vo - MUST use "Threshold" criteria type for the Voting Module - MUST use the `Predeploys.GOVERNANCE_TOKEN` and `IERC20.transfer` signature to create the `calldata` for each option -- MUST NOT request to transfer more than `proposalDistributionThreshold` tokens +- MUST NOT request to transfer more than `proposalDistributionThreshold` tokens for each option - MUST emit `ProposalSubmitted` and `ProposalVotingModuleData` events - MUST store submission proposal data which are defined by the `ProposalData` struct @@ -168,9 +168,8 @@ This requires the user who submits the proposal to provide some additional data For the `ProposalSettings` of the voting module, these are: - `uint128 criteriaValue`: Since the passing criteria type is always "Threshold", for this proposal type, -this value will be the percentage that will be used to calculate the fraction of the votable supply that -the proposal will need in votes in order to pass. - +this value will be the absolute number of votes required for the proposal to pass. +It represents the threshold that must be met or exceeded for any option to be considered successful. For the `ProposalOptions` of the voting module, these are: - `string[] optionsDescriptions`: The strings of the different options that can be voted. - `address[] optionsRecipients`: An address for each option to transfer funds to in case the option passes the voting. @@ -285,7 +284,7 @@ Returns true if a delegate is part of the top100 delegates based on the dynamic - MUST return TRUE if the delegate is part of the top100 and can approve a proposal ```solidity -function canApproveProposal(bytes32 _attestationUid, address _delegate) public view returns (bool canApprove_) +function canApproveProposal(bytes32 _attestationUid, address _delegate) external view returns (bool canApprove_) ``` --- @@ -296,7 +295,7 @@ Sets the voting cycle data. - MUST only be called by the owner of the contract - MUST NOT change an existing voting cycle -- MUST emit `VotingCycleSet` event +- MUST emit `VotingCycleDataSet` event ```solidity function setVotingCycleData( @@ -346,7 +345,7 @@ The schema UID for attestations in the Ethereum Attestation Service for checking is an approved proposer. ```solidity -/// Schema { approvedProposer: address, proposalType: uint8 } +/// Schema { proposalType: uint8, date: string } bytes32 public immutable APPROVED_PROPOSER_ATTESTATION_SCHEMA_UID; ``` @@ -419,8 +418,8 @@ A struct that holds all the data for a single proposal. Consists of: - `proposalType`: The type of the proposal - `inVoting`: Returns true if the proposal has already been submitted for voting - `delegateApprovals`: Mapping of addresses that approved the specific proposal -- `approvalsCounter`: The number of approvals the specific proposal has received -- `votingCycle`: The voting cycle number the proposal is targetted for. +- `approvalCount`: The number of approvals the specific proposal has received +- `votingCycle`: The voting cycle number the proposal is targeted for. ```solidity struct ProposalData { @@ -428,7 +427,7 @@ struct ProposalData { ProposalType proposalType; bool movedToVote; mapping(address => bool) delegateApprovals; - uint256 approvalsCounter; + uint256 approvalCount; uint256 votingCycle; } ``` @@ -462,7 +461,7 @@ struct VotingCycleData { uint256 startingTimestamp; uint256 duration; uint256 votingCycleDistributionLimit; - movedToVoteTokenCount; + uint256 movedToVoteTokenCount; } ``` @@ -542,7 +541,7 @@ custom logic to the contract. proposers. - **Low integration overhead**: `EAS` and `SchemaRegistry` are predeploys on Optimism, requiring no additional deployments or infrastructure. -- **Schema validation**: Ensures attestations follow strict data formats (e.g. `address approvedDelegate, uint8 proposalType`). +- **Schema validation**: Ensures attestations follow strict data formats (e.g. `uint8 proposalType, string date`). - **Revocability and expiration**: EAS supports expiration and revocation semantics natively, allowing dynamic control over authorized proposers.