> For the complete documentation index, see [llms.txt](https://docs.eywa.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.eywa.fi/developer-documentation/guide-for-developers/technical-documentation-for-eywa-dao-smart-contracts/proposalmanager.md).

# 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.
