# ProposalManager

### Overview <a href="#overview-7" id="overview-7"></a>

**ProposalManager** is a governance contract built on OpenZeppelin’s **Governor** framework, incorporating additional logic for:

* **Protected Function Selectors:** Certain function calls (on specific chain IDs and target addresses) are restricted to the contract owner. This prevents unauthorized calls to critical on-chain functionality within governance proposals.
* **Cross-Chain Awareness:** Maintains a set of valid chain IDs for bridging proposals, allowing secure multi-chain governance actions when using the **bridge**.
* **Custom Quorum Rules:** Allows the owner to adjust the quorum fraction within set bounds (25%–80%).

The contract also integrates with a **Calldata Helper** (`CALLDATA_HELPER`) to decode function selectors from the bridging calls. By extending `Governor`, `GovernorCountingSimple`, `GovernorVotes`, `GovernorVotesQuorumFraction`, `GovernorTimelockControl`, and `Ownable`, **ProposalManager** offers a robust on-chain DAO mechanism with time-locked execution, vote counting, adjustable quorum, and protective measures for crucial function selectors.

***

### Inherited Contracts and Interfaces <a href="#inherited-contracts-and-interfaces-7" id="inherited-contracts-and-interfaces-7"></a>

1. **Governor (OpenZeppelin):**\
   Base contract for on-chain governance, offering functionalities such as proposal creation, voting, proposal states, etc.
2. **GovernorCountingSimple (OpenZeppelin):**\
   Implements a simple vote counting mechanism: For, Against, and Abstain.
3. **GovernorVotes (OpenZeppelin):**\
   Integrates an `IVotes` token (in this case, an escrow manager providing voting power) for calculating a user’s votes at proposal snapshot blocks.
4. **GovernorVotesQuorumFraction (OpenZeppelin):**\
   Defines quorum as a fraction of the total supply of the governance token. The fraction is adjustable by the contract’s owner within defined limits.
5. **GovernorTimelockControl (OpenZeppelin):**\
   Integrates a `TimelockController` for executing proposals after a governance-defined delay.
6. **Ownable (OpenZeppelin):**\
   Provides basic ownership functionality, allowing the owner to perform restricted actions (e.g., adjusting quorum fraction, managing chain IDs/selectors).
7. **IProposalManager (Custom):**\
   Declares additional functions, errors, and events specifically for managing cross-chain or bridging-based proposals, including protected selectors.

**Additional External References:**

* **ICalldataHelperV1 (`CALLDATA_HELPER`)**: A contract that decodes selectors from proposal call data, used for identifying protected function selectors in bridging calls.
* **EnumerableSet (OpenZeppelin)**: Used to store a set of valid chain IDs, ensuring efficient addition, removal, and existence checks.

***

### Constants <a href="#constants-4" id="constants-4"></a>

```solidity
uint256 public constant MINIMUM_QUORUM_NUMERATOR = 25;
uint256 public constant MAXIMUM_QUORUM_NUMERATOR = 80;

```

* **MINIMUM\_QUORUM\_NUMERATOR (25):** Minimum permissible quorum fraction (25%).
* **MAXIMUM\_QUORUM\_NUMERATOR (80):** Maximum permissible quorum fraction (80%).

***

### State Variables <a href="#state-variables-5" id="state-variables-5"></a>

* **`CALLDATA_HELPER (ICalldataHelperV1 immutable)`**\
  The address of a helper contract used to decode function selectors for bridging calls. Set in the constructor and cannot be changed.
* **`s_bridge (address)`**\
  The address of the **bridge** contract used for cross-chain proposals or calls. Initially set in the constructor, can be updated by `setBridge`.
* **`_chainIds (EnumerableSet.UintSet)`**\
  A private set of valid chain IDs recognized by the contract. The owner can add or remove chain IDs via `addChainId` / `removeChainId`.
* **`s_protectedSelectorsByChainIdAndTarget (mapping(uint256 => mapping(address => bytes4[])))`**\
  Stores arrays of protected function selectors for each `(chainId, target)` pair.
* **`s_isProtectedSelector (mapping(uint256 => mapping(address => mapping(bytes4 => bool))))`**\
  A quick boolean lookup indicating if a particular function selector is protected on a given `chainId` and `target` address.

***

### Constructor <a href="#constructor-7" id="constructor-7"></a>

```solidity
constructor(
    IVotes escrowManager_,
    TimelockController timelock_,
    ICalldataHelperV1 calldataHelper_,
    address owner_,
    address bridge_
)
    Governor("EYWA DAO")
    GovernorVotes(escrowManager_)
    GovernorVotesQuorumFraction(50)
    GovernorTimelockControl(timelock_)
    Ownable(owner_)
{
    CALLDATA_HELPER = calldataHelper_;
    s_bridge = bridge_;
}

```

