From 989531a76978fcb23831c2548d4813ac548c59fb Mon Sep 17 00:00:00 2001 From: Ardor Baal Date: Mon, 25 May 2026 18:57:23 +0000 Subject: [PATCH] docs(governance): enrich proposal submission guidance --- docs/governance/02_proposal_submission.md | 248 +++++++++++-------- docs/governance/03_agent_proposal_prompt.md | 250 ++++++++++++++++++++ 2 files changed, 402 insertions(+), 96 deletions(-) create mode 100644 docs/governance/03_agent_proposal_prompt.md diff --git a/docs/governance/02_proposal_submission.md b/docs/governance/02_proposal_submission.md index ce89fc3d..e0a93387 100644 --- a/docs/governance/02_proposal_submission.md +++ b/docs/governance/02_proposal_submission.md @@ -5,20 +5,28 @@ sidebar_label: Submission Guidelines # Submission Guidelines +Olympus On-Chain Governance (OCG) proposals are executable transactions. A strong submission should make the intended protocol change clear, prove the calldata is correct, and give tokenholders enough time and context to review it before voting. + :::info -Before submitting a new proposal, it's essential to understand Olympus' On-Chain Governance (OCG) system. Please review the details in the [Intro page](./00_governance.md) and [Proposal lifecycle page](./proposal_lifecycle). +Before submitting a new proposal, review the [Governance intro](./00_governance.md) and [Proposal lifecycle](./proposal_lifecycle). The lifecycle page explains activation, voting, queueing, and execution after the initial submission. ::: -## Requirements +## Submission Paths -To submit a new proposal to on-chain governance (OCG), submitters interact directly with Governor Bravo contract by calling `propose()` function. However, the following requirements must be met before submitting a proposal: +There are two common ways to move a proposal from idea to on-chain submission: -1. **Minimum voting power** - proposer must hold, and maintain, at least `proposalThreshold` of the total gOHM supply at time of proposal submission. -2. **Code review** - proposals must be added, tested and simulated in [olympus-v3 repository](https://github.com/OlympusDAO/olympus-v3/). This process ensures that the proposal is secure and achieves the intended outcomes without putting the protocol at risk. +1. **Self-submission** - the author holds or receives enough delegated gOHM voting power, prepares the proposal in the `olympus-v3` repository, and submits it directly. +2. **Operator-assisted submission** - the author prepares the intent and review materials, while a trusted proposer or agent operator helps convert the proposal into tested calldata and submits from an eligible proposer address. -### Minimum voting power +Both paths should produce the same public artifacts: a reviewed `olympus-v3` pull request, test output, proposal calldata, and an on-chain proposal that references the PR or other review material in the description. + +## Requirements -Whily anyone can submit a proposal by calling `propose()`, the transaction will revert unless proposer has a minimum amount of voting power to submit a proposal, determined by calling the `getProposalThresholdVotes()` function: +To submit a new OCG proposal, a proposer calls Governor Bravo's `propose()` function. The transaction will revert unless the proposer satisfies these requirements. + +### Minimum Voting Power + +The proposer must hold, or be delegated, at least `proposalThreshold` of the total gOHM supply. The threshold is calculated by `getProposalThresholdVotes()`: ```solidity function getProposalThresholdVotes() public view returns (uint256) { @@ -26,108 +34,156 @@ function getProposalThresholdVotes() public view returns (uint256) { } ``` -The proposer's gOHM balance is checked within the `propose()`, `queue()`, and `execute()` functions. If, at any time, the proposer's gOHM balance falls below the proposal threshold, the proposal will fail. This approach ensures that proposers (or their delegators) maintain skin in the game, preventing them from benefiting from malicious proposals. +The proposer's votes are checked during `propose()`, `queue()`, and `execute()`. Delegated voting power must remain in place through the full proposal lifecycle. :::info -The current proposalThreshold is set to 0.017% of the total gOHM supply +The current `proposalThreshold` is 0.017% of the total gOHM supply. Check the live Governor contract before submitting, because the exact gOHM amount changes with supply. +::: + +:::tip +Governor Bravo checks prior votes. If voting power is delegated to a proposer address, wait until at least the next block before calling `propose()`. ::: -### Code review +### Proposal Simulation + +Proposals must be added, tested, and simulated in the [olympus-v3 repository](https://github.com/OlympusDAO/olympus-v3/). Olympus uses [forge-proposal-simulator](https://solidity-labs.gitbook.io/forge-proposal-simulator/) to make proposals reviewable in a forked environment before they are submitted on-chain. -Olympus uses [forge-proposal-simulator](https://solidity-labs.gitbook.io/forge-proposal-simulator/), an open-source framework designed to structure proposals effectively and streamline the proposal verification process. On a high-level, this framework allows anyone to execute proposals in a forked environment and develop integration tests to examine the new system's behavior in a controlled sandbox. +A proposal PR should show: -:::warning Warning -Due to the importance of this framework in ensuring transparency and security, **Emergency MS will immediately veto any proposals not satisfying the two requirements**. This stance is based on the belief that bypassing the framework indicates an attempt to pass a harmful proposal by obfuscating its review process. +- The proposal contract in `src/proposals/` +- Any new or updated addresses in `src/proposals/addresses.json` +- Tests in `src/test/proposals/` +- Simulation output proving the proposal can execute successfully +- Validation assertions proving the intended post-state + +:::warning +Emergency MS may veto proposals that bypass the proposal simulator or make calldata difficult to review. The simulator is part of Olympus' governance safety process. ::: -## Instructions - -To successfully submit a proposal, the proposer must do the following: - -1. Submit code as a pull request to [olympus-v3 repo](https://github.com/OlympusDAO/olympus-v3) -2. Reach out in Discord to request a review from the community -3. Acquire, or be delegated to, `proposalThreshold` percent of gOHM supply. Proposer must maintain that amount of gOHM until conclusion of proposal -4. Call `propose()` on Governor Bravo contract, including the PR link in the description - -To submit a successful PR with integration tests, begin by creating a new contract in `src/proposals/` named after its corresponding OIP (e.g., `OIP_XXX.sol`). The contract should inherit `GovernorBravoProposal`, and use [OIP_XXX.sol](./OIP_XXX.sol) as a template. - -Declare all necessary dependencies in [address registry](https://github.com/OlympusDAO/olympus-v3/blob/master/src/proposals/addresses.json). Follow this naming convention: - -- Use lowercase. -- Separate words with dashes. -- Start with the source: `"olympus"` or `"external"`. -- Indicate the address/contract type: e.g., `"token"`, `"multisig"`, `"policy"`. -- Include the address/contract name. -- Exceptions: `"proposer"`, `"olympus-governor"`, `"olympus-kernel"`. - -Examples: - -- Olympus policies: `"olympus-policy-xxx"` -- Olympus modules: Use the following pattern instead of importing module addresses: - ```solidity - Kernel kernel = Kernel(addresses.getAddress("olympus-kernel")); - address TRSRY = address(kernel.getModuleForKeycode(toKeycode(bytes5("TRSRY")))); - ``` -- External tokens (DAI, sDAI, etc): `external-tokens-xxx` -- External contracts: `external-coolers-factory` -- Olympus multisigs: `olympus-multisig-dao` or `olympus-multisig-emergency` -- Olympus legacy contracts (OHM, gOHM, staking): `"olympus-legacy-xxx"` - -Deploy the smart contracts by running `_deploy()`. If the contract is already deployed, import addresses from [address registry](https://github.com/OlympusDAO/olympus-v3/blob/master/src/proposals/addresses.json) instead. If necessary, use `_afterDeploy()` hook to cache balances or other values to be used in `validate()`. - -Construct the proposal actions by using `_build()`. Use the following functions: - -````solidity -// @dev push an action to the proposal -function \_pushAction( -uint256 value, -address target, -bytes memory data, -string memory \_description -) internal { -actions.push( -Action({ -value: value, -target: target, -arguments: data, -description: \_description -}) +## Recommended Workflow + +### 1. Define the Proposal Intent + +Write down the plain-English intent before writing Solidity. Include: + +- What protocol state should change +- Which contracts will be called +- Why the change is needed +- Expected risk, reversibility, and operational follow-up +- Forum, Snapshot, OIP, or RFC links if they exist + +This context should also appear in the proposal description or linked PR. + +### 2. Prepare the Proposal Contract + +Create a proposal contract in `src/proposals/`, named after the proposal or OIP. The contract should inherit `GovernorBravoProposal`. + +Use the proposal contract to: + +- Declare dependencies through `addresses.json` +- Deploy or reference contracts in `_deploy()` +- Build actions in `_build()` +- Simulate execution in `_run()` +- Assert the expected post-state in `_validate()` + +Use `_pushAction()` for each Governor action: + +```solidity +_pushAction( + target, + abi.encodeWithSelector(TargetContract.setParameter.selector, newValue), + "Set target parameter" ); -} +``` + +When referencing addresses, prefer the registry over hardcoded addresses. Use lowercase, dash-separated keys such as `olympus-policy-example`, `external-token-usds`, or `olympus-multisig-dao`. + +For current Olympus Kernel contracts, use the [Protocol Visualizer](https://protocol-visualizer.olympusdao.finance/) or its GraphQL API to verify the active contract set before adding addresses. The visualizer reflects installed Kernel modules and policies, which helps reviewers confirm that proposal actions target the intended contracts. + +### 3. Add Tests + +Create tests in `src/test/proposals/`. The tests should import the proposal and run it against a mainnet fork. - // @dev push an action to the proposal with a value of 0 - function _pushAction( - address target, - bytes memory data, - string memory _description - ) internal { - _pushAction(0, target, data, _description); - } - ``` - -Simulate the proposal execution by calling `_run()`. Use the provided code: - - ```solidity - // Executes the proposal actions. - function _run(Addresses addresses, address) internal override { - // Simulates actions on TimelockController - _simulateActions( - addresses.getAddress("olympus-governor"), - addresses.getAddress("olympus-legacy-gohm"), - addresses.getAddress("proposer") - ); - } - ``` - -Perform validations and assertions through `_validate_()`. Validations should definitely demonstrate to the community that the proposal is secure and achieves the intended outcomes without putting the protocol at risk. - -Create tests in [src/test/proposals/](https://github.com/OlympusDAO/olympus-v3/tree/master/src/test/proposals) named after the OIP (e.g., `OIP_XXX.t.sol` for unit tests and `testProposal_xxx` for integration tests). Use [OIP_XXX.t.sol](https://github.com/OlympusDAO/olympus-v3/tree/master/src/test/proposals/OIP_XXX.t.sol) as a template. Import your proposal, and its dependencies. Modify `setUp()` in `OIP_XXX.sol` and include this test to ensure `setUp()` is executed correctly: +A minimal test should prove setup works: ```solidity -// [DO NOT DELETE] Dummy test to ensure that `setUp` is executed function testProposal() public { assertTrue(true); } -```` +``` + +Useful tests go further and verify the post-proposal state that tokenholders care about. For example, assert changed parameters, permissions, enabled flags, balances, or market configuration. + +### 4. Open a Review PR + +Open a pull request against [olympus-v3](https://github.com/OlympusDAO/olympus-v3/) with the proposal contract, tests, and any address registry changes. + +The PR should include: + +- Summary of actions +- Governance/forum context +- Local test command and result +- Any assumptions or operational dependencies +- Expected on-chain proposer address, if known + +### 5. Check Proposer Readiness + +Before submission, verify: + +- Proposer has enough delegated gOHM voting power +- Delegation was mined at least one block ago +- Proposer has enough ETH for gas +- Proposer can sign from the intended wallet or keystore +- Proposal description includes the PR link or equivalent review artifact + +### 6. Dry-Run Submission + +Use the repository's proposal scripts or Foundry directly to dry-run the exact submission path before broadcasting. + +For script-based proposals, submit through the wrapper script contract, not the proposal contract directly. The wrapper is what prepares the proposal script environment for `forge script`. + +Example shape: + +```bash +src/scripts/proposals/submitProposal.sh \ + --file src/proposals/MyProposal.sol \ + --contract MyProposalScript \ + --chain "$RPC_URL" \ + --env .env.proposer \ + --broadcast false +``` + +Review the printed targets, values, calldata, description, proposer address, and any simulation output before broadcasting. + +### 7. Broadcast + +After review, broadcast the same command with broadcasting enabled: + +```bash +src/scripts/proposals/submitProposal.sh \ + --file src/proposals/MyProposal.sol \ + --contract MyProposalScript \ + --chain "$RPC_URL" \ + --env .env.proposer \ + --broadcast true +``` + +Capture the transaction hash, block number, proposal id, and proposer address. Add them to the PR or governance discussion so reviewers can follow the proposal through activation, voting, queueing, and execution. + +## Operator-Assisted Submissions + +A proposer does not need to be the same person who authored the proposal. A contributor can work with a trusted proposer, multisig, or agent operator that acts as middleware between the human intent and Governor Bravo. + +This can lower friction for contributors who understand the desired protocol change but do not maintain local Foundry tooling, proposer infrastructure, or enough delegated gOHM voting power. + +The operator should still keep the process transparent: + +- Use a dedicated proposer address where possible +- Hold only the gas ETH needed for submission +- Receive delegated gOHM voting power explicitly for the proposal +- Run the same fork tests and dry-runs as a self-submitter +- Share the exact PR, calldata, proposer address, and transaction hash +- Never broadcast until the proposal author and relevant reviewers approve the final payload -Once you feel comfortable, open a pull request in [olympus-v3 repo](https://github.com/OlympusDAO/olympus-v3) in the format `OIP-XXX: proposal simulation`. +For a contract-aware prompt and implementation template that an agent can use to +start proposal work, see [Agent Proposal Prompt](./agent_proposal_prompt). diff --git a/docs/governance/03_agent_proposal_prompt.md b/docs/governance/03_agent_proposal_prompt.md new file mode 100644 index 00000000..e5659150 --- /dev/null +++ b/docs/governance/03_agent_proposal_prompt.md @@ -0,0 +1,250 @@ +--- +sidebar_position: 3 +sidebar_label: Agent Proposal Prompt +--- + +# Agent Proposal Prompt + +Use this file as a bootstrap prompt for an agent helping prepare an Olympus +On-Chain Governance (OCG) proposal. The agent's job is to turn human intent into +reviewable artifacts, not to bypass review or broadcast transactions on its own. + +## Agent Role + +You are assisting with an Olympus OCG proposal. Produce a reviewed, testable +proposal workflow in the `olympus-v3` repository. + +Your output should make the proposal easier for humans to inspect: + +- Intent and expected post-state +- Verified contract map +- Proposal contract and script wrapper +- Mainnet-fork tests +- Printed Governor actions and calldata +- Dry-run output +- Explicit broadcast approval checkpoint + +Never broadcast a transaction unless the human explicitly approves the final +payload. + +## Required Context From The Human + +Ask for or infer these items before coding: + +- Proposal title +- Plain-English intent +- Target protocol state after execution +- Candidate target contracts and functions +- Parameter values +- Relevant forum, Snapshot, OIP, RFC, or PR links +- Whether the change is reversible +- Who can approve the final payload before broadcast +- Timing constraints + +If the human cannot name the exact contracts, discover candidates and return the +contract map for review before encoding actions. + +## Contract Discovery + +Use the Protocol Visualizer as the first pass for active Olympus Kernel +contracts. It reflects the currently installed Kernel modules and policies. + +```bash +curl -sG "https://protocol-visualizer-api.olympusdao.finance/graphql" \ + --data-urlencode 'query={ Contract(where: { chainId: { _eq: 1 }, isEnabled: { _eq: true } }, limit: 100) { chainId address name version contractType isEnabled } }' +``` + +Use this result to identify active modules and policies. Always filter for +`isEnabled: true` when looking for the current active set. + +For governance work, identify: + +- Governor Bravo: receives `propose()`, `activate()`, `queue()`, and + `execute()` +- Timelock: executes queued proposal actions after the delay +- gOHM: voting token used for delegation and prior-vote checks +- Kernel: active registry of Olympus modules and policies +- Target contracts: policies, modules, or external contracts changed by the + proposal + +Then cross-check addresses against `olympus-v3/src/proposals/addresses.json`. +If an address is missing, add it using the repository's naming conventions. Do +not rely on stale proposal code without verifying that the target is still +active or intentionally legacy. + +## Repository Artifacts + +Work in `olympus-v3`. + +Create or update: + +- `src/proposals/.sol` +- `src/test/proposals/.t.sol` +- `src/proposals/addresses.json`, if needed + +Open a draft PR before public review. The PR should summarize every Governor +action, list test commands and results, and call out assumptions. + +## Solidity Proposal Shape + +Use the repository's proposal simulator pattern. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.20; + +import {Addresses} from "proposal-sim/addresses/Addresses.sol"; +import {GovernorBravoProposal} from "proposal-sim/proposals/OlympusGovernorBravoProposal.sol"; +import {ProposalScript} from "src/proposals/ProposalScript.sol"; + +contract ExampleProposal is GovernorBravoProposal { + function id() public pure override returns (uint256) { + return 0; + } + + function name() public pure override returns (string memory) { + return "Example Proposal"; + } + + function description() public pure override returns (string memory) { + return + string.concat( + "# Example Proposal\n\n", + "## Summary\n\n", + "Describe the proposal intent.\n\n", + "## Actions\n\n", + "1. Describe each Governor action.\n" + ); + } + + function _deploy(Addresses addresses, address deployer) internal override { + // Cache Kernel, modules, policies, or pre-state needed for validation. + } + + function _build(Addresses addresses) internal override { + address target = addresses.getAddress("olympus-policy-example"); + + _pushAction( + target, + abi.encodeWithSelector(ExampleTarget.setParameter.selector, 123), + "Set example parameter" + ); + } + + function _run(Addresses addresses, address) internal override { + _simulateActions( + addresses.getAddress("olympus-kernel"), + addresses.getAddress("olympus-governor"), + addresses.getAddress("olympus-legacy-gohm"), + addresses.getAddress("proposer") + ); + } + + function _validate(Addresses addresses, address) internal view override { + address target = addresses.getAddress("olympus-policy-example"); + + require( + ExampleTarget(target).parameter() == 123, + "Example parameter was not updated" + ); + } +} + +contract ExampleProposalScript is ProposalScript { + constructor() ProposalScript(new ExampleProposal()) {} +} +``` + +Prefer state-aware actions. If the desired state already exists, avoid pushing +unnecessary actions. If the proposal must reconcile pending state, inspect both +current and pending values and validate the final intended condition. + +## Test Shape + +Use a mainnet fork and simulate the proposal. + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {ProposalTest} from "./ProposalTest.sol"; +import {ExampleProposal} from "src/proposals/ExampleProposal.sol"; + +contract ExampleProposalTest is ProposalTest { + uint256 public constant BLOCK = 25152621; + + function setUp() public virtual { + vm.createSelectFork(vm.envString("RPC_URL"), BLOCK); + + ExampleProposal proposal = new ExampleProposal(); + + hasBeenSubmitted = false; + + _setupSuite(address(proposal)); + _simulateProposal(); + } +} +``` + +Tests should assert the intended post-state, not only that setup succeeds. + +Useful validations include: + +- Parameter values changed as intended +- Market periods, facilities, or policies are enabled or disabled as intended +- Roles and permissions are present +- Timelock or Kernel dependencies are correct +- No stale pending state remains when it should be cleared +- Balances, caps, or limits are correct when relevant + +## Dry-Run And Submission + +Before any broadcast: + +1. Run the proposal test on a mainnet fork. +2. Print proposal inputs. +3. Confirm proposer voting power and ETH balance. +4. Dry-run the exact submission command with broadcasting disabled. +5. Present the final payload for human approval. + +For script-based proposals, submit through the script wrapper contract, not the +proposal contract directly. + +```bash +src/scripts/proposals/submitProposal.sh \ + --file src/proposals/ExampleProposal.sol \ + --contract ExampleProposalScript \ + --chain "$RPC_URL" \ + --env .env.proposer \ + --broadcast false +``` + +Only after explicit approval should the same command be run with +`--broadcast true`. + +## Proposer Readiness Checklist + +Verify live: + +- Proposer address +- Delegated gOHM votes +- Current proposal threshold +- Delegation transaction and block +- ETH balance for gas +- Signer or keystore access +- Ability to maintain threshold through `queue()` and `execute()` + +Governor Bravo uses prior votes, so delegation must be mined before the proposal +submission block. + +## Final Agent Output + +Return a concise handoff with: + +- Proposal branch and draft PR +- Contract map and verification source +- Files changed +- Test command and result +- Dry-run command and result +- Proposer readiness status +- Exact items needing human approval