An Aragon OSx governance plugin where a proposal passes once X out of Y listed members approve it on-chain.
Documentation: protocol-doc, Multisig Plugin.
Halborn: audit report
- Commit ID: fffc680f563698cfb7aec962fb89b4196025f629
- Started: 2024-11-18
- Finished: 2025-02-13
| Contract | Purpose |
|---|---|
src/Multisig.sol |
The plugin (UUPS upgradeable). |
src/MultisigSetup.sol |
Plugin setup: installs, updates and uninstalls the plugin through the PluginSetupProcessor. |
src/ListedCheckCondition.sol |
Permission condition enforcing onlyListed on proposal creation. |
src/IMultisig.sol |
Interface. |
Deployed addresses and ABIs are published in artifacts-hub.
Requirements: Foundry and just.
git clone https://github.com/aragon/multisig-plugin.git
cd multisig-plugin
just init <network> # fetch the git submodules (lib/), create .env from .env.example, select the network (default: mainnet)
just help # list every recipe (available once the submodules are fetched)Network settings (RPC, chain ID, OSx and management DAO addresses) come from just-foundry (lib/just-foundry/networks/<network>.env). Switch networks with just switch <network> and inspect the resolved values with just env. Secrets go in .env (see .env.example) or in vars (see .vars.yaml).
forge buildjust test # unit, integration, fuzz and invariant tests
just test-fork # fork tests against the active network (requires RPC_URL)
just test-coverage # HTML coverage report under ./reportLayout:
test/
├── unit/<contract>/concrete/<function>/ one folder per function
├── unit/<contract>/fuzz/
├── unit/metadata/ build metadata vs what the contracts decode
├── unit/script/ deployment scripts
├── integration/concrete/pluginSetup/ install, update and uninstall through a local PluginSetupProcessor
├── integration/fuzz/ invariants
├── fork/ live networks
└── utils/ constants, mocks, harnesses
just predeploy # simulate Deploy.s.sol
just deploy # new network: creates the plugin repo and publishes VERSION_BUILD
just pre-new-version # simulate NewVersion.s.sol
just new-version # existing repo: deploys the setup, prints the management DAO proposalDeploy.s.solcreates the plugin repo (ENS subdomainMULTISIG_ENS_SUBDOMAIN:multisigin production; no ENS name when unset), publishesPlaceholderSetupbuilds belowVERSION_BUILDso that build numbers match every other network, publishesMultisigSetupasVERSION_BUILD, and hands ROOT, MAINTAINER and UPGRADE_REPO over to the management DAO.NewVersion.s.soldeploysMultisigSetupand prints thecreateVersionaction(s) forMULTISIG_PLUGIN_REPO_ADDRESS, wrapped in acreateProposalcall forMANAGEMENT_DAO_MULTISIG_ADDRESS. Any member of the management DAO multisig submits it.
Both scripts write artifacts/artifacts-<network>-<timestamp>.json for artifacts-hub (just import-plugin <file> there). For NewVersion, import it only after the proposal has executed.
- Bump
VERSION_BUILDinscript/PluginSettings.sol(andVERSION_RELEASEfor a new release). - Update the files in
script/metadata/:build-metadata.json(itsprepareUpdatekeys are the builds that can update from),new-version-proposal-metadata.json, andrelease-metadata.jsonon a new release. - Pin each file with
just ipfs-pin <file>and paste theipfs://URIs intoscript/PluginSettings.sol. The scripts refuse to run while any of them is empty.
Publishing a build does not update installed plugins: each DAO applies the update through the PluginSetupProcessor. Since build 3, MultisigSetup does not grant UPGRADE_PLUGIN_PERMISSION to the DAO and prepareUpdate from build 3 returns no permissions. A DAO updating from build 3 to a build with a new implementation must therefore grant UPGRADE_PLUGIN_PERMISSION on the plugin to the PluginSetupProcessor, apply the update and revoke it, in the same proposal.
- I have checked out the official repository on the
mainbranch, andgit statusreports no local changes - The rest of the ceremony reports the same
git log -n 1commit hash - I have run
just init <network>andjust envshows the right network, addresses and deployer -
DEPLOYER_KEYis a fresh wallet that only I operate -
ETHERSCAN_API_KEYis set (when the network uses Etherscan) -
VERSION_BUILDand every metadata URI inscript/PluginSettings.solare final -
MULTISIG_ENS_SUBDOMAIN=multisig(new network only) -
just testruns clean, andjust test-forkruns clean on the target network -
just predeploy(orjust pre-new-version) completes without errors -
just balanceshows at least 15% more funds than the simulation estimated - My machine is on a trusted network and exposes no services
- I run
just deploy(orjust new-version)
- The script completed without errors and every contract is verified on the network's explorer
- The log under
logs/matches the console output -
artifacts/artifacts-<network>-<timestamp>.jsonmatches the logged addresses, and it was imported into artifacts-hub (after the proposal executed, forNewVersion) - The plugin repo's ROOT, MAINTAINER and UPGRADE_REPO permissions belong to the management DAO only (
Deploy) - The log, the artifact and
broadcast/<script>/<chain-id>/run-latest.jsonare uploaded to the shared location
just-foundry selects forge-zksync automatically on zkSync networks (just switch zksync or zksync-sepolia). MultisigSetup deploys plugins as UUPS proxies and conditions with new, so no zkSync-specific setup is needed.
Do not open a public issue for a security vulnerability. Email sirt@aragon.org instead.
AGPL-3.0-or-later, see LICENSE.md.