* **Parameters:**
  * `escrowManager_`: An `IVotes`-compliant contract (e.g., an escrow manager) used for vote calculations.
  * `timelock_`: The `TimelockController` contract address used to queue and execute proposals after a time delay.
  * `calldataHelper_`: The helper contract for decoding function selectors in bridging calls.
  * `owner_`: Address designated as the owner of this contract (can set quorum fraction, manage chain IDs, etc.).
  * `bridge_`: The initial address of the cross-chain bridge.
* **Logic and Effects:**
  1. Initializes the underlying Governor modules:
     * **`Governor("EYWA DAO")`** sets the governor name.
     * **`GovernorVotes(escrowManager_)`** specifies the votes token source.
     * **`GovernorVotesQuorumFraction(50)`** sets an initial quorum fraction of 50%.
     * **`GovernorTimelockControl(timelock_)`** ties this governor to the specified timelock.
     * **`Ownable(owner_)`** sets the contract owner.
  2. Stores `CALLDATA_HELPER` and `s_bridge`.

***

### External Functions <a href="#external-functions-4" id="external-functions-4"></a>

#### 1. **`setBridge(address bridge_)`** <a href="#id-1-setbridgeaddress-bridge" id="id-1-setbridgeaddress-bridge"></a>

```solidity
function setBridge(address bridge_) external onlyOwner

```

* **Description:**\
  Updates the `s_bridge` address used for bridging calls. Only callable by the contract owner.
* **Parameters:**
  * `bridge_`: The new bridge contract address.
* **Events:**
  * **`BridgeUpdated(oldBridge, newBridge)`** logs the address change.

***

#### 2. **`addChainId(uint256 chainId_)`** <a href="#id-2-addchainiduint256-chainid" id="id-2-addchainiduint256-chainid"></a>

```solidity
function addChainId(uint256 chainId_) external onlyOwner

```

* **Description:**\
  Adds a new chain ID to `_chainIds`. If the chain ID already exists, it reverts.
* **Checks:**
  * `_chainIds.add(chainId_)` must return true, otherwise reverts with `ChainIdAlreadyExists(chainId_)`.

***

#### 3. **`removeChainId(uint256 chainId_)`** <a href="#id-3-removechainiduint256-chainid" id="id-3-removechainiduint256-chainid"></a>

```solidity
function removeChainId(uint256 chainId_) external onlyOwner

```

* **Description:**\
  Removes an existing chain ID from `_chainIds`. If the chain ID does not exist, it reverts.
* **Checks:**
  * `_chainIds.remove(chainId_)` must return true, otherwise reverts with `ChainIdDoesNotExist(chainId_)`.

***

#### 4. **`getChainIdsLength()`** <a href="#id-4-getchainidslength" id="id-4-getchainidslength"></a>

```solidity
function getChainIdsLength() external view returns (uint256)

```

* **Description:**\
  Returns the number of chain IDs stored in `_chainIds`.
* **Return:**
  * `uint256`: The length of `_chainIds`.

***

#### 5. **`getChainIdAtIndex(uint256 index_)`** <a href="#id-5-getchainidatindexuint256-index" id="id-5-getchainidatindexuint256-index"></a>

```solidity
function getChainIdAtIndex(uint256 index_) external view returns (uint256)

```

* **Description:**\
  Retrieves a chain ID from `_chainIds` by `index_`.
* **Return:**
  * `uint256`: The chain ID at the specified index.

***

#### 6. **`addProtectedSelector(uint256 chainId_, address target_, bytes4 selector_)`** <a href="#id-6-addprotectedselectoruint256-chainid_-address-target_-bytes4-selector" id="id-6-addprotectedselectoruint256-chainid_-address-target_-bytes4-selector"></a>

```solidity
function addProtectedSelector(uint256 chainId_, address target_, bytes4 selector_) external onlyOwner

```

* **Description:**\
  Marks a function selector (`selector_`) as protected on a particular `chainId_` and `target_`. If the chain ID doesn’t exist or the selector is already protected, it reverts.
* **Events & Checks:**
  * Must have `_chainIds.contains(chainId_)`, otherwise `InvalidChainId(...)`.
  * If `s_isProtectedSelector[chainId_][target_][selector_]` is true, reverts with `SelectorAlreadyExists(...)`.
  * Adds the selector to `s_protectedSelectorsByChainIdAndTarget[chainId_][target_]` and sets `s_isProtectedSelector[chainId_][target_][selector_] = true`.

***

#### 7. **`removeProtectedSelector(uint256 chainId_, address target_, bytes4 selector_)`** <a href="#id-7-removeprotectedselectoruint256-chainid_-address-target_-bytes4-selector" id="id-7-removeprotectedselectoruint256-chainid_-address-target_-bytes4-selector"></a>

```solidity
function removeProtectedSelector(uint256 chainId_, address target_, bytes4 selector_) external onlyOwner

```

* **Description:**\
  Removes a protected function selector from `s_protectedSelectorsByChainIdAndTarget`. If the chain ID isn’t recognized or the selector isn’t actually protected, it reverts.
* **Logic:**
  1. Ensures `chainId_` is valid in `_chainIds`.
  2. Checks if `s_isProtectedSelector[chainId_][target_][selector_] == true`; otherwise reverts with `SelectorDoesNotExist(...)`.
  3. Searches in the array `s_protectedSelectorsByChainIdAndTarget[chainId_][target_]`, removes the entry by swapping the last element, then popping the array.
  4. Deletes `s_isProtectedSelector[chainId_][target_][selector_]`.

***

#### 8. **`updateQuorumNumerator(uint256 quorumNumerator_)`** <a href="#id-8-updatequorumnumeratoruint256-quorumnumerator" id="id-8-updatequorumnumeratoruint256-quorumnumerator"></a>

```solidity
function updateQuorumNumerator(uint256 quorumNumerator_) external override onlyOwner

```

* **Description:**\
  An override from **GovernorVotesQuorumFraction** that checks if the new quorum fraction is within `[MINIMUM_QUORUM_NUMERATOR, MAXIMUM_QUORUM_NUMERATOR]`. Otherwise reverts with `GovernorInvalidQuorumFraction(...)`.

***

#### 9. **`updateTimelock(TimelockController)`** <a href="#id-9-updatetimelocktimelockcontroller" id="id-9-updatetimelocktimelockcontroller"></a>

```solidity
function updateTimelock(TimelockController) external override

```

* **Description:**\
  An override from **GovernorTimelockControl**. This function does nothing here (the timelock is set in the constructor).

***

#### 10. **`propose(...)`** (Overridden from Governor) <a href="#id-10-propose-overridden-from-governor" id="id-10-propose-overridden-from-governor"></a>

```solidity
function propose(
    address[] memory targets_,
    uint256[] memory values_,
    bytes[] memory calldatas_,
    string memory description_
)
    public
    override (Governor, IGovernor)
    returns (uint256)

```

* **Description:**
  * Creates a new proposal. Before creation, the contract calls `_checkSelectors(...)` to see if any protected calls are included in `calldatas_`. If protected calls are found but the proposer is not the owner, it reverts with `ProtectedFunctionSelectorUsed()`.
  * After checking, it calls `super.propose(...)` to proceed with the standard Governor proposal workflow.
* **Parameters:**
  * `targets_`, `values_`, `calldatas_`: Arrays specifying which contracts and functions to call, with how much ETH, plus the function data.
  * `description_`: A string describing the proposal.
* **Return:**
  * `uint256`: The ID of the newly created proposal.

***

### Integration with OpenZeppelin Governor Modules <a href="#integration-with-openzeppelin-governor-modules" id="integration-with-openzeppelin-governor-modules"></a>

#### `clock()` (IERC6372) <a href="#clock-ierc6372" id="clock-ierc6372"></a>

```solidity
function clock() public view override (Governor, GovernorVotes, IERC6372) returns (uint48)

```

* **Description:**\
  Returns the current timestamp as a `uint48`. Used by the governance system for time-related logic.

#### `state(uint256 proposalId_)` <a href="#stateuint256-proposalid" id="stateuint256-proposalid"></a>

```solidity
function state(uint256 proposalId_) public view override(Governor, GovernorTimelockControl, IGovernor) returns (ProposalState)

```

* **Description:**\
  Returns the current state of a proposal (Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, or Executed), integrating timelock considerations.

#### `quorum(uint256 timepoint_)` <a href="#quorumuint256-timepoint" id="quorumuint256-timepoint"></a>

```solidity
function quorum(uint256 timepoint_) public view override (Governor, GovernorVotesQuorumFraction, IGovernor) returns (uint256)

```

* **Description:**\
  Calculates the number of votes required for quorum at a given `timepoint_`, i.e., `(totalSupplyAt(timepoint_) * quorumNumerator(timepoint_)) / quorumDenominator()`.

#### `CLOCK_MODE()` <a href="#clock_mode" id="clock_mode"></a>

```solidity
function CLOCK_MODE() public pure override (Governor, GovernorVotes, IERC6372) returns (string memory)

```

* **Description:**\
  Returns `"mode=timestamp"`, indicating that block timestamps (instead of block numbers) are used for governance timing.

#### `proposalThreshold()`, `votingDelay()`, `votingPeriod()`, `proposalNeedsQueuing(...)` <a href="#proposalthreshold-votingdelay-votingperiod-proposalneedsqueuing" id="proposalthreshold-votingdelay-votingperiod-proposalneedsqueuing"></a>

These standard overrides define:

* **`proposalThreshold()`:** `2_500e18` — minimum voting power needed to create a proposal.
* **`votingDelay()`:** `2 days` — time between proposal creation and the start of voting.
* **`votingPeriod()`:** `5 days` — how long votes are accepted.
* **`proposalNeedsQueuing(...)`:** returns `true`, indicating proposals must be queued in the timelock after passing, before execution.

***

### Internal Functions <a href="#internal-functions-1" id="internal-functions-1"></a>

#### `_checkSelectors(...)` <a href="#checkselectors" id="checkselectors"></a>

```solidity
function _checkSelectors(
    address proposalCreator_,
    address[] memory targets_,
    bytes[] memory calldatas_
)
    private
    view

```

* **Description:**
  * Iterates over `targets_` and associated `calldatas_`, extracting the function selector for each. Then checks if it is among the protected selectors for the given chain ID (`block.chainid`) or if the target is the `s_bridge` address.
  * If any call uses a protected selector and `proposalCreator_` is not the contract owner, reverts with `ProtectedFunctionSelectorUsed()`.
* **Parameters:**
  * `proposalCreator_`: The address that created the proposal.
  * `targets_`: The array of target contract addresses.
  * `calldatas_`: The array of encoded function calls corresponding to each target.
* **Logic:**
  1. If `targets_[i]` equals `s_bridge`, decodes further with `CALLDATA_HELPER.decode(...)` to find `(m_calldata, m_target, m_chainId)`. Then extracts the first 4 bytes (selector) from `m_calldata`.
  2. Checks `s_protectedSelectorsByChainIdAndTarget[m_chainId][m_target]`.
  3. If matched, marks `m_isProtected = true`.
  4. Otherwise, if `targets_[i] != s_bridge`, reads the first 4 bytes from `calldatas_[i]` as the selector and checks `s_protectedSelectorsByChainIdAndTarget[block.chainid][targets_[i]]`.
  5. If `m_isProtected == true` and `proposalCreator_ != owner()`, revert with `ProtectedFunctionSelectorUsed()`.

***

#### `_queueOperations(...)`, `_executeOperations(...)`, `_cancel(...)`, `_executor()` <a href="#queueoperations-_executeoperations-_cancel-_executor" id="queueoperations-_executeoperations-_cancel-_executor"></a>

These are standard overrides from **GovernorTimelockControl** that handle queueing, executing, and canceling proposals in the timelock. The `_executeOperations` override also calls `_checkSelectors(...)` again before executing the proposal.

***

### Events <a href="#events-5" id="events-5"></a>

1. **`BridgeUpdated(address indexed oldBridge, address indexed newBridge)`**
   * Emitted when the bridge address changes via `setBridge`.

No additional custom events are defined besides those in the `IProposalManager` interface and standard Governor events.

***

### Errors <a href="#errors-6" id="errors-6"></a>

From `IProposalManager`, we have:

* **`ChainIdAlreadyExists(uint256 chainId)`**
* **`ChainIdDoesNotExist(uint256 chainId)`**
* **`SelectorAlreadyExists(bytes4 selector)`**
* **`SelectorDoesNotExist(bytes4 selector)`**
* **`InvalidChainId(uint256 chainId)`**
* **`ProtectedFunctionSelectorUsed()`**
* **`GovernorInvalidQuorumFraction(uint256 newQuorumNumerator, uint256 quorumDenominator)`** (part of `GovernorVotesQuorumFraction` logic)

***

### Summary <a href="#summary-7" id="summary-7"></a>

**ProposalManager** extends and customizes OpenZeppelin’s Governor to cater to EYWA DAO’s multi-chain governance needs. Through protective measures (`_checkSelectors`), it guards certain function calls on specific chain IDs and target addresses, ensuring only the contract owner can propose them. Additionally, it manages a dynamic range of acceptable quorum fractions and integrates with a `TimelockController` for secure, time-delayed proposal execution. By combining these features with `ICalldataHelperV1` for decoding bridging calls, **ProposalManager** offers a flexible yet secure governance solution for cross-chain proposals in the EYWA ecosystem.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.eywa.fi/developer-documentation/guide-for-developers/technical-documentation-for-eywa-dao-smart-contracts/proposalmanager.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
