diff --git a/.env.example b/.env.example index 9ae1a4ba..2985893e 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,9 @@ # GENERAL ## The network used for testing purposes -NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon", "mumbai","baseMainnet", "baseGoerli", "baseSepolia", "arbitrum", "arbitrumSepolia"] +NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon"] -## To deploy contracts, you need to set: +## To upload the metadata for deployed contracts PUB_PINATA_JWT= # CONTRACTS @@ -24,13 +24,38 @@ POLYGONSCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" BASESCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" ARBISCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" - -# SUBGRAPH - -## The Graph credentials -GRAPH_KEY="zzzzzzzzzzzz" +## Deployment addresses +# Note that addresses will be also used for testing so ensure they are valid on the network you are running the forking tests on. + +# optional, address if not provided will get it from the latest deployment on the network or from the ens registrar +# defined in the framework if it supports it. In case it is not found will create a new one. +# For example for mainnet: +# 0x8c278e37D0817210E18A7958524b7D0a1fAA6F7b +PLUGIN_REPO_ADDRESS=0x0000000000000000000000000000000000000000 +# not optional, if not provided will not be able to deploy the plugin or run the forking tests. +# For example for mainnet: +# 0xaac9E9cdb8C1eb42d881ADd59Ee9c53847a3a4f3 +PLUGIN_REPO_FACTORY_ADDRESS=0x0000000000000000000000000000000000000000 +# optional, only needed when a latest versions of the plugin are going to be deploy on a new network. +PLACEHOLDER_SETUP=0x0000000000000000000000000000000000000000 +# not optional, if not provided will not be able to transfer the ownership of the plugin when deploying +# the plugin or running the forking tests, or when the plugin is going to be installed on the management dao. +# for example for mainnet: +# 0xf2d594F3C93C19D7B1a6F15B5489FFcE4B01f7dA +MANAGEMENT_DAO_ADDRESS=0x0000000000000000000000000000000000000000 +# optional, only needed when the plugin is going to be installed on the management dao. +# for example for mainnet: +# 0xE978942c691e43f65c1B7c7F8f1dc8cDF061B13f +PLUGIN_SETUP_PROCESSOR_ADDRESS=0x0000000000000000000000000000000000000000 + +## Plugin installation in management DAO +# all of them are optional, only needed when the plugin is going to be installed on the management dao. +MANAGEMENT_DAO_MULTISIG_LISTED_ONLY=true +MANAGEMENT_DAO_MULTISIG_MIN_APPROVALS=1 +MANAGEMENT_DAO_MULTISIG_APPROVERS=0x0000000000000000000000000000000000000000,0x1111111111111111111111111111111111111111111111111111,... ## Subgraph +GRAPH_KEY="zzzzzzzzzzzz" SUBGRAPH_NAME="osx" SUBGRAPH_VERSION="v1.0.0" SUBGRAPH_NETWORK_NAME="mainnet" # ["mainnet", "sepolia", "polygon", "base", "arbitrum"] diff --git a/.github/workflows/contracts-tests.yml b/.github/workflows/contracts-tests.yml index fb34a28e..8988781e 100644 --- a/.github/workflows/contracts-tests.yml +++ b/.github/workflows/contracts-tests.yml @@ -26,7 +26,7 @@ jobs: node-version: 18 - name: 'Install the dependencies' - run: 'yarn install --frozen-lockfile' + run: 'yarn --frozen-lockfile --ignore-scripts' - name: 'Build the contracts' run: 'yarn build' @@ -37,4 +37,7 @@ jobs: run: 'yarn coverage' env: NETWORK_NAME: ${{ vars.NETWORK_NAME }} + PLUGIN_REPO_FACTORY_ADDRESS: ${{ vars.PLUGIN_REPO_FACTORY_ADDRESS }} + MANAGEMENT_DAO_ADDRESS: ${{ vars.MANAGEMENT_DAO_ADDRESS }} ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} + PUB_PINATA_JWT: ${{ secrets.PUB_PINATA_JWT }} diff --git a/.github/workflows/formatting-linting.yml b/.github/workflows/formatting-linting.yml index 4552e7a2..5c1e05e4 100644 --- a/.github/workflows/formatting-linting.yml +++ b/.github/workflows/formatting-linting.yml @@ -19,7 +19,7 @@ jobs: node-version: 18 - name: 'Install the dependencies' - run: 'yarn install --frozen-lockfile' + run: 'yarn --frozen-lockfile --ignore-scripts' - name: 'Check code formatting' run: 'yarn prettier:check' diff --git a/.github/workflows/publish-npm-artifacts.yml b/.github/workflows/publish-npm-artifacts.yml new file mode 100644 index 00000000..9f6824f1 --- /dev/null +++ b/.github/workflows/publish-npm-artifacts.yml @@ -0,0 +1,63 @@ +name: Publish NPM Artifacts + +on: + workflow_dispatch: + +jobs: + publish-artifacts-to-npm: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + registry-url: 'https://registry.npmjs.org/' + + - name: Configure NPM for Scoped Package + run: | + cd packages/artifacts + SCOPE=$(jq -r '.name' package.json | cut -d'/' -f1) + echo "$SCOPE:registry=https://registry.npmjs.org/" > ~/.npmrc + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ~/.npmrc + + - name: Get Version from package.json + id: get_version + run: | + cd packages/artifacts + VERSION=$(jq -r '.version' package.json) + TAG_VERSION="v$VERSION" + echo "VERSION=$TAG_VERSION" >> $GITHUB_ENV + + - name: Create Git Tag + run: | + git config --global user.name "github-actions" + git config --global user.email "github-actions@github.com" + git tag $VERSION + git push origin $VERSION + + - name: Install Dependencies + run: | + cd packages/artifacts + yarn install + + - name: Build Package + env: + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} + run: | + cd packages/artifacts + yarn build + + - name: Publish to NPM + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + cd packages/artifacts + if [[ "$VERSION" == *"-alpha"* ]]; then + npm publish --tag alpha --access public + else + npm publish --tag latest --access public + fi diff --git a/.github/workflows/subgraph-tests.yml b/.github/workflows/subgraph-tests.yml index ef013b86..23fb8d61 100644 --- a/.github/workflows/subgraph-tests.yml +++ b/.github/workflows/subgraph-tests.yml @@ -25,15 +25,15 @@ jobs: node-version: 18 - name: 'Install root dependencies' - run: 'yarn install --frozen-lockfile' + run: 'yarn --frozen-lockfile --ignore-scripts' working-directory: . - name: 'Install dependencies for contracts' - run: 'yarn install --frozen-lockfile' + run: 'yarn --frozen-lockfile --ignore-scripts' working-directory: packages/contracts - name: 'Install the dependencies for subgraph' - run: 'yarn install --frozen-lockfile' + run: 'yarn --frozen-lockfile --ignore-scripts' working-directory: packages/subgraph - name: 'Build the subgraph' diff --git a/.gitignore b/.gitignore index 1f4f5fae..167efd64 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ .coverage_artifacts .coverage_cache .coverage_contracts -artifacts build cache coverage @@ -16,8 +15,10 @@ packages/subgraph/tests/.bin packages/contracts/.upgradable packages/contracts/deployments +packages/contracts/createVersionProposalData*.json +packages/contracts/artifacts -docs +packages/artifacts/src/abi.ts # files *.env @@ -33,4 +34,4 @@ packages/subgraph/tests/helpers/extended-schema.ts artifacts-zk cache-zk -deployments-zk \ No newline at end of file +deployments-zk diff --git a/.prettierignore b/.prettierignore index 41c0dc9a..a7366b2f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,6 +15,9 @@ imported generated */js-client/test/integration/*.test.ts +packages/contracts/docs/templates/contract.hbs +packages/contracts/docs/templates/page.hbs + # files *.env *.log diff --git a/README.md b/README.md index 81519c2a..b71ab839 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,16 @@ [license]: https://opensource.org/licenses/AGPL-v3 [license-badge]: https://img.shields.io/badge/License-AGPL_v3-blue.svg +## Audit + +### v1.3.0 + +**Halborn**: [audit report](https://github.com/aragon/osx/tree/main/audits/Halborn_AragonOSx_v1_4_Smart_Contract_Security_Assessment_Report_2025_01_03.pdf) + +- Commit ID: [fffc680f563698cfb7aec962fb89b4196025f629](https://github.com/aragon/multisig-plugin/commit/fffc680f563698cfb7aec962fb89b4196025f629) +- Started: 2024-11-18 +- Finished: 2025-02-13 + ## Project The root folder of the repo includes two subfolders: @@ -35,7 +45,7 @@ The root folder of the repo includes two subfolders: The root-level `package.json` file contains global `dev-dependencies` for formatting and linting. After installing the dependencies with ```sh -yarn install +yarn --ignore-scripts ``` you can run the associated [formatting](#formatting) and [linting](#linting) commands. @@ -79,7 +89,7 @@ Before deploying, you MUST also change the default hardhat private key (`PRIVATE This package is located in `packages/contracts`, first run ```sh -yarn install +yarn --ignore-scripts ``` ### Building @@ -235,7 +245,7 @@ yarn deploy:zksync --network zksyncMainnet --tags ... In `packages/subgraph`, first run ```sh -yarn install +yarn --ignore-scripts ``` which will also run diff --git a/package.json b/package.json index 26d74775..ca57c786 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "@aragon/multisig-plugin", "description": "A template to fork from when developing an Aragon OSx plugin", - "version": "0.0.1-alpha.1", "license": "AGPL-3.0-or-later", "author": { "name": "Aragon", diff --git a/packages/artifacts/CHANGELOG.md b/packages/artifacts/CHANGELOG.md new file mode 100644 index 00000000..1b7b9106 --- /dev/null +++ b/packages/artifacts/CHANGELOG.md @@ -0,0 +1,11 @@ +# Multisig Plugin artifacts + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v1.3.0 + +### Added + +- First NPM release of the Plugin's ABI (release 1, build 3) + diff --git a/packages/artifacts/README.md b/packages/artifacts/README.md new file mode 100644 index 00000000..d45c27bb --- /dev/null +++ b/packages/artifacts/README.md @@ -0,0 +1,58 @@ +# Multisig Plugin artifacts + +This package contains the ABI of the Multisig Plugin for OSx, as well as the address of its plugin repository on each supported network. Install it with: + +```sh +yarn add @aragon/multisig-plugin-artifacts +``` + +## Usage + +```typescript +// ABI definitions +import { + MultisigABI, + IMultisigABI, + ListedCheckConditionABI, + MultisigSetupABI +} from "@aragon/multisig-plugin-artifacts"; + +// Plugin Repository addresses per-network +import { addresses } from "@aragon/multisig-plugin-artifacts"; +``` + +You can also open [addresses.json](./src/addresses.json) directly. + +## Development + +### Building the package + +Install the dependencies and generate the local ABI definitions. + +```sh +yarn --ignore-scripts +yarn build +``` + +The `build` script will: +1. Move to `packages/contracts`. +2. Install its dependencies. +3. Compile the contracts using Hardhat. +4. Generate their ABI. +5. Extract their ABI and embed it into on `src/abi.ts`. + +## Documentation + +You can find all documentation regarding how to use this plugin in [Aragon's documentation here](https://docs.aragon.org/multisig/1.x/index.html). + +## Contributing + +If you like what we're doing and would love to support, please review our `CONTRIBUTING_GUIDE.md` [here](https://github.com/aragon/multisig-plugin/blob/main/CONTRIBUTIONS.md). We'd love to build with you. + +## Security + +If you believe you've found a security issue, we encourage you to notify us. We welcome working with you to resolve the issue promptly. + +Security Contact Email: sirt@aragon.org + +Please do not use the issue tracker for security issues. diff --git a/packages/artifacts/package.json b/packages/artifacts/package.json new file mode 100644 index 00000000..e20ed851 --- /dev/null +++ b/packages/artifacts/package.json @@ -0,0 +1,22 @@ +{ + "name": "@aragon/multisig-plugin-artifacts", + "author": "Aragon X", + "version": "1.3.0-alpha", + "license": "AGPL-3.0-or-later", + "description": "The Multisig Plugin ABI definition", + "typings": "dist/index.d.ts", + "main": "dist/index.js", + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "yarn prepare-abi && rm -Rf dist && tsc -p tsconfig.json", + "prepare-abi": "bash prepare-abi.sh" + }, + "devDependencies": { + "typescript": "^5.5.4" + } +} diff --git a/packages/artifacts/prepare-abi.sh b/packages/artifacts/prepare-abi.sh new file mode 100644 index 00000000..c7c02cac --- /dev/null +++ b/packages/artifacts/prepare-abi.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# Exit on error +set -e + +# Constants +CONTRACTS_FOLDER="../contracts" +TARGET_ABI_FILE="./src/abi.ts" + +# Move into contracts package and install dependencies +cd $CONTRACTS_FOLDER + +yarn --ignore-scripts && yarn build + +# Move back to artifacts package +cd - > /dev/null + +# Wipe the destination file +echo "// NOTE: Do not edit this file. It is generated automatically." > $TARGET_ABI_FILE + +# Extract the abi field and create a TS file +for SRC_CONTRACT_FILE in $(ls $CONTRACTS_FOLDER/src/*.sol ) +do + SRC_FILE_NAME=$(basename $(echo $SRC_CONTRACT_FILE)) + SRC_FILE_PATH=$CONTRACTS_FOLDER/artifacts/src/$SRC_FILE_NAME/${SRC_FILE_NAME%".sol"}.json + + ABI=$(node -e "console.log(JSON.stringify(JSON.parse(fs.readFileSync(\"$SRC_FILE_PATH\").toString()).abi))") + CONTRACT_NAME=${SRC_FILE_NAME%".sol"} + + echo "const ${CONTRACT_NAME}ABI = $ABI as const;" >> $TARGET_ABI_FILE + echo "export {${CONTRACT_NAME}ABI};" >> $TARGET_ABI_FILE + echo "" >> $TARGET_ABI_FILE +done + +echo "ABI prepared: $TARGET_ABI_FILE" diff --git a/packages/artifacts/src/addresses.json b/packages/artifacts/src/addresses.json new file mode 100644 index 00000000..5225116a --- /dev/null +++ b/packages/artifacts/src/addresses.json @@ -0,0 +1,18 @@ +{ + "pluginRepo": { + "mainnet": "0x8c278e37D0817210E18A7958524b7D0a1fAA6F7b", + "sepolia": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2", + "holesky": "0xde1414F52A885cb9b899870f85bDCdb2Dec7C5dd", + "devSepolia": "0xA0901B5BC6e04F14a9D0d094653E047644586DdE", + "polygon": "0x5A5035E7E8aeff220540F383a9cf8c35929bcF31", + "mumbai": "0x2c4690b8be39adAd4F15A69340d5035aC6E53eEF", + "base": "0xcDC4b0BC63AEfFf3a7826A19D101406C6322A585", + "baseSepolia": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2", + "arbitrum": "0x7553E6Fb020c5740768cF289e603770AA09b7aE2", + "arbitrumSepolia": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2", + "linea": "0x2667636E0f5eA63c771509e1d3377177E9Da056D", + "lineaSepolia": "0x86B433017Ce556ED93e6D34d6Ba0e3a9EB19015C", + "zksync": "0x83f88d380073c8F929fAB649F3d016649c101D3A", + "zksyncSepolia": "0x2cae809b6ca149b49cBcA8B887Da2805174052F3" + } +} diff --git a/packages/artifacts/src/index.ts b/packages/artifacts/src/index.ts new file mode 100644 index 00000000..4afad30b --- /dev/null +++ b/packages/artifacts/src/index.ts @@ -0,0 +1,4 @@ +export * from "./abi"; + +import * as addresses from "./addresses.json"; +export {addresses}; diff --git a/packages/artifacts/tsconfig.json b/packages/artifacts/tsconfig.json new file mode 100644 index 00000000..11d5e877 --- /dev/null +++ b/packages/artifacts/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "commonjs", + "declaration": true, + "outDir": "./dist", + "resolveJsonModule": true + }, + "include": ["src"] +} diff --git a/packages/artifacts/yarn.lock b/packages/artifacts/yarn.lock new file mode 100644 index 00000000..f4937e77 --- /dev/null +++ b/packages/artifacts/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +typescript@^5.5.4: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== diff --git a/packages/contracts/.eslintrc.yml b/packages/contracts/.eslintrc.yml index 57709aed..06943327 100644 --- a/packages/contracts/.eslintrc.yml +++ b/packages/contracts/.eslintrc.yml @@ -16,3 +16,5 @@ ignorePatterns: - typechain # files - coverage.json + - docs/config.js + - scripts/gen-nav.js diff --git a/packages/contracts/README.adoc b/packages/contracts/README.adoc new file mode 100644 index 00000000..6657909a --- /dev/null +++ b/packages/contracts/README.adoc @@ -0,0 +1,11 @@ += Multisig API + +== Core + +{{Multisig}} + +{{MultisigSetup}} + +{{ListedCheckCondition}} + + diff --git a/packages/contracts/deploy/10_create_repo/11_create_repo.ts b/packages/contracts/deploy/10_create_repo/11_create_repo.ts index caf42237..5993be82 100644 --- a/packages/contracts/deploy/10_create_repo/11_create_repo.ts +++ b/packages/contracts/deploy/10_create_repo/11_create_repo.ts @@ -1,22 +1,19 @@ -import {PLUGIN_REPO_ENS_SUBDOMAIN_NAME} from '../../plugin-settings'; +import { + PLUGIN_REPO_ENS_SUBDOMAIN_NAME, + PLUGIN_REPO_PROXY_NAME, +} from '../../plugin-settings'; import { findPluginRepo, getProductionNetworkName, pluginEnsDomain, + getPluginRepoFactory, + frameworkSupportsENS, } from '../../utils/helpers'; -import { - getLatestNetworkDeployment, - getNetworkNameByAlias, -} from '@aragon/osx-commons-configs'; -import { - UnsupportedNetworkError, - findEventTopicLog, -} from '@aragon/osx-commons-sdk'; +import {findEventTopicLog} from '@aragon/osx-commons-sdk'; import { PluginRepoRegistryEvents, PluginRepoRegistry__factory, PluginRepo__factory, - PluginRepoFactory__factory, } from '@aragon/osx-ethers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -27,32 +24,21 @@ import path from 'path'; * @param {HardhatRuntimeEnvironment} hre */ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - console.log( - `Creating the '${pluginEnsDomain( - hre - )}' plugin repo through Aragon's 'PluginRepoFactory'...` - ); + console.log(`Creating plugin repo through Aragon's 'PluginRepoFactory'...`); const [deployer] = await hre.ethers.getSigners(); - // Get the Aragon `PluginRepoFactory` from the `osx-commons-configs` - const productionNetworkName = getProductionNetworkName(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); - } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; - } - const pluginRepoFactory = PluginRepoFactory__factory.connect( - networkDeployments.PluginRepoFactory.address, - deployer - ); + // Get the Aragon `PluginRepoFactory` + const pluginRepoFactory = await getPluginRepoFactory(hre); + + // if the framework supports ENS, use the subdomain from the `./plugin-settings.ts` file + // otherwise, use an empty string + const supportsENS = await frameworkSupportsENS(pluginRepoFactory); + const subdomain = supportsENS ? PLUGIN_REPO_ENS_SUBDOMAIN_NAME : ''; // Create the `PluginRepo` through the Aragon `PluginRepoFactory` const tx = await pluginRepoFactory.createPluginRepo( - PLUGIN_REPO_ENS_SUBDOMAIN_NAME, + subdomain, deployer.address ); @@ -69,8 +55,18 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { deployer ); + // Save the plugin repo deployment + await hre.deployments.save(PLUGIN_REPO_PROXY_NAME, { + abi: PluginRepo__factory.abi, + address: pluginRepo.address, + receipt: await tx.wait(), + transactionHash: tx.hash, + }); + console.log( - `PluginRepo '${pluginEnsDomain(hre)}' deployed at '${pluginRepo.address}'.` + `PluginRepo ${ + supportsENS ? 'with ens:' + pluginEnsDomain(hre) : 'without ens' + } deployed at '${pluginRepo.address}'.` ); hre.aragonToVerifyContracts.push({ @@ -90,11 +86,11 @@ func.skip = async (hre: HardhatRuntimeEnvironment) => { console.log(`\n🏗️ ${path.basename(__filename)}:`); // Check if the ens record exists already - const {pluginRepo, ensDomain} = await findPluginRepo(hre); + const {pluginRepo} = await findPluginRepo(hre); if (pluginRepo !== null) { console.log( - `ENS name '${ensDomain}' was claimed already at '${ + `Plugin Repo already deployed at '${ pluginRepo.address }' on network '${getProductionNetworkName(hre)}'. Skipping deployment...` ); @@ -106,7 +102,7 @@ func.skip = async (hre: HardhatRuntimeEnvironment) => { return true; } else { - console.log(`ENS name '${ensDomain}' is unclaimed. Deploying...`); + console.log('Deploying Plugin Repo'); return false; } diff --git a/packages/contracts/deploy/20_new_version/23_publish.ts b/packages/contracts/deploy/20_new_version/23_publish.ts index 56ad6b16..a3bb648b 100644 --- a/packages/contracts/deploy/20_new_version/23_publish.ts +++ b/packages/contracts/deploy/20_new_version/23_publish.ts @@ -11,8 +11,11 @@ import { impersonatedManagementDaoSigner, isLocal, pluginEnsDomain, + publishPlaceholderVersion, + isValidAddress, } from '../../utils/helpers'; import {PLUGIN_REPO_PERMISSIONS, uploadToPinata} from '@aragon/osx-commons-sdk'; +import {PluginRepo__factory} from '@aragon/osx-ethers'; import {writeFile} from 'fs/promises'; import {ethers} from 'hardhat'; import {DeployFunction} from 'hardhat-deploy/types'; @@ -36,14 +39,20 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { let buildMetadataURI = '0x'; if (!isLocal(hre)) { + if (!process.env.PUB_PINATA_JWT) { + throw Error('PUB_PINATA_JWT is not set'); + } + // Upload the metadata to IPFS releaseMetadataURI = await uploadToPinata( - JSON.stringify(METADATA.release, null, 2), - `${PLUGIN_REPO_ENS_SUBDOMAIN_NAME}-release-metadata` + METADATA.release, + `${PLUGIN_REPO_ENS_SUBDOMAIN_NAME}-release-metadata`, + process.env.PUB_PINATA_JWT ); buildMetadataURI = await uploadToPinata( - JSON.stringify(METADATA.build, null, 2), - `${PLUGIN_REPO_ENS_SUBDOMAIN_NAME}-build-metadata` + METADATA.build, + `${PLUGIN_REPO_ENS_SUBDOMAIN_NAME}-build-metadata`, + process.env.PUB_PINATA_JWT ); } @@ -73,18 +82,24 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Check build number const latestBuild = (await pluginRepo.buildCount(VERSION.release)).toNumber(); - if (VERSION.build < latestBuild) { - throw Error( - `Publishing with build number ${VERSION.build} is not possible. The latest build is ${latestBuild}. Aborting publication...` - ); - } - if (VERSION.build > latestBuild + 1) { - throw Error( - `Publishing with build number ${VERSION.build} is not possible. + if (latestBuild == 0 && VERSION.build > 1) { + // it means there's no build yet on the repo on the specific VERSION.release + // and build version in the plugin settings is > 1, meaning that + // it must push placeholder contracts and as the last one, push the actual plugin setup. + } else { + if (VERSION.build < latestBuild) { + throw Error( + `Publishing with build number ${VERSION.build} is not possible. The latest build is ${latestBuild}. Aborting publication...` + ); + } + if (VERSION.build > latestBuild + 1) { + throw Error( + `Publishing with build number ${VERSION.build} is not possible. The latest build is ${latestBuild} and the next release you can publish is release number ${ - latestBuild + 1 - }. Aborting publication...` - ); + latestBuild + 1 + }. Aborting publication...` + ); + } } if (setup == undefined || setup?.receipt == undefined) { @@ -114,6 +129,25 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { [] ) ) { + if (latestBuild == 0 && VERSION.build > 1) { + // We are publishing the first version as build > 1. + // So we need to publish placeholders first.. + const placeholderSetup = process.env.PLACEHOLDER_SETUP; + + if (!placeholderSetup || !isValidAddress(placeholderSetup)) { + throw new Error( + 'Aborting. Placeholder setup not defined in .env or is not a valid address (is not an address or is address zero)' + ); + } + await publishPlaceholderVersion( + placeholderSetup, + VERSION.build, + VERSION.release, + pluginRepo, + signer + ); + } + // Create the new version const tx = await pluginRepo .connect(signer) @@ -139,6 +173,25 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } else { // The deployer does not have `MAINTAINER_PERMISSION_ID` permission and we are not deploying to a production network, // so we write the data into a file for a management DAO member to create a proposal from it. + const pluginRepoInterface = PluginRepo__factory.createInterface(); + const versionData = { + _release: VERSION.release, + _pluginSetup: setup.address, + _buildMetadata: ethers.utils.hexlify( + ethers.utils.toUtf8Bytes(buildMetadataURI) + ), + _releaseMetadata: ethers.utils.hexlify( + ethers.utils.toUtf8Bytes(releaseMetadataURI) + ), + }; + + const calldata = pluginRepoInterface.encodeFunctionData('createVersion', [ + versionData._release, + versionData._pluginSetup, + versionData._buildMetadata, + versionData._releaseMetadata, + ]); + const data = { proposalTitle: `Publish '${PLUGIN_CONTRACT_NAME}' plugin v${VERSION.release}.${VERSION.build}`, proposalSummary: `Publishes v${VERSION.release}.${VERSION.build} of the '${PLUGIN_CONTRACT_NAME}' plugin in the '${ensDomain}' plugin repo.`, @@ -148,16 +201,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { actions: [ { to: pluginRepo.address, - createVersion: { - _release: VERSION.release, - _pluginSetup: setup.address, - _buildMetadata: ethers.utils.hexlify( - ethers.utils.toUtf8Bytes(buildMetadataURI) - ), - _releaseMetadata: ethers.utils.hexlify( - ethers.utils.toUtf8Bytes(releaseMetadataURI) - ), - }, + value: 0, + data: calldata, + createVersion: versionData, }, ], }; @@ -165,7 +211,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const path = `./createVersionProposalData-${hre.network.name}.json`; await writeFile(path, JSON.stringify(data, null, 2)); console.log( - `Saved data to '${path}'. Use this to create a proposal on the managing DAO calling the 'createVersion' function on the ${ensDomain} plugin repo deployed at ${pluginRepo.address}.` + `Saved data to '${path}'. Use this to create a proposal on the management DAO calling the 'createVersion' function on the ${ensDomain} plugin repo deployed at ${pluginRepo.address}.` ); } }; @@ -186,20 +232,24 @@ func.skip = async (hre: HardhatRuntimeEnvironment) => { throw `PluginRepo '${pluginEnsDomain(hre)}' does not exist yet.`; } - const pastVersions = await getPastVersionCreatedEvents(pluginRepo); + try { + const pastVersions = await getPastVersionCreatedEvents(pluginRepo); - // Check if the version was published already - const filteredLogs = pastVersions.filter( - items => - items.event.args.release === VERSION.release && - items.event.args.build === VERSION.build - ); - - if (filteredLogs.length !== 0) { - console.log( - `Build number ${VERSION.build} has already been published for release ${VERSION.release}. Skipping publication...` + // Check if the version was published already + const filteredLogs = pastVersions.filter( + items => + items.event.args.release === VERSION.release && + items.event.args.build === VERSION.build ); - return true; + + if (filteredLogs.length !== 0) { + console.log( + `Build number ${VERSION.build} has already been published for release ${VERSION.release}. Skipping publication...` + ); + return true; + } + } catch (error) { + console.log(`Error in geting previouse version ${error}.`); } return false; diff --git a/packages/contracts/deploy/30_upgrade_repo/_common.ts b/packages/contracts/deploy/30_upgrade_repo/_common.ts index 1070dcec..88fac792 100644 --- a/packages/contracts/deploy/30_upgrade_repo/_common.ts +++ b/packages/contracts/deploy/30_upgrade_repo/_common.ts @@ -1,8 +1,9 @@ -import {findPluginRepo, getProductionNetworkName} from '../../utils/helpers'; import { - getLatestNetworkDeployment, - getNetworkNameByAlias, -} from '@aragon/osx-commons-configs'; + findPluginRepo, + getProductionNetworkName, + getPluginRepoFactory, +} from '../../utils/helpers'; +import {getNetworkNameByAlias} from '@aragon/osx-commons-configs'; import {UnsupportedNetworkError} from '@aragon/osx-commons-sdk'; import {PluginRepo, PluginRepo__factory} from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -30,10 +31,6 @@ export async function fetchData( if (network === null) { throw new UnsupportedNetworkError(productionNetworkName); } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; - } // Get PluginRepo const {pluginRepo, ensDomain} = await findPluginRepo(hre); @@ -46,8 +43,10 @@ export async function fetchData( ); // Get the latest `PluginRepo` implementation as the upgrade target + const pluginRepoFactory = await getPluginRepoFactory(hre); + const latestPluginRepoImplementation = PluginRepo__factory.connect( - networkDeployments.PluginRepoBase.address, + await pluginRepoFactory.pluginRepoBase(), deployer ); diff --git a/packages/contracts/deploy/90_install_on_management_dao/90_install.ts b/packages/contracts/deploy/90_install_on_management_dao/90_install.ts new file mode 100644 index 00000000..c328a301 --- /dev/null +++ b/packages/contracts/deploy/90_install_on_management_dao/90_install.ts @@ -0,0 +1,300 @@ +import {VERSION, METADATA} from '../../plugin-settings'; +import { + getManagementDao, + isValidAddress, + findPluginRepo, + isPermissionSetCorrectly, +} from '../../utils/helpers'; +import { + getNamedTypesFromMetadata, + findEventTopicLog, +} from '@aragon/osx-commons-sdk'; +import { + DAO_PERMISSIONS, + Operation, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS, +} from '@aragon/osx-commons-sdk'; +import { + PluginSetupProcessor__factory, + PluginSetupProcessorEvents, + DAOStructs, +} from '@aragon/osx-ethers'; +import {defaultAbiCoder, keccak256} from 'ethers/lib/utils'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +/** + * Prints information about the used/forked network and initial deployer wallet balance. + * @param {HardhatRuntimeEnvironment} hre + */ +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {ethers} = hre; + + const [deployer] = await hre.ethers.getSigners(); + + const approvers = process.env.MANAGEMENT_DAO_MULTISIG_APPROVERS?.split( + ',' + ) || [deployer.address]; + const minApprovals = parseInt( + process.env.MANAGEMENT_DAO_MULTISIG_MIN_APPROVALS || '1' + ); + + const listedOnly = + process.env.MANAGEMENT_DAO_MULTISIG_LISTED_ONLY === 'false' ? false : true; + + // Get `managementDAO` address. + const managementDAO = await getManagementDao(hre); + + // Get `PluginSetupProcessor` from env vars or commons config deployment + const pspAddress = process.env.PLUGIN_SETUP_PROCESSOR_ADDRESS; + + if (!pspAddress || !isValidAddress(pspAddress)) { + throw new Error( + 'PluginSetupProcessor address in .env is not defined or is not a valid address (is not an address or is address zero)' + ); + } + + // Get `PluginSetupProcessor` contract. + const pspContract = PluginSetupProcessor__factory.connect( + pspAddress, + deployer + ); + + // Install latest multisig version + const {pluginRepo: multisigRepo} = await findPluginRepo(hre); + if (!multisigRepo) { + throw new Error('Multisig repo not found'); + } + + if (!multisigRepo.address) { + throw new Error('Multisig repo address not found'); + } + + const pluginSetupRef = { + pluginSetupRepo: multisigRepo.address, + versionTag: VERSION, + }; + + // Prepare multisig plugin for managementDAO + const params = getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareInstallation.inputs + ); + + const data = ethers.utils.defaultAbiCoder.encode(params, [ + approvers, + [listedOnly, minApprovals], + [ethers.constants.AddressZero, 0], // [target, operation] + '0x', // metadata + ]); + + const prepareTx = await pspContract.prepareInstallation( + managementDAO.address, + { + pluginSetupRef, + data, + } + ); + + // extract info from prepare event + const event = + await findEventTopicLog( + await prepareTx.wait(), + PluginSetupProcessor__factory.createInterface(), + 'InstallationPrepared' + ); + + const installationPreparedEvent = event.args; + + console.log( + `Prepared (Multisig: ${installationPreparedEvent.plugin} version (release: ${VERSION.release} / build: ${VERSION.build}) to be applied on (ManagementDAO: ${managementDAO.address}), see (tx: ${prepareTx.hash})` + ); + + // grant + // ROOT_PERMISSION on the management dao to the PSP + // APPLY_INSTALLATION_PERMISSION on the PSP to the deployer + const permissionsToGrant: DAOStructs.MultiTargetPermissionStruct[] = [ + { + operation: Operation.Grant, + where: managementDAO.address, + who: pspAddress, + condition: ethers.constants.AddressZero, + permissionId: DAO_PERMISSIONS.ROOT_PERMISSION_ID, + }, + { + operation: Operation.Grant, + where: pspAddress, + who: deployer.address, + condition: ethers.constants.AddressZero, + permissionId: + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID, + }, + ]; + + const applyPermissionTx = await managementDAO.applyMultiTargetPermissions( + permissionsToGrant + ); + await applyPermissionTx.wait(); + + // Apply multisig plugin to the managementDAO + const applyTx = await pspContract.applyInstallation(managementDAO.address, { + helpersHash: hashHelpers( + installationPreparedEvent.preparedSetupData.helpers + ), + permissions: installationPreparedEvent.preparedSetupData.permissions, + plugin: installationPreparedEvent.plugin, + pluginSetupRef, + }); + await applyTx.wait(); + + const multisigPluginPermission = { + operation: Operation.Grant, + where: {name: 'ManagementDAO', address: managementDAO.address}, + who: {name: 'Multisig plugin', address: installationPreparedEvent.plugin}, + permission: 'EXECUTE_PERMISSION', + }; + + const isPermissionCorrect = await isPermissionSetCorrectly( + managementDAO, + multisigPluginPermission + ); + + if (!isPermissionCorrect) { + const {who, where, operation} = multisigPluginPermission; + if (operation === Operation.Grant) { + throw new Error( + `(${who.name}: ${who.address}) doesn't have ${multisigPluginPermission.permission} on (${where.name}: ${where.address}) in ${managementDAO.address}` + ); + } + throw new Error( + `(${who.name}: ${who.address}) has ${multisigPluginPermission.permission} on (${where.name}: ${where.address}) in ${managementDAO.address}` + ); + } + + console.log( + `Applied (Multisig: ${installationPreparedEvent.plugin}) on (ManagementDAO: ${managementDAO.address}), see (tx: ${applyTx.hash})` + ); + + // revoke + // ROOT_PERMISSION permission on the management dao from deployer + // ROOT_PERMISSION permission on the management dao from psp + // APPLY_INSTALLATION_PERMISSION permission on the PSP from deployer + const permissionsToRevoke: DAOStructs.MultiTargetPermissionStruct[] = [ + { + operation: Operation.Revoke, + where: managementDAO.address, + who: pspAddress, + condition: ethers.constants.AddressZero, + permissionId: DAO_PERMISSIONS.ROOT_PERMISSION_ID, + }, + { + operation: Operation.Revoke, + where: pspAddress, + who: deployer.address, + condition: ethers.constants.AddressZero, + permissionId: + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID, + }, + { + operation: Operation.Revoke, + where: managementDAO.address, + who: deployer.address, + condition: ethers.constants.AddressZero, + permissionId: DAO_PERMISSIONS.ROOT_PERMISSION_ID, + }, + ]; + + const revokePermissionsTx = await managementDAO.applyMultiTargetPermissions( + permissionsToRevoke + ); + await revokePermissionsTx.wait(); + + console.log('Permissions revoked....'); + + // check if the permissions are revoked correctly + for (const permission of permissionsToRevoke) { + const hasPermission = await managementDAO.hasPermission( + permission.where, + permission.who, + permission.permissionId, + '0x' + ); + if (hasPermission) { + throw new Error( + `Permission ${permission.permissionId} not revoked for ${permission.who} on ${permission.where}` + ); + } + } +}; + +export default func; +func.tags = ['InstallOnManagementDao']; + +export function hashHelpers(helpers: string[]) { + return keccak256(defaultAbiCoder.encode(['address[]'], [helpers])); +} + +/** + * Skips installation if is local network or env vars needed are not defined + * @param {HardhatRuntimeEnvironment} hre + */ +func.skip = async () => { + console.log(`\n✨ Install on Management DAO:`); + + if ( + !('MANAGEMENT_DAO_MULTISIG_LISTED_ONLY' in process.env) || + !('MANAGEMENT_DAO_MULTISIG_MIN_APPROVALS' in process.env) || + !('MANAGEMENT_DAO_MULTISIG_APPROVERS' in process.env) + ) { + console.log(`Needed env vars not set, skipping installation...`); + return true; + } else { + return !areEnvVarsValid( + process.env.MANAGEMENT_DAO_MULTISIG_LISTED_ONLY!, + process.env.MANAGEMENT_DAO_MULTISIG_MIN_APPROVALS!, + process.env.MANAGEMENT_DAO_MULTISIG_APPROVERS!.split(',') + ); + } +}; + +function areEnvVarsValid( + listedOnly: string, + minApprovals: string, + approvers: string[] +) { + // Validate LISTED_ONLY is boolean + if (listedOnly !== 'true' && listedOnly !== 'false') { + console.log( + `MANAGEMENT_DAO_MULTISIG_LISTED_ONLY must be 'true' or 'false'` + ); + return false; + } + + // Validate MIN_APPROVALS is a valid number + + if (isNaN(parseInt(minApprovals)) || parseInt(minApprovals) < 1) { + console.log( + `MANAGEMENT_DAO_MULTISIG_MIN_APPROVALS must be a positive number` + ); + return false; + } + + // Validate APPROVERS is a non-empty list + if (!approvers || approvers.length === 0) { + console.log( + `MANAGEMENT_DAO_MULTISIG_APPROVERS must contain at least one valid address` + ); + return false; + } else { + for (const approver of approvers) { + if (!isValidAddress(approver)) { + console.log( + `${approver} in MANAGEMENT_DAO_MULTISIG_APPROVERS is not a valid address` + ); + return false; + } + } + } + + console.log('All env vars set properly'); + return true; +} diff --git a/packages/contracts/docs/antora.yml b/packages/contracts/docs/antora.yml new file mode 100644 index 00000000..a6b8f2a9 --- /dev/null +++ b/packages/contracts/docs/antora.yml @@ -0,0 +1,7 @@ +name: multisig +title: '@aragon/multisig' +version: 1.x +prerelease: false +nav: + - modules/ROOT/nav.adoc + - modules/api/nav.adoc diff --git a/packages/contracts/docs/config.js b/packages/contracts/docs/config.js new file mode 100644 index 00000000..5a241920 --- /dev/null +++ b/packages/contracts/docs/config.js @@ -0,0 +1,22 @@ +const path = require('path'); +const fs = require('fs'); + +const {version, repository} = require('../package.json'); + +const helpers = require(path.resolve(__dirname, './templates/helpers')); + +// overwrite the functions. +helpers.version = () => `${version}/packages/contracts`; +helpers.githubURI = () => repository.url; + +/** @type import('solidity-docgen/dist/config').UserConfig */ +module.exports = { + outputDir: 'docs/modules/api/pages', + templates: 'docs/templates', + exclude: ['mocks', 'test'], + pageExtension: '.adoc', + collapseNewlines: true, + pages: (_, file, config) => { + return 'Multisig' + config.pageExtension; + }, +}; diff --git a/packages/contracts/docs/modules/ROOT/nav.adoc b/packages/contracts/docs/modules/ROOT/nav.adoc new file mode 100644 index 00000000..59a7257c --- /dev/null +++ b/packages/contracts/docs/modules/ROOT/nav.adoc @@ -0,0 +1 @@ +* xref:index.adoc[Overview] \ No newline at end of file diff --git a/packages/contracts/docs/modules/ROOT/pages/index.adoc b/packages/contracts/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 00000000..fec2277f --- /dev/null +++ b/packages/contracts/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,151 @@ += Multisig + +== Description + +**Multisig** is a governance plugin developed and maintained by the Aragon core team. +It allows users to create proposals that require a configurable subset of approvals (e.g., *x out of y*) from a list of approvers to pass. +Once a proposal meets the required threshold of approvals, the list of proposed actions can be passed to the associated DAO or executor for execution. + +Each approver can individually cast their approval directly on-chain, +unlike traditional multisig solutions that rely on off-chain aggregation of signatures. + + +== Proposal Lifecycle + +=== **Creation**: + +Proposals can be created by users who have the `CREATE_PROPOSAL_PERMISSION` or those verified through the `ListedCheckCondition`, depending on the plugin’s configuration and how the permission is granted. The creation process includes: + +** Taking a snapshot block (`block.number - 1`) at the time of creation to ensure consistent eligibility for approvers. +** Instantiating a proposal and recording proposal metadata, a list of actions, start and end dates, and approval thresholds based on the snapshot block. +** Optionally, the proposal creator can immediately cast their approval during the creation process. + +[NOTE] +==== +**Proposal creation settings**: +- onlyListed: Whether only listed addresses can create a proposal or not. +==== + +=== **Approval**: + +Approvers listed in the address list can cast their approval. The process includes: + +** Verifying that the proposal is open, the approver is eligible based on the snapshot taken at creation, and the approver hasn’t already approved. +** Ensuring the proposal has not expired; proposals are considered expired and ineligible for further approval once their end date has passed. +** Updating the count of approvals and marking the approver as having approved. +** Optionally, the approver can pass `true` for `tryExecution` along with their approval. This prompts the plugin to attempt execution if the proposal meets the necessary conditions. + +=== **Execution**: + +Once the proposal meets the required number of approvals: + +** The `execute` function can be called by users with the `EXECUTE_PROPOSAL_PERMISSION`. +[NOTE] +==== +Typically, this permission is set to any address, allowing anyone to execute, but it can also be restricted to specific users or conditions if needed. +==== + +** The plugin verifies that the proposal is open, the approval threshold has been met, and then executes the listed actions by passing them to the associated DAO or executor. +** After execution, the proposal is marked as executed, preventing further approvals or execution attempts. + +[NOTE] +==== +**Proposal execution settings**: +- minApprovals: The minimal number of approvals required for a proposal to pass. +==== + +== Plugin Settings + +The **Multisig** plugin provides three key configuration functions to manage governance settings and approvers. All three functions require the `UPDATE_MULTISIG_SETTINGS_PERMISSION`, which is typically granted to the associated DAO. + +=== 1. Update Multisig Settings + +Modifies the plugin’s configuration, which includes: **`onlyListed` and `minApprovals`**. + +* **Constraints**: + - `minApprovals` must be at least 1 and no greater than the current number of approvers. + +* **Impact**: + - Changes apply to all proposals created after the settings are updated. + +=== 2. Add Addresses + +Adds new addresses to the list of approvers, allowing them to approve new proposal. + +* **Constraints**: + - The total number of addresses cannot exceed `2^16 - 1` (to ensure compatibility with `uint16`, the maximal number of approvals). + +* **Impact**: + - Newly added addresses can approve proposals created after their addition. + - Reduces the relative weight of approvals required, as the minimum approval threshold remains the same while the total number of approvers increases. + +=== 3. Remove Addresses + +Removes existing addresses from the list of approvers, preventing them from approving new proposals. + +* **Constraints**: + - After removal, the number of remaining addresses must not be less than the current minimum approvals threshold (`minApprovals`). + +* **Impact**: + - Removed addresses lose their approval rights for all proposals created after their removal. + - Increases the relative weight of approvals required, as the minimum approval threshold remains the same while the total number of approvers decreases. + +[IMPORTANT] +==== +Since each function contains its own independent logic checks, if it is necessary to reduce the number of approvers to below the current `minApproval` threshold, the proposal for making this change should include at least two actions in the following exact order: + +1. Update the multisig settings to lower the `minApproval` value. +2. Remove the addresses from the approvers list. + +This ensures that the operation is valid and does not violate the plugin’s constraints. +==== + +== Plugin Setup + +* **Contracts**: The **MultisigSetup** contains the **Multisig** plugin’s implementation and automatically deploys and prepares the following contracts: +** **Multisig Proxy**: The ERC1967Proxy contract pointing to the Multisig plugin’s implementation. +** **ListedCheckCondition**: A condition contract used to determine whether a user meets the eligibility criteria for creating proposals. It enforces the `onlyListed` setting from the Multisig plugin, ensuring that only listed members can propose actions if the setting is enabled. + +* **Permissions**: The **MultisigSetup** establishes the following default permissions to ensure smooth operation and integration with the associated DAO: + +[cols="2,2,2,2,2", options="header"] +|=== +| Permission ID | Where (Granted By) | Who (Granted To) | Condition | Functions + +| `EXECUTE_PERMISSION_ID` +| DAO +| Plugin +| None +| `execute` + +| `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` +| Plugin +| DAO +| None +| `addAddresses`, `removeAddresses`, `updateMultisigSettings` + +| `CREATE_PROPOSAL_PERMISSION_ID` +| Plugin +| Any Address +| `ListedCheckCondition` +| `createProposal` + +| `SET_TARGET_CONFIG_PERMISSION_ID` +| Plugin +| DAO +| None +| `setTargetConfig` + +| `SET_METADATA_PERMISSION_ID` +| Plugin +| DAO +| None +| `setMetadata` + +| `EXECUTE_PROPOSAL_PERMISSION_ID` +| Plugin +| Any Address +| None +| `execute` +|=== + +This setup ensures that the **Multisig** plugin is ready for operation immediately after installation, with all required contracts deployed and permissions configured. diff --git a/packages/contracts/docs/modules/api/nav.adoc b/packages/contracts/docs/modules/api/nav.adoc new file mode 100644 index 00000000..9bb30309 --- /dev/null +++ b/packages/contracts/docs/modules/api/nav.adoc @@ -0,0 +1,2 @@ +.API +* xref:Multisig.adoc[Multisig] diff --git a/packages/contracts/docs/modules/api/pages/Multisig.adoc b/packages/contracts/docs/modules/api/pages/Multisig.adoc new file mode 100644 index 00000000..b1315c37 --- /dev/null +++ b/packages/contracts/docs/modules/api/pages/Multisig.adoc @@ -0,0 +1,861 @@ +:github-icon: pass:[] +:xref-Multisig-initialize-contract-IDAO-address---struct-Multisig-MultisigSettings-struct-IPlugin-TargetConfig-bytes-: xref:Multisig.adoc#Multisig-initialize-contract-IDAO-address---struct-Multisig-MultisigSettings-struct-IPlugin-TargetConfig-bytes- +:xref-Multisig-initializeFrom-uint16-bytes-: xref:Multisig.adoc#Multisig-initializeFrom-uint16-bytes- +:xref-Multisig-supportsInterface-bytes4-: xref:Multisig.adoc#Multisig-supportsInterface-bytes4- +:xref-Multisig-addAddresses-address---: xref:Multisig.adoc#Multisig-addAddresses-address--- +:xref-Multisig-removeAddresses-address---: xref:Multisig.adoc#Multisig-removeAddresses-address--- +:xref-Multisig-updateMultisigSettings-struct-Multisig-MultisigSettings-: xref:Multisig.adoc#Multisig-updateMultisigSettings-struct-Multisig-MultisigSettings- +:xref-Multisig-createProposal-bytes-struct-Action---uint256-bool-bool-uint64-uint64-: xref:Multisig.adoc#Multisig-createProposal-bytes-struct-Action---uint256-bool-bool-uint64-uint64- +:xref-Multisig-createProposal-bytes-struct-Action---uint64-uint64-bytes-: xref:Multisig.adoc#Multisig-createProposal-bytes-struct-Action---uint64-uint64-bytes- +:xref-Multisig-customProposalParamsABI--: xref:Multisig.adoc#Multisig-customProposalParamsABI-- +:xref-Multisig-approve-uint256-bool-: xref:Multisig.adoc#Multisig-approve-uint256-bool- +:xref-Multisig-canApprove-uint256-address-: xref:Multisig.adoc#Multisig-canApprove-uint256-address- +:xref-Multisig-canExecute-uint256-: xref:Multisig.adoc#Multisig-canExecute-uint256- +:xref-Multisig-hasSucceeded-uint256-: xref:Multisig.adoc#Multisig-hasSucceeded-uint256- +:xref-Multisig-getProposal-uint256-: xref:Multisig.adoc#Multisig-getProposal-uint256- +:xref-Multisig-hasApproved-uint256-address-: xref:Multisig.adoc#Multisig-hasApproved-uint256-address- +:xref-Multisig-execute-uint256-: xref:Multisig.adoc#Multisig-execute-uint256- +:xref-Multisig-isMember-address-: xref:Multisig.adoc#Multisig-isMember-address- +:xref-Multisig-_execute-uint256-: xref:Multisig.adoc#Multisig-_execute-uint256- +:xref-Multisig-_canApprove-uint256-address-: xref:Multisig.adoc#Multisig-_canApprove-uint256-address- +:xref-Multisig-_canExecute-uint256-: xref:Multisig.adoc#Multisig-_canExecute-uint256- +:xref-Multisig-_isProposalOpen-struct-Multisig-Proposal-: xref:Multisig.adoc#Multisig-_isProposalOpen-struct-Multisig-Proposal- +:xref-Multisig-_updateMultisigSettings-struct-Multisig-MultisigSettings-: xref:Multisig.adoc#Multisig-_updateMultisigSettings-struct-Multisig-MultisigSettings- +:xref-Multisig-UPDATE_MULTISIG_SETTINGS_PERMISSION_ID-bytes32: xref:Multisig.adoc#Multisig-UPDATE_MULTISIG_SETTINGS_PERMISSION_ID-bytes32 +:xref-Multisig-CREATE_PROPOSAL_PERMISSION_ID-bytes32: xref:Multisig.adoc#Multisig-CREATE_PROPOSAL_PERMISSION_ID-bytes32 +:xref-Multisig-EXECUTE_PROPOSAL_PERMISSION_ID-bytes32: xref:Multisig.adoc#Multisig-EXECUTE_PROPOSAL_PERMISSION_ID-bytes32 +:xref-Multisig-multisigSettings-struct-Multisig-MultisigSettings: xref:Multisig.adoc#Multisig-multisigSettings-struct-Multisig-MultisigSettings +:xref-Multisig-lastMultisigSettingsChange-uint64: xref:Multisig.adoc#Multisig-lastMultisigSettingsChange-uint64 +:xref-Multisig-Approved-uint256-address-: xref:Multisig.adoc#Multisig-Approved-uint256-address- +:xref-Multisig-MultisigSettingsUpdated-bool-uint16-: xref:Multisig.adoc#Multisig-MultisigSettingsUpdated-bool-uint16- +:xref-Multisig-ProposalCreationForbidden-address-: xref:Multisig.adoc#Multisig-ProposalCreationForbidden-address- +:xref-Multisig-NonexistentProposal-uint256-: xref:Multisig.adoc#Multisig-NonexistentProposal-uint256- +:xref-Multisig-ApprovalCastForbidden-uint256-address-: xref:Multisig.adoc#Multisig-ApprovalCastForbidden-uint256-address- +:xref-Multisig-ProposalExecutionForbidden-uint256-: xref:Multisig.adoc#Multisig-ProposalExecutionForbidden-uint256- +:xref-Multisig-MinApprovalsOutOfBounds-uint16-uint16-: xref:Multisig.adoc#Multisig-MinApprovalsOutOfBounds-uint16-uint16- +:xref-Multisig-AddresslistLengthOutOfBounds-uint16-uint256-: xref:Multisig.adoc#Multisig-AddresslistLengthOutOfBounds-uint16-uint256- +:xref-Multisig-ProposalAlreadyExists-uint256-: xref:Multisig.adoc#Multisig-ProposalAlreadyExists-uint256- +:xref-Multisig-DateOutOfBounds-uint64-uint64-: xref:Multisig.adoc#Multisig-DateOutOfBounds-uint64-uint64- +:xref-Multisig-MULTISIG_INTERFACE_ID-bytes4: xref:Multisig.adoc#Multisig-MULTISIG_INTERFACE_ID-bytes4 +:xref-Multisig-proposals-mapping-uint256----struct-Multisig-Proposal-: xref:Multisig.adoc#Multisig-proposals-mapping-uint256----struct-Multisig-Proposal- +:xref-MultisigSetup-constructor--: xref:Multisig.adoc#MultisigSetup-constructor-- +:xref-MultisigSetup-prepareInstallation-address-bytes-: xref:Multisig.adoc#MultisigSetup-prepareInstallation-address-bytes- +:xref-MultisigSetup-prepareUpdate-address-uint16-struct-IPluginSetup-SetupPayload-: xref:Multisig.adoc#MultisigSetup-prepareUpdate-address-uint16-struct-IPluginSetup-SetupPayload- +:xref-MultisigSetup-prepareUninstallation-address-struct-IPluginSetup-SetupPayload-: xref:Multisig.adoc#MultisigSetup-prepareUninstallation-address-struct-IPluginSetup-SetupPayload- +:xref-ListedCheckCondition-constructor-address-: xref:Multisig.adoc#ListedCheckCondition-constructor-address- +:xref-ListedCheckCondition-isGranted-address-address-bytes32-bytes-: xref:Multisig.adoc#ListedCheckCondition-isGranted-address-address-bytes32-bytes- += Multisig API + +== Core + +:Proposal: pass:normal[xref:#Multisig-Proposal[`++Proposal++`]] +:ProposalParameters: pass:normal[xref:#Multisig-ProposalParameters[`++ProposalParameters++`]] +:MultisigSettings: pass:normal[xref:#Multisig-MultisigSettings[`++MultisigSettings++`]] +:MULTISIG_INTERFACE_ID: pass:normal[xref:#Multisig-MULTISIG_INTERFACE_ID-bytes4[`++MULTISIG_INTERFACE_ID++`]] +:UPDATE_MULTISIG_SETTINGS_PERMISSION_ID: pass:normal[xref:#Multisig-UPDATE_MULTISIG_SETTINGS_PERMISSION_ID-bytes32[`++UPDATE_MULTISIG_SETTINGS_PERMISSION_ID++`]] +:CREATE_PROPOSAL_PERMISSION_ID: pass:normal[xref:#Multisig-CREATE_PROPOSAL_PERMISSION_ID-bytes32[`++CREATE_PROPOSAL_PERMISSION_ID++`]] +:EXECUTE_PROPOSAL_PERMISSION_ID: pass:normal[xref:#Multisig-EXECUTE_PROPOSAL_PERMISSION_ID-bytes32[`++EXECUTE_PROPOSAL_PERMISSION_ID++`]] +:proposals: pass:normal[xref:#Multisig-proposals-mapping-uint256----struct-Multisig-Proposal-[`++proposals++`]] +:multisigSettings: pass:normal[xref:#Multisig-multisigSettings-struct-Multisig-MultisigSettings[`++multisigSettings++`]] +:lastMultisigSettingsChange: pass:normal[xref:#Multisig-lastMultisigSettingsChange-uint64[`++lastMultisigSettingsChange++`]] +:ProposalCreationForbidden: pass:normal[xref:#Multisig-ProposalCreationForbidden-address-[`++ProposalCreationForbidden++`]] +:NonexistentProposal: pass:normal[xref:#Multisig-NonexistentProposal-uint256-[`++NonexistentProposal++`]] +:ApprovalCastForbidden: pass:normal[xref:#Multisig-ApprovalCastForbidden-uint256-address-[`++ApprovalCastForbidden++`]] +:ProposalExecutionForbidden: pass:normal[xref:#Multisig-ProposalExecutionForbidden-uint256-[`++ProposalExecutionForbidden++`]] +:MinApprovalsOutOfBounds: pass:normal[xref:#Multisig-MinApprovalsOutOfBounds-uint16-uint16-[`++MinApprovalsOutOfBounds++`]] +:AddresslistLengthOutOfBounds: pass:normal[xref:#Multisig-AddresslistLengthOutOfBounds-uint16-uint256-[`++AddresslistLengthOutOfBounds++`]] +:ProposalAlreadyExists: pass:normal[xref:#Multisig-ProposalAlreadyExists-uint256-[`++ProposalAlreadyExists++`]] +:DateOutOfBounds: pass:normal[xref:#Multisig-DateOutOfBounds-uint64-uint64-[`++DateOutOfBounds++`]] +:Approved: pass:normal[xref:#Multisig-Approved-uint256-address-[`++Approved++`]] +:MultisigSettingsUpdated: pass:normal[xref:#Multisig-MultisigSettingsUpdated-bool-uint16-[`++MultisigSettingsUpdated++`]] +:initialize: pass:normal[xref:#Multisig-initialize-contract-IDAO-address---struct-Multisig-MultisigSettings-struct-IPlugin-TargetConfig-bytes-[`++initialize++`]] +:initializeFrom: pass:normal[xref:#Multisig-initializeFrom-uint16-bytes-[`++initializeFrom++`]] +:supportsInterface: pass:normal[xref:#Multisig-supportsInterface-bytes4-[`++supportsInterface++`]] +:addAddresses: pass:normal[xref:#Multisig-addAddresses-address---[`++addAddresses++`]] +:removeAddresses: pass:normal[xref:#Multisig-removeAddresses-address---[`++removeAddresses++`]] +:updateMultisigSettings: pass:normal[xref:#Multisig-updateMultisigSettings-struct-Multisig-MultisigSettings-[`++updateMultisigSettings++`]] +:createProposal: pass:normal[xref:#Multisig-createProposal-bytes-struct-Action---uint256-bool-bool-uint64-uint64-[`++createProposal++`]] +:createProposal: pass:normal[xref:#Multisig-createProposal-bytes-struct-Action---uint64-uint64-bytes-[`++createProposal++`]] +:customProposalParamsABI: pass:normal[xref:#Multisig-customProposalParamsABI--[`++customProposalParamsABI++`]] +:approve: pass:normal[xref:#Multisig-approve-uint256-bool-[`++approve++`]] +:canApprove: pass:normal[xref:#Multisig-canApprove-uint256-address-[`++canApprove++`]] +:canExecute: pass:normal[xref:#Multisig-canExecute-uint256-[`++canExecute++`]] +:hasSucceeded: pass:normal[xref:#Multisig-hasSucceeded-uint256-[`++hasSucceeded++`]] +:getProposal: pass:normal[xref:#Multisig-getProposal-uint256-[`++getProposal++`]] +:hasApproved: pass:normal[xref:#Multisig-hasApproved-uint256-address-[`++hasApproved++`]] +:execute: pass:normal[xref:#Multisig-execute-uint256-[`++execute++`]] +:isMember: pass:normal[xref:#Multisig-isMember-address-[`++isMember++`]] +:_execute: pass:normal[xref:#Multisig-_execute-uint256-[`++_execute++`]] +:_canApprove: pass:normal[xref:#Multisig-_canApprove-uint256-address-[`++_canApprove++`]] +:_canExecute: pass:normal[xref:#Multisig-_canExecute-uint256-[`++_canExecute++`]] +:_isProposalOpen: pass:normal[xref:#Multisig-_isProposalOpen-struct-Multisig-Proposal-[`++_isProposalOpen++`]] +:_updateMultisigSettings: pass:normal[xref:#Multisig-_updateMultisigSettings-struct-Multisig-MultisigSettings-[`++_updateMultisigSettings++`]] + +[.contract] +[[Multisig]] +=== `++Multisig++` link:https://github.com/aragon/multisig-plugin/blob/v1.3/packages/contracts/src/Multisig.sol[{github-icon},role=heading-link] + +v1.3 (Release 1, Build 3). For each upgrade, if the reinitialization step is required, + increment the version numbers in the modifier for both the initialize and initializeFrom functions. + +[.contract-index] +.Functions +-- +* {xref-Multisig-initialize-contract-IDAO-address---struct-Multisig-MultisigSettings-struct-IPlugin-TargetConfig-bytes-}[`++initialize(_dao, _members, _multisigSettings, _targetConfig, _pluginMetadata)++`] +* {xref-Multisig-initializeFrom-uint16-bytes-}[`++initializeFrom(_fromBuild, _initData)++`] +* {xref-Multisig-supportsInterface-bytes4-}[`++supportsInterface(_interfaceId)++`] +* {xref-Multisig-addAddresses-address---}[`++addAddresses(_members)++`] +* {xref-Multisig-removeAddresses-address---}[`++removeAddresses(_members)++`] +* {xref-Multisig-updateMultisigSettings-struct-Multisig-MultisigSettings-}[`++updateMultisigSettings(_multisigSettings)++`] +* {xref-Multisig-createProposal-bytes-struct-Action---uint256-bool-bool-uint64-uint64-}[`++createProposal(_metadata, _actions, _allowFailureMap, _approveProposal, _tryExecution, _startDate, _endDate)++`] +* {xref-Multisig-createProposal-bytes-struct-Action---uint64-uint64-bytes-}[`++createProposal(_metadata, _actions, _startDate, _endDate, _data)++`] +* {xref-Multisig-customProposalParamsABI--}[`++customProposalParamsABI()++`] +* {xref-Multisig-approve-uint256-bool-}[`++approve(_proposalId, _tryExecution)++`] +* {xref-Multisig-canApprove-uint256-address-}[`++canApprove(_proposalId, _account)++`] +* {xref-Multisig-canExecute-uint256-}[`++canExecute(_proposalId)++`] +* {xref-Multisig-hasSucceeded-uint256-}[`++hasSucceeded(_proposalId)++`] +* {xref-Multisig-getProposal-uint256-}[`++getProposal(_proposalId)++`] +* {xref-Multisig-hasApproved-uint256-address-}[`++hasApproved(_proposalId, _account)++`] +* {xref-Multisig-execute-uint256-}[`++execute(_proposalId)++`] +* {xref-Multisig-isMember-address-}[`++isMember(_account)++`] +* {xref-Multisig-_execute-uint256-}[`++_execute(_proposalId)++`] +* {xref-Multisig-_canApprove-uint256-address-}[`++_canApprove(_proposalId, _account)++`] +* {xref-Multisig-_canExecute-uint256-}[`++_canExecute(_proposalId)++`] +* {xref-Multisig-_isProposalOpen-struct-Multisig-Proposal-}[`++_isProposalOpen(proposal_)++`] +* {xref-Multisig-_updateMultisigSettings-struct-Multisig-MultisigSettings-}[`++_updateMultisigSettings(_multisigSettings)++`] +* {xref-Multisig-UPDATE_MULTISIG_SETTINGS_PERMISSION_ID-bytes32}[`++UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()++`] +* {xref-Multisig-CREATE_PROPOSAL_PERMISSION_ID-bytes32}[`++CREATE_PROPOSAL_PERMISSION_ID()++`] +* {xref-Multisig-EXECUTE_PROPOSAL_PERMISSION_ID-bytes32}[`++EXECUTE_PROPOSAL_PERMISSION_ID()++`] +* {xref-Multisig-multisigSettings-struct-Multisig-MultisigSettings}[`++multisigSettings()++`] +* {xref-Multisig-lastMultisigSettingsChange-uint64}[`++lastMultisigSettingsChange()++`] + +[.contract-subindex-inherited] +.Addresslist +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/governance/Addresslist.sol[isListedAtBlock] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/governance/Addresslist.sol[isListed] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/governance/Addresslist.sol[addresslistLengthAtBlock] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/governance/Addresslist.sol[addresslistLength] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/governance/Addresslist.sol[_addAddresses] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/governance/Addresslist.sol[_removeAddresses] + +[.contract-subindex-inherited] +.ProposalUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol[proposalCount] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol[_createProposalId] + +[.contract-subindex-inherited] +.PluginUUPSUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[pluginType] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[getCurrentTargetConfig] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[getTargetConfig] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[__PluginUUPSUpgradeable_init] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[setTargetConfig] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[implementation] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[_setTargetConfig] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[_execute] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[_execute] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[_authorizeUpgrade] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[SET_TARGET_CONFIG_PERMISSION_ID] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[UPGRADE_PLUGIN_PERMISSION_ID] + +[.contract-subindex-inherited] +.ProtocolVersion +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/versioning/ProtocolVersion.sol[protocolVersion] + +[.contract-subindex-inherited] +.IProtocolVersion + +[.contract-subindex-inherited] +.MetadataExtensionUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/metadata/MetadataExtensionUpgradeable.sol[setMetadata] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/metadata/MetadataExtensionUpgradeable.sol[getMetadata] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/metadata/MetadataExtensionUpgradeable.sol[_setMetadata] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/metadata/MetadataExtensionUpgradeable.sol[SET_METADATA_PERMISSION_ID] + +[.contract-subindex-inherited] +.DaoAuthorizableUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/permission/auth/DaoAuthorizableUpgradeable.sol[__DaoAuthorizableUpgradeable_init] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/permission/auth/DaoAuthorizableUpgradeable.sol[dao] + +[.contract-subindex-inherited] +.ContextUpgradeable + +[.contract-subindex-inherited] +.UUPSUpgradeable + +[.contract-subindex-inherited] +.ERC1967UpgradeUpgradeable + +[.contract-subindex-inherited] +.IERC1967Upgradeable + +[.contract-subindex-inherited] +.IERC1822ProxiableUpgradeable + +[.contract-subindex-inherited] +.ERC165Upgradeable + +[.contract-subindex-inherited] +.IERC165Upgradeable + +[.contract-subindex-inherited] +.Initializable + +[.contract-subindex-inherited] +.IProposal + +[.contract-subindex-inherited] +.IPlugin + +[.contract-subindex-inherited] +.IMembership + +[.contract-subindex-inherited] +.IMultisig + +-- + +[.contract-index] +.Events +-- +* {xref-Multisig-Approved-uint256-address-}[`++Approved(proposalId, approver)++`] +* {xref-Multisig-MultisigSettingsUpdated-bool-uint16-}[`++MultisigSettingsUpdated(onlyListed, minApprovals)++`] + +[.contract-subindex-inherited] +.Addresslist + +[.contract-subindex-inherited] +.ProposalUpgradeable + +[.contract-subindex-inherited] +.PluginUUPSUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[TargetSet] + +[.contract-subindex-inherited] +.ProtocolVersion + +[.contract-subindex-inherited] +.IProtocolVersion + +[.contract-subindex-inherited] +.MetadataExtensionUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/metadata/MetadataExtensionUpgradeable.sol[MetadataSet] + +[.contract-subindex-inherited] +.DaoAuthorizableUpgradeable + +[.contract-subindex-inherited] +.ContextUpgradeable + +[.contract-subindex-inherited] +.UUPSUpgradeable + +[.contract-subindex-inherited] +.ERC1967UpgradeUpgradeable + +[.contract-subindex-inherited] +.IERC1967Upgradeable + +[.contract-subindex-inherited] +.IERC1822ProxiableUpgradeable + +[.contract-subindex-inherited] +.ERC165Upgradeable + +[.contract-subindex-inherited] +.IERC165Upgradeable + +[.contract-subindex-inherited] +.Initializable + +[.contract-subindex-inherited] +.IProposal +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/proposal/IProposal.sol[ProposalCreated] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/proposal/IProposal.sol[ProposalExecuted] + +[.contract-subindex-inherited] +.IPlugin + +[.contract-subindex-inherited] +.IMembership +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/membership/IMembership.sol[MembersAdded] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/membership/IMembership.sol[MembersRemoved] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/membership/IMembership.sol[MembershipContractAnnounced] + +[.contract-subindex-inherited] +.IMultisig + +-- + +[.contract-index] +.Errors +-- +* {xref-Multisig-ProposalCreationForbidden-address-}[`++ProposalCreationForbidden(sender)++`] +* {xref-Multisig-NonexistentProposal-uint256-}[`++NonexistentProposal(proposalId)++`] +* {xref-Multisig-ApprovalCastForbidden-uint256-address-}[`++ApprovalCastForbidden(proposalId, sender)++`] +* {xref-Multisig-ProposalExecutionForbidden-uint256-}[`++ProposalExecutionForbidden(proposalId)++`] +* {xref-Multisig-MinApprovalsOutOfBounds-uint16-uint16-}[`++MinApprovalsOutOfBounds(limit, actual)++`] +* {xref-Multisig-AddresslistLengthOutOfBounds-uint16-uint256-}[`++AddresslistLengthOutOfBounds(limit, actual)++`] +* {xref-Multisig-ProposalAlreadyExists-uint256-}[`++ProposalAlreadyExists(proposalId)++`] +* {xref-Multisig-DateOutOfBounds-uint64-uint64-}[`++DateOutOfBounds(limit, actual)++`] + +[.contract-subindex-inherited] +.Addresslist +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/governance/Addresslist.sol[InvalidAddresslistUpdate] + +[.contract-subindex-inherited] +.ProposalUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol[FunctionDeprecated] + +[.contract-subindex-inherited] +.PluginUUPSUpgradeable +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[InvalidTargetConfig] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[DelegateCallFailed] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/PluginUUPSUpgradeable.sol[AlreadyInitialized] + +[.contract-subindex-inherited] +.ProtocolVersion + +[.contract-subindex-inherited] +.IProtocolVersion + +[.contract-subindex-inherited] +.MetadataExtensionUpgradeable + +[.contract-subindex-inherited] +.DaoAuthorizableUpgradeable + +[.contract-subindex-inherited] +.ContextUpgradeable + +[.contract-subindex-inherited] +.UUPSUpgradeable + +[.contract-subindex-inherited] +.ERC1967UpgradeUpgradeable + +[.contract-subindex-inherited] +.IERC1967Upgradeable + +[.contract-subindex-inherited] +.IERC1822ProxiableUpgradeable + +[.contract-subindex-inherited] +.ERC165Upgradeable + +[.contract-subindex-inherited] +.IERC165Upgradeable + +[.contract-subindex-inherited] +.Initializable + +[.contract-subindex-inherited] +.IProposal + +[.contract-subindex-inherited] +.IPlugin + +[.contract-subindex-inherited] +.IMembership + +[.contract-subindex-inherited] +.IMultisig + +-- + +[.contract-index] +.Internal Variables +-- +* {xref-Multisig-MULTISIG_INTERFACE_ID-bytes4}[`++bytes4 constant MULTISIG_INTERFACE_ID++`] +* {xref-Multisig-proposals-mapping-uint256----struct-Multisig-Proposal-}[`++mapping(uint256 => struct Multisig.Proposal) proposals++`] + +[.contract-subindex-inherited] +.Addresslist + +[.contract-subindex-inherited] +.ProposalUpgradeable + +[.contract-subindex-inherited] +.PluginUUPSUpgradeable + +[.contract-subindex-inherited] +.ProtocolVersion + +[.contract-subindex-inherited] +.IProtocolVersion + +[.contract-subindex-inherited] +.MetadataExtensionUpgradeable + +[.contract-subindex-inherited] +.DaoAuthorizableUpgradeable + +[.contract-subindex-inherited] +.ContextUpgradeable + +[.contract-subindex-inherited] +.UUPSUpgradeable + +[.contract-subindex-inherited] +.ERC1967UpgradeUpgradeable + +[.contract-subindex-inherited] +.IERC1967Upgradeable + +[.contract-subindex-inherited] +.IERC1822ProxiableUpgradeable + +[.contract-subindex-inherited] +.ERC165Upgradeable + +[.contract-subindex-inherited] +.IERC165Upgradeable + +[.contract-subindex-inherited] +.Initializable + +[.contract-subindex-inherited] +.IProposal + +[.contract-subindex-inherited] +.IPlugin + +[.contract-subindex-inherited] +.IMembership + +[.contract-subindex-inherited] +.IMultisig + +-- + +[.contract-item] +[[Multisig-initialize-contract-IDAO-address---struct-Multisig-MultisigSettings-struct-IPlugin-TargetConfig-bytes-]] +==== `[.contract-item-name]#++initialize++#++(contract IDAO _dao, address[] _members, struct Multisig.MultisigSettings _multisigSettings, struct IPlugin.TargetConfig _targetConfig, bytes _pluginMetadata)++` [.item-kind]#external# + +Initializes Release 1, Build 3. + +This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). + +[.contract-item] +[[Multisig-initializeFrom-uint16-bytes-]] +==== `[.contract-item-name]#++initializeFrom++#++(uint16 _fromBuild, bytes _initData)++` [.item-kind]#external# + +Reinitializes the Multisig after an upgrade from a previous build version. For each + reinitialization step, use the `_fromBuild` version to decide which internal functions to call + for reinitialization. + +WARNING: The contract should only be upgradeable through PSP to ensure that _fromBuild is not + incorrectly passed, and that the appropriate permissions for the upgrade are properly configured. + +[.contract-item] +[[Multisig-supportsInterface-bytes4-]] +==== `[.contract-item-name]#++supportsInterface++#++(bytes4 _interfaceId) → bool++` [.item-kind]#public# + +Checks if this or the parent contract supports an interface by its ID. + +[.contract-item] +[[Multisig-addAddresses-address---]] +==== `[.contract-item-name]#++addAddresses++#++(address[] _members)++` [.item-kind]#external# + +Adds new members to the address list. Previously, it checks if the new address + list length would be greater than `type(uint16).max`, the maximal number of approvals. + +Requires the `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission. + +[.contract-item] +[[Multisig-removeAddresses-address---]] +==== `[.contract-item-name]#++removeAddresses++#++(address[] _members)++` [.item-kind]#external# + +Removes existing members from the address list. Previously, it checks if the + new address list length is at least as long as the minimum approvals parameter requires. + Note that `minApprovals` is must be at least 1 so the address list cannot become empty. + +Requires the `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission. + +[.contract-item] +[[Multisig-updateMultisigSettings-struct-Multisig-MultisigSettings-]] +==== `[.contract-item-name]#++updateMultisigSettings++#++(struct Multisig.MultisigSettings _multisigSettings)++` [.item-kind]#external# + +Updates the plugin settings. + +Requires the `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission. + +[.contract-item] +[[Multisig-createProposal-bytes-struct-Action---uint256-bool-bool-uint64-uint64-]] +==== `[.contract-item-name]#++createProposal++#++(bytes _metadata, struct Action[] _actions, uint256 _allowFailureMap, bool _approveProposal, bool _tryExecution, uint64 _startDate, uint64 _endDate) → uint256 proposalId++` [.item-kind]#public# + +Creates a new multisig proposal. + +Requires the `CREATE_PROPOSAL_PERMISSION_ID` permission. + +[.contract-item] +[[Multisig-createProposal-bytes-struct-Action---uint64-uint64-bytes-]] +==== `[.contract-item-name]#++createProposal++#++(bytes _metadata, struct Action[] _actions, uint64 _startDate, uint64 _endDate, bytes _data) → uint256 proposalId++` [.item-kind]#external# + +Creates a new proposal. + +Calls a public function that requires the `CREATE_PROPOSAL_PERMISSION_ID` permission. + +[.contract-item] +[[Multisig-customProposalParamsABI--]] +==== `[.contract-item-name]#++customProposalParamsABI++#++() → string++` [.item-kind]#external# + +The human-readable abi format for extra params included in `data` of `createProposal`. + +Used for UI to easily detect what extra params the contract expects. + +[.contract-item] +[[Multisig-approve-uint256-bool-]] +==== `[.contract-item-name]#++approve++#++(uint256 _proposalId, bool _tryExecution)++` [.item-kind]#public# + +Records an approval for a proposal and, if specified, attempts execution if certain conditions are met. + +If `_tryExecution` is `true`, the function attempts execution after recording the approval. + Execution will only proceed if the proposal is no longer open, the minimum approval requirements are met, + and the caller has been granted execution permission. If execution conditions are not met, + the function does not revert. + +[.contract-item] +[[Multisig-canApprove-uint256-address-]] +==== `[.contract-item-name]#++canApprove++#++(uint256 _proposalId, address _account) → bool++` [.item-kind]#external# + +Checks if an account is eligible to participate in a proposal vote. + Confirms that the proposal is open, the account is listed as a member, + and the account has not previously voted or approved this proposal. + +Reverts if the proposal with the given `_proposalId` does not exist. + +[.contract-item] +[[Multisig-canExecute-uint256-]] +==== `[.contract-item-name]#++canExecute++#++(uint256 _proposalId) → bool++` [.item-kind]#external# + +Checks if a proposal can be executed. + +Reverts if the proposal with the given `_proposalId` does not exist. + +[.contract-item] +[[Multisig-hasSucceeded-uint256-]] +==== `[.contract-item-name]#++hasSucceeded++#++(uint256 _proposalId) → bool++` [.item-kind]#external# + +Whether proposal succeeded or not. + +Note that this must not include time window checks and only make a decision based on the thresholds. + +[.contract-item] +[[Multisig-getProposal-uint256-]] +==== `[.contract-item-name]#++getProposal++#++(uint256 _proposalId) → bool executed, uint16 approvals, struct Multisig.ProposalParameters parameters, struct Action[] actions, uint256 allowFailureMap, struct IPlugin.TargetConfig targetConfig++` [.item-kind]#public# + +Returns all information for a proposal by its ID. + +[.contract-item] +[[Multisig-hasApproved-uint256-address-]] +==== `[.contract-item-name]#++hasApproved++#++(uint256 _proposalId, address _account) → bool++` [.item-kind]#public# + +Returns whether the account has approved the proposal. + +May return false if the `_proposalId` or `_account` do not exist, + as the function does not verify their existence. + +[.contract-item] +[[Multisig-execute-uint256-]] +==== `[.contract-item-name]#++execute++#++(uint256 _proposalId)++` [.item-kind]#public# + +Executes a proposal if all execution conditions are met. + +Requires the `EXECUTE_PROPOSAL_PERMISSION_ID` permission. +Reverts if the proposal is still open or if the minimum approval threshold has not been met. + +[.contract-item] +[[Multisig-isMember-address-]] +==== `[.contract-item-name]#++isMember++#++(address _account) → bool++` [.item-kind]#external# + +Checks if an account is a member of the DAO. + +This function must be implemented in the plugin contract that introduces the members to the DAO. + +[.contract-item] +[[Multisig-_execute-uint256-]] +==== `[.contract-item-name]#++_execute++#++(uint256 _proposalId)++` [.item-kind]#internal# + +Internal function to execute a proposal. + +It assumes the queried proposal exists. + +[.contract-item] +[[Multisig-_canApprove-uint256-address-]] +==== `[.contract-item-name]#++_canApprove++#++(uint256 _proposalId, address _account) → bool++` [.item-kind]#internal# + +Internal function to check if an account can approve. + +It assumes the queried proposal exists. + +[.contract-item] +[[Multisig-_canExecute-uint256-]] +==== `[.contract-item-name]#++_canExecute++#++(uint256 _proposalId) → bool++` [.item-kind]#internal# + +Internal function to check if a proposal can be executed. + +It assumes the queried proposal exists. + +[.contract-item] +[[Multisig-_isProposalOpen-struct-Multisig-Proposal-]] +==== `[.contract-item-name]#++_isProposalOpen++#++(struct Multisig.Proposal proposal_) → bool++` [.item-kind]#internal# + +Internal function to check if a proposal is still open. + +[.contract-item] +[[Multisig-_updateMultisigSettings-struct-Multisig-MultisigSettings-]] +==== `[.contract-item-name]#++_updateMultisigSettings++#++(struct Multisig.MultisigSettings _multisigSettings)++` [.item-kind]#internal# + +Internal function to update the plugin settings. + +[.contract-item] +[[Multisig-UPDATE_MULTISIG_SETTINGS_PERMISSION_ID-bytes32]] +==== `[.contract-item-name]#++UPDATE_MULTISIG_SETTINGS_PERMISSION_ID++#++() → bytes32++` [.item-kind]#public# + +The ID of the permission required to call the + `addAddresses`, `removeAddresses` and `updateMultisigSettings` functions. + +[.contract-item] +[[Multisig-CREATE_PROPOSAL_PERMISSION_ID-bytes32]] +==== `[.contract-item-name]#++CREATE_PROPOSAL_PERMISSION_ID++#++() → bytes32++` [.item-kind]#public# + +The ID of the permission required to call the `createProposal` function. + +[.contract-item] +[[Multisig-EXECUTE_PROPOSAL_PERMISSION_ID-bytes32]] +==== `[.contract-item-name]#++EXECUTE_PROPOSAL_PERMISSION_ID++#++() → bytes32++` [.item-kind]#public# + +The ID of the permission required to call the `execute` function. + +[.contract-item] +[[Multisig-multisigSettings-struct-Multisig-MultisigSettings]] +==== `[.contract-item-name]#++multisigSettings++#++() → struct Multisig.MultisigSettings++` [.item-kind]#public# + +The current plugin settings. + +[.contract-item] +[[Multisig-lastMultisigSettingsChange-uint64]] +==== `[.contract-item-name]#++lastMultisigSettingsChange++#++() → uint64++` [.item-kind]#public# + +Keeps track at which block number the multisig settings have been changed the last time. + +This variable prevents a proposal from being created in the same block in which the multisig + settings change. + +[.contract-item] +[[Multisig-Approved-uint256-address-]] +==== `[.contract-item-name]#++Approved++#++(uint256 indexed proposalId, address indexed approver)++` [.item-kind]#event# + +Emitted when a proposal is approve by an approver. + +[.contract-item] +[[Multisig-MultisigSettingsUpdated-bool-uint16-]] +==== `[.contract-item-name]#++MultisigSettingsUpdated++#++(bool onlyListed, uint16 indexed minApprovals)++` [.item-kind]#event# + +Emitted when the plugin settings are set. + +[.contract-item] +[[Multisig-ProposalCreationForbidden-address-]] +==== `[.contract-item-name]#++ProposalCreationForbidden++#++(address sender)++` [.item-kind]#error# + +Thrown when a sender is not allowed to create a proposal. + +[.contract-item] +[[Multisig-NonexistentProposal-uint256-]] +==== `[.contract-item-name]#++NonexistentProposal++#++(uint256 proposalId)++` [.item-kind]#error# + +Thrown when a proposal doesn't exist. + +[.contract-item] +[[Multisig-ApprovalCastForbidden-uint256-address-]] +==== `[.contract-item-name]#++ApprovalCastForbidden++#++(uint256 proposalId, address sender)++` [.item-kind]#error# + +Thrown if an approver is not allowed to cast an approve. This can be because the proposal + - is not open, + - was executed, or + - the approver is not on the address list + +[.contract-item] +[[Multisig-ProposalExecutionForbidden-uint256-]] +==== `[.contract-item-name]#++ProposalExecutionForbidden++#++(uint256 proposalId)++` [.item-kind]#error# + +Thrown if the proposal execution is forbidden. + +[.contract-item] +[[Multisig-MinApprovalsOutOfBounds-uint16-uint16-]] +==== `[.contract-item-name]#++MinApprovalsOutOfBounds++#++(uint16 limit, uint16 actual)++` [.item-kind]#error# + +Thrown if the minimal approvals value is out of bounds (less than 1 or greater than the number of + members in the address list). + +[.contract-item] +[[Multisig-AddresslistLengthOutOfBounds-uint16-uint256-]] +==== `[.contract-item-name]#++AddresslistLengthOutOfBounds++#++(uint16 limit, uint256 actual)++` [.item-kind]#error# + +Thrown if the address list length is out of bounds. + +[.contract-item] +[[Multisig-ProposalAlreadyExists-uint256-]] +==== `[.contract-item-name]#++ProposalAlreadyExists++#++(uint256 proposalId)++` [.item-kind]#error# + +Thrown if the proposal with the same id already exists. + +[.contract-item] +[[Multisig-DateOutOfBounds-uint64-uint64-]] +==== `[.contract-item-name]#++DateOutOfBounds++#++(uint64 limit, uint64 actual)++` [.item-kind]#error# + +Thrown if a date is out of bounds. + +[.contract-item] +[[Multisig-MULTISIG_INTERFACE_ID-bytes4]] +==== `bytes4 [.contract-item-name]#++MULTISIG_INTERFACE_ID++#` [.item-kind]#internal constant# + +The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract. + +[.contract-item] +[[Multisig-proposals-mapping-uint256----struct-Multisig-Proposal-]] +==== `mapping(uint256 => struct Multisig.Proposal) [.contract-item-name]#++proposals++#` [.item-kind]#internal# + +A mapping between proposal IDs and proposal information. + +:constructor: pass:normal[xref:#MultisigSetup-constructor--[`++constructor++`]] +:prepareInstallation: pass:normal[xref:#MultisigSetup-prepareInstallation-address-bytes-[`++prepareInstallation++`]] +:prepareUpdate: pass:normal[xref:#MultisigSetup-prepareUpdate-address-uint16-struct-IPluginSetup-SetupPayload-[`++prepareUpdate++`]] +:prepareUninstallation: pass:normal[xref:#MultisigSetup-prepareUninstallation-address-struct-IPluginSetup-SetupPayload-[`++prepareUninstallation++`]] + +[.contract] +[[MultisigSetup]] +=== `++MultisigSetup++` link:https://github.com/aragon/multisig-plugin/blob/v1.3/packages/contracts/src/MultisigSetup.sol[{github-icon},role=heading-link] + +v1.3 (Release 1, Build 3) + +[.contract-index] +.Functions +-- +* {xref-MultisigSetup-constructor--}[`++constructor()++`] +* {xref-MultisigSetup-prepareInstallation-address-bytes-}[`++prepareInstallation(_dao, _data)++`] +* {xref-MultisigSetup-prepareUpdate-address-uint16-struct-IPluginSetup-SetupPayload-}[`++prepareUpdate(_dao, _fromBuild, _payload)++`] +* {xref-MultisigSetup-prepareUninstallation-address-struct-IPluginSetup-SetupPayload-}[`++prepareUninstallation(_dao, _payload)++`] + +[.contract-subindex-inherited] +.PluginUpgradeableSetup +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/setup/PluginUpgradeableSetup.sol[supportsInterface] +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/setup/PluginUpgradeableSetup.sol[implementation] + +[.contract-subindex-inherited] +.ProtocolVersion +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/versioning/ProtocolVersion.sol[protocolVersion] + +[.contract-subindex-inherited] +.IProtocolVersion + +[.contract-subindex-inherited] +.IPluginSetup + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-index] +.Errors +-- + +[.contract-subindex-inherited] +.PluginUpgradeableSetup +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/plugin/setup/PluginUpgradeableSetup.sol[InvalidUpdatePath] + +[.contract-subindex-inherited] +.ProtocolVersion + +[.contract-subindex-inherited] +.IProtocolVersion + +[.contract-subindex-inherited] +.IPluginSetup + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[MultisigSetup-constructor--]] +==== `[.contract-item-name]#++constructor++#++()++` [.item-kind]#public# + +The contract constructor, that deploys the `Multisig` plugin logic contract. + +[.contract-item] +[[MultisigSetup-prepareInstallation-address-bytes-]] +==== `[.contract-item-name]#++prepareInstallation++#++(address _dao, bytes _data) → address plugin, struct IPluginSetup.PreparedSetupData preparedSetupData++` [.item-kind]#external# + +Prepares the installation of a plugin. + +[.contract-item] +[[MultisigSetup-prepareUpdate-address-uint16-struct-IPluginSetup-SetupPayload-]] +==== `[.contract-item-name]#++prepareUpdate++#++(address _dao, uint16 _fromBuild, struct IPluginSetup.SetupPayload _payload) → bytes initData, struct IPluginSetup.PreparedSetupData preparedSetupData++` [.item-kind]#external# + +Prepares the update of a plugin. + +Revoke the upgrade plugin permission to the DAO for all builds prior the current one (3). + +[.contract-item] +[[MultisigSetup-prepareUninstallation-address-struct-IPluginSetup-SetupPayload-]] +==== `[.contract-item-name]#++prepareUninstallation++#++(address _dao, struct IPluginSetup.SetupPayload _payload) → struct PermissionLib.MultiTargetPermission[] permissions++` [.item-kind]#external# + +Prepares the uninstallation of a plugin. + +:constructor: pass:normal[xref:#ListedCheckCondition-constructor-address-[`++constructor++`]] +:isGranted: pass:normal[xref:#ListedCheckCondition-isGranted-address-address-bytes32-bytes-[`++isGranted++`]] + +[.contract] +[[ListedCheckCondition]] +=== `++ListedCheckCondition++` link:https://github.com/aragon/multisig-plugin/blob/v1.3/packages/contracts/src/ListedCheckCondition.sol[{github-icon},role=heading-link] + +[.contract-index] +.Functions +-- +* {xref-ListedCheckCondition-constructor-address-}[`++constructor(_multisig)++`] +* {xref-ListedCheckCondition-isGranted-address-address-bytes32-bytes-}[`++isGranted(_where, _who, _permissionId, _data)++`] + +[.contract-subindex-inherited] +.PermissionCondition +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/permission/condition/PermissionCondition.sol[supportsInterface] + +[.contract-subindex-inherited] +.ProtocolVersion +* link:https://github.com/aragon/osx-commons/tree/main/contracts/src/utils/versioning/ProtocolVersion.sol[protocolVersion] + +[.contract-subindex-inherited] +.IProtocolVersion + +[.contract-subindex-inherited] +.IPermissionCondition + +[.contract-subindex-inherited] +.ERC165 + +[.contract-subindex-inherited] +.IERC165 + +-- + +[.contract-item] +[[ListedCheckCondition-constructor-address-]] +==== `[.contract-item-name]#++constructor++#++(address _multisig)++` [.item-kind]#public# + +Initializes the condition with the address of the Multisig plugin. + +[.contract-item] +[[ListedCheckCondition-isGranted-address-address-bytes32-bytes-]] +==== `[.contract-item-name]#++isGranted++#++(address _where, address _who, bytes32 _permissionId, bytes _data) → bool++` [.item-kind]#public# + +Checks if a call is permitted. + diff --git a/packages/contracts/hardhat-zksync.config.ts b/packages/contracts/hardhat-zksync.config.ts index 2515dc5f..df5ea508 100644 --- a/packages/contracts/hardhat-zksync.config.ts +++ b/packages/contracts/hardhat-zksync.config.ts @@ -35,11 +35,9 @@ extendEnvironment(hre => { hre.aragonToVerifyContracts = []; }); -task('build-contracts').setAction(async (args, hre) => { - await hre.run('compile'); - - const network = hre.network.name; - if (isZkSync(network)) { +task('compile').setAction(async (args, hre, runSuper) => { + await runSuper(args); + if (isZkSync(hre.network.name)) { // Copy zkSync specific build artifacts and cache to the default directories. // This ensures that we don't need to change import paths for artifacts in the project. fs.cpSync('./build/artifacts-zk', './artifacts', { @@ -47,6 +45,7 @@ task('build-contracts').setAction(async (args, hre) => { force: true, }); fs.cpSync('./build/cache-zk', './cache', {recursive: true, force: true}); + return; } @@ -63,8 +62,8 @@ task('deploy-contracts') }); }); -task('test-contracts').setAction(async (args, hre) => { - await hre.run('build-contracts'); +task('test').setAction(async (args, hre, runSuper) => { + await hre.run('compile'); const imp = await import('./test/test-utils/wrapper'); const wrapper = await imp.Wrapper.create( @@ -73,7 +72,7 @@ task('test-contracts').setAction(async (args, hre) => { ); hre.wrapper = wrapper; - await hre.run('test'); + await runSuper(args); }); // You need to export an object to set up your config diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 1a2d7349..92172d17 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -5,8 +5,8 @@ import { SupportedNetworks, } from '@aragon/osx-commons-configs'; import '@nomicfoundation/hardhat-chai-matchers'; -import '@nomicfoundation/hardhat-toolbox'; -import '@nomiclabs/hardhat-etherscan'; +import '@nomicfoundation/hardhat-network-helpers'; +import '@nomicfoundation/hardhat-verify'; import '@openzeppelin/hardhat-upgrades'; import '@typechain/hardhat'; import {config as dotenvConfig} from 'dotenv'; @@ -33,7 +33,9 @@ if (process.env.ALCHEMY_API_KEY) { throw new Error('ALCHEMY_API_KEY in .env not set'); } -task('test-contracts').setAction(async (args, hre) => { +// Override the test task so it injects wrapper. +// Note that this also gets injected when running it through coverage. +task('test').setAction(async (args, hre, runSuper) => { await hre.run('compile'); const imp = await import('./test/test-utils/wrapper'); @@ -43,7 +45,7 @@ task('test-contracts').setAction(async (args, hre) => { ); hre.wrapper = wrapper; - await hre.run('test'); + await runSuper(args); }); // Fetch the accounts specified in the .env file @@ -86,7 +88,18 @@ function getHardhatNetworkAccountsConfig( } // Add the accounts specified in the `.env` file to the networks from osx-commons-configs -const networks: {[index: string]: NetworkUserConfig} = osxCommonsConfigNetworks; +const networks: {[index: string]: NetworkUserConfig} = { + ...osxCommonsConfigNetworks, + agungTestnet: { + url: 'https://wss-async.agung.peaq.network', + chainId: 9990, + gasPrice: 35000000000, + }, + peaq: { + url: 'https://erpc-mpfn1.peaq.network', + chainId: 3338, + }, +}; for (const network of Object.keys(networks) as SupportedNetworks[]) { networks[network].accounts = specifiedAccounts(); } @@ -134,6 +147,7 @@ const config: HardhatUserConfig = { polygon: process.env.POLYGONSCAN_API_KEY || '', base: process.env.BASESCAN_API_KEY || '', arbitrumOne: process.env.ARBISCAN_API_KEY || '', + peaq: '1', }, customChains: [ { @@ -152,6 +166,15 @@ const config: HardhatUserConfig = { browserURL: 'https://basescan.org', }, }, + { + network: 'peaq', + chainId: 3338, + urls: { + apiURL: + 'https://peaq.api.subscan.io/api/scan/evm/contract/verifysource', + browserURL: 'https://peaq.subscan.io/', + }, + }, ], }, @@ -189,13 +212,7 @@ const config: HardhatUserConfig = { outDir: 'typechain', target: 'ethers-v5', }, - docgen: { - outputDir: 'docs', - theme: 'markdown', - pages: 'files', - collapseNewlines: true, - exclude: ['test', 'mocks'], - }, + docgen: process.env.DOCS ? require('./docs/config.js') : undefined, }; export default config; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 8f51b25b..fed89d0d 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,53 +1,61 @@ { + "private": true, + "version": "1.3", + "repository": { + "type": "git", + "url": "https://github.com/aragon/multisig-plugin" + }, + "author": "Aragon", "license": "AGPL-3.0-or-later", + "files": [ + "/src" + ], "scripts": { "build": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", - "build:zksync": "hardhat build-contracts --config hardhat-zksync.config.ts && yarn typechain", + "build:zksync": "hardhat compile --config hardhat-zksync.config.ts && yarn typechain", "coverage": "hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"test/**/*.ts\" && yarn typechain", "deploy": "hardhat deploy", "deploy:zksync": "hardhat deploy-contracts --config hardhat-zksync.config.ts", "lint": "yarn lint:sol && yarn lint:ts", "lint:sol": "cd ../../ && yarn run lint:contracts:sol", "lint:ts": "cd ../../ && yarn run lint:contracts:ts", - "test": "hardhat test-contracts", - "test:zksync": "hardhat test-contracts --config hardhat-zksync.config.ts", - "docgen": "hardhat docgen", + "docs": "DOCS=true scripts/prepare-docs.sh", + "test": "hardhat test", + "test:zksync": "hardhat test --config hardhat-zksync.config.ts", "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain", - "clean": "rimraf ./artifacts ./cache ./coverage ./typechain ./types ./coverage.json && yarn typechain" + "clean": "rimraf ./artifacts ./cache ./coverage ./typechain ./coverage.json && yarn typechain" }, "dependencies": { - "@aragon/osx-commons-contracts": "1.4.0-alpha.5", + "@aragon/osx-commons-contracts": "^1.4.0", "@openzeppelin/contracts": "^4.9.6", "@openzeppelin/contracts-upgradeable": "^4.9.6" }, "devDependencies": { - "@aragon/osx-commons-configs": "0.4.0", - "@aragon/osx-ethers": "1.4.0-alpha.0", - "@aragon/osx-commons-sdk": "0.0.1-alpha.11", - "@aragon/osx": "1.3.0", + "@aragon/osx": "^1.4.0", + "@aragon/osx-commons-configs": "^0.8.0", + "@aragon/osx-commons-sdk": "^0.2.0", + "@aragon/osx-ethers": "^1.4.0", "@aragon/osx-v1.0.0": "npm:@aragon/osx@1.0.1", "@aragon/osx-v1.3.0": "npm:@aragon/osx@1.3.0", - "@matterlabs/hardhat-zksync-deploy": "0.8", - "@matterlabs/hardhat-zksync-node": "0.1.0", - "@matterlabs/hardhat-zksync-solc": "1.2.5", - "@matterlabs/hardhat-zksync-upgradable": "0.4.0", - "@matterlabs/hardhat-zksync-verify": "0.7.0", - "@matterlabs/hardhat-zksync-ethers": "0.0.1-beta.2", - "zksync-ethers": "5.7.0", "@ethersproject/abi": "5.7.0", "@ethersproject/abstract-signer": "5.7.0", "@ethersproject/bignumber": "5.7.0", "@ethersproject/bytes": "5.7.0", "@ethersproject/providers": "5.7.2", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", + "@matterlabs/hardhat-zksync-deploy": "0.8", + "@matterlabs/hardhat-zksync-ethers": "0.0.1-beta.2", + "@matterlabs/hardhat-zksync-node": "0.1.0", + "@matterlabs/hardhat-zksync-solc": "1.2.5", + "@matterlabs/hardhat-zksync-upgradable": "0.4.0", + "@matterlabs/hardhat-zksync-verify": "0.7.0", "@nomicfoundation/hardhat-network-helpers": "^1.0.8", - "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@nomiclabs/hardhat-ethers": "^2.2.3", + "@nomiclabs/hardhat-ethers": "^2.2.1", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", "@nomiclabs/hardhat-etherscan": "^3.1.8", "@openzeppelin/hardhat-upgrades": "^1.28.0", + "@nomicfoundation/hardhat-verify": "^1.0.4", "@typechain/ethers-v5": "^10.1.1", "@typechain/hardhat": "^6.1.4", - "solidity-docgen": "^0.6.0-beta.35", "@types/chai": "^4.3.4", "@types/mocha": "^10.0.0", "@types/node": "^18.11.9", @@ -59,18 +67,15 @@ "hardhat-deploy": "^0.14.0", "hardhat-gas-reporter": "^1.0.9", "ipfs-http-client": "^51.0.0", + "lodash.startcase": "^4.4.0", "mocha": "^10.1.0", "rimraf": "^5.0.5", "solidity-coverage": "^0.8.2", + "solidity-docgen": "^0.6.0-beta.29", "tmp-promise": "^3.0.3", "ts-node": "^10.9.1", "typechain": "^8.3.2", - "typescript": "^5.2.2" - }, - "files": [ - "/src" - ], - "publishConfig": { - "access": "public" + "typescript": "^5.2.2", + "zksync-ethers": "5.7.0" } } diff --git a/packages/contracts/plugin-settings.ts b/packages/contracts/plugin-settings.ts index d481d42c..4ed87271 100644 --- a/packages/contracts/plugin-settings.ts +++ b/packages/contracts/plugin-settings.ts @@ -2,6 +2,7 @@ import buildMetadata from './src/build-metadata.json'; import releaseMetadata from './src/release-metadata.json'; import {VersionTag} from '@aragon/osx-commons-sdk'; +export const PLUGIN_REPO_PROXY_NAME = 'MultisigProxy'; export const PLUGIN_CONTRACT_NAME = 'Multisig'; export const PLUGIN_SETUP_CONTRACT_NAME = 'MultisigSetup'; export const PLUGIN_REPO_ENS_SUBDOMAIN_NAME = 'multisig'; // 'multisig.plugin.dao.eth' diff --git a/packages/contracts/scripts/gen-nav.js b/packages/contracts/scripts/gen-nav.js new file mode 100644 index 00000000..f3f29637 --- /dev/null +++ b/packages/contracts/scripts/gen-nav.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node + +const path = require('path'); +const glob = require('glob'); +const startCase = require('lodash.startcase'); + +const baseDir = process.argv[2]; + +const files = glob + .sync(baseDir + '/**/*.adoc') + .map(f => path.relative(baseDir, f)); + +console.log('.API'); + +function getPageTitle(directory) { + switch (directory) { + case 'metatx': + return 'Meta Transactions'; + case 'common': + return 'Common (Tokens)'; + default: + return startCase(directory); + } +} + +const links = files.map(file => { + const doc = file.replace(baseDir, ''); + const title = path.parse(file).name; + + return { + xref: `* xref:${doc}[${getPageTitle(title)}]`, + title, + }; +}); + +// Case-insensitive sort based on titles (so 'token/ERC20' gets sorted as 'erc20') +const sortedLinks = links.sort(function (a, b) { + return a.title + .toLowerCase() + .localeCompare(b.title.toLowerCase(), undefined, {numeric: true}); +}); + +for (const link of sortedLinks) { + console.log(link.xref); +} diff --git a/packages/contracts/scripts/prepare-docs.sh b/packages/contracts/scripts/prepare-docs.sh new file mode 100755 index 00000000..e977971c --- /dev/null +++ b/packages/contracts/scripts/prepare-docs.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -euo pipefail + +PACKAGE_NAME="@aragon/osx-commons-configs" +PACKAGE_PATH=$(node -p "require.resolve('$PACKAGE_NAME')") +TEMPLATES_PATH=$(dirname "$PACKAGE_PATH")/docs/templates + +cp -r "$TEMPLATES_PATH" "./docs" + +OUTDIR="$(node -p 'require("./docs/config.js").outputDir')" + +if [ ! -d node_modules ]; then + npm ci +fi + +rm -rf "$OUTDIR" + +hardhat docgen + +node scripts/gen-nav.js "$OUTDIR" > "$OUTDIR/../nav.adoc" + +rm -rf ./docs/templates/ + + diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index a59ea504..e819b957 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -9,7 +9,6 @@ import { IProposal__factory, IProtocolVersion__factory, } from '../../typechain'; -import {ExecutedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/dao/IDAO'; import { ApprovedEvent, ProposalCreatedEvent, @@ -39,7 +38,7 @@ import { TIME, DAO_PERMISSIONS, } from '@aragon/osx-commons-sdk'; -import {DAO, DAOStructs, DAO__factory} from '@aragon/osx-ethers'; +import {DAO, DAOStructs, DAO__factory, DAOEvents} from '@aragon/osx-ethers'; import {defaultAbiCoder} from '@ethersproject/abi'; import {loadFixture, time} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -2555,7 +2554,7 @@ describe('Multisig', function () { let tx = await plugin.connect(alice).approve(id, true); let rc = await tx.wait(); expect(() => - findEventTopicLog( + findEventTopicLog( rc, DAO__factory.createInterface(), 'Executed' @@ -2570,7 +2569,7 @@ describe('Multisig', function () { tx = await plugin.connect(bob).approve(id, false); rc = await tx.wait(); expect(() => - findEventTopicLog( + findEventTopicLog( rc, DAO__factory.createInterface(), 'Executed' @@ -2584,7 +2583,7 @@ describe('Multisig', function () { // Check that the proposal got executed by checking the `Executed` event emitted by the DAO. { - const event = findEventTopicLog( + const event = findEventTopicLog( await tx.wait(), DAO__factory.createInterface(), 'Executed' diff --git a/packages/contracts/utils/helpers.ts b/packages/contracts/utils/helpers.ts index 7033596f..1565d56c 100644 --- a/packages/contracts/utils/helpers.ts +++ b/packages/contracts/utils/helpers.ts @@ -1,11 +1,14 @@ -import {PLUGIN_REPO_ENS_SUBDOMAIN_NAME} from '../plugin-settings'; +import { + PLUGIN_REPO_ENS_SUBDOMAIN_NAME, + PLUGIN_REPO_PROXY_NAME, +} from '../plugin-settings'; import { SupportedNetworks, - getLatestNetworkDeployment, getNetworkNameByAlias, getPluginEnsDomain, } from '@aragon/osx-commons-configs'; -import {UnsupportedNetworkError, findEvent} from '@aragon/osx-commons-sdk'; +import {UnsupportedNetworkError} from '@aragon/osx-commons-sdk'; +import {Operation} from '@aragon/osx-commons-sdk'; import { DAO, DAO__factory, @@ -13,12 +16,15 @@ import { ENS__factory, IAddrResolver__factory, PluginRepo, - PluginRepoEvents, + PluginRepoFactory, PluginRepo__factory, + PluginRepoFactory__factory, + PluginRepoRegistry__factory, } from '@aragon/osx-ethers'; import {setBalance} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {BigNumber, ContractTransaction} from 'ethers'; +import {BigNumber} from 'ethers'; +import {Contract} from 'ethers'; import {LogDescription} from 'ethers/lib/utils'; import {ethers} from 'hardhat'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -66,28 +72,72 @@ export function pluginEnsDomain(hre: HardhatRuntimeEnvironment): string { return `${PLUGIN_REPO_ENS_SUBDOMAIN_NAME}.${pluginEnsDomain}`; } +/** + * try to get the plugin repo first + * 1- env var PLUGIN_REPO_ADDRESS + * 2- try to get the latest network deployment + * 3- from the commons configs + * - plugin repo factory address from env var + */ export async function findPluginRepo( hre: HardhatRuntimeEnvironment ): Promise<{pluginRepo: PluginRepo | null; ensDomain: string}> { const [deployer] = await hre.ethers.getSigners(); - const productionNetworkName: string = getProductionNetworkName(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); + const ensDomain = pluginEnsDomain(hre); + + // from env var + if (process.env.PLUGIN_REPO_ADDRESS) { + if (!isValidAddress(process.env.PLUGIN_REPO_ADDRESS)) { + throw new Error( + 'Plugin Repo in .env is not a valid address (is not an address or is address zero)' + ); + } + + return { + pluginRepo: PluginRepo__factory.connect( + process.env.PLUGIN_REPO_ADDRESS, + deployer + ), + ensDomain, + }; + } + + // from deployments + const pluginRepo = await hre.deployments.getOrNull(PLUGIN_REPO_PROXY_NAME); + if (pluginRepo) { + console.log( + 'using the plugin repo from the deployments', + pluginRepo.address + ); + return { + pluginRepo: PluginRepo__factory.connect(pluginRepo.address, deployer), + ensDomain, + }; } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; + + // get ENS registrar from the plugin factory provided + const pluginRepoFactory = await getPluginRepoFactory(hre); + + const pluginRepoRegistry = PluginRepoRegistry__factory.connect( + await pluginRepoFactory.pluginRepoRegistry(), + deployer + ); + + const subdomainRegistrarAddress = + await pluginRepoRegistry.subdomainRegistrar(); + + if (subdomainRegistrarAddress === ethers.constants.AddressZero) { + // the network does not support ENS and the plugin repo could not be found by env var or deployments + return {pluginRepo: null, ensDomain: ''}; } const registrar = ENSSubdomainRegistrar__factory.connect( - networkDeployments.PluginENSSubdomainRegistrarProxy.address, + subdomainRegistrarAddress, deployer ); // Check if the ens record exists already const ens = ENS__factory.connect(await registrar.ens(), deployer); - const ensDomain = pluginEnsDomain(hre); const node = ethers.utils.namehash(ensDomain); const recordExists = await ens.recordExists(node); @@ -114,40 +164,34 @@ export async function getManagementDao( hre: HardhatRuntimeEnvironment ): Promise { const [deployer] = await hre.ethers.getSigners(); - const productionNetworkName = getProductionNetworkName(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); - } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; + + const managementDaoAddress = process.env.MANAGEMENT_DAO_ADDRESS; + + // getting the management DAO from the env var + if (!managementDaoAddress || !isValidAddress(managementDaoAddress)) { + throw new Error( + 'Management DAO address in .env is not defined or is not a valid address (is not an address or is address zero)' + ); } - return DAO__factory.connect( - networkDeployments.ManagementDAOProxy.address, - deployer - ); + return DAO__factory.connect(managementDaoAddress, deployer); } -export async function getManagementDaoMultisig( +export async function getPluginRepoFactory( hre: HardhatRuntimeEnvironment -): Promise { +): Promise { const [deployer] = await hre.ethers.getSigners(); - const productionNetworkName = getProductionNetworkName(hre); - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); - } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; + + const pluginRepoFactoryAddress = process.env.PLUGIN_REPO_FACTORY_ADDRESS; + + // from env var + if (!pluginRepoFactoryAddress || !isValidAddress(pluginRepoFactoryAddress)) { + throw new Error( + 'Plugin Repo Factory address in .env is not defined or is not a valid address (is not an address or is address zero)' + ); } - return DAO__factory.connect( - networkDeployments.ManagementDAOProxy.address, - deployer - ); + return PluginRepoFactory__factory.connect(pluginRepoFactoryAddress, deployer); } export async function impersonatedManagementDaoSigner( @@ -197,49 +241,44 @@ export type LatestVersion = { buildMetadata: string; }; -export async function createVersion( - pluginRepoContract: string, - pluginSetupContract: string, - releaseNumber: number, - releaseMetadata: string, - buildMetadata: string -): Promise { - const signers = await ethers.getSigners(); - - const PluginRepo = new PluginRepo__factory(signers[0]); - const pluginRepo = PluginRepo.attach(pluginRepoContract); - - const tx = await pluginRepo.createVersion( - releaseNumber, - pluginSetupContract, - buildMetadata, - releaseMetadata - ); - - console.log(`Creating build for release ${releaseNumber} with tx ${tx.hash}`); +async function createVersion( + pluginRepo: PluginRepo, + release: number, + setup: string, + releaseMetadataURI: string, + buildMetadataURI: string, + signer: SignerWithAddress +) { + const tx = await pluginRepo + .connect(signer) + .createVersion( + release, + setup, + ethers.utils.hexlify(ethers.utils.toUtf8Bytes(buildMetadataURI)), + ethers.utils.hexlify(ethers.utils.toUtf8Bytes(releaseMetadataURI)) + ); - const versionCreatedEvent = findEvent( - await tx.wait(), - pluginRepo.interface.events['VersionCreated(uint8,uint16,address,bytes)'] - .name - ); + await tx.wait(); +} - // Check if versionCreatedEvent is not undefined - if (versionCreatedEvent) { - console.log( - `Created build ${versionCreatedEvent.args.build} for release ${ - versionCreatedEvent.args.release - } with setup address: ${ - versionCreatedEvent.args.pluginSetup - }, with build metadata ${ethers.utils.toUtf8String( - buildMetadata - )} and release metadata ${ethers.utils.toUtf8String(releaseMetadata)}` +export async function publishPlaceholderVersion( + placeholderSetup: string, + versionBuild: number, + versionRelease: number, + pluginRepo: PluginRepo, + signer: any +) { + for (let i = 0; i < versionBuild - 1; i++) { + console.log('Publishing placeholder', i + 1); + await createVersion( + pluginRepo, + versionRelease, + placeholderSetup, + `{}`, + 'placeholder-setup-build', + signer ); - } else { - // Handle the case where the event is not found - throw new Error('Failed to get VersionCreatedEvent event log'); } - return tx; } export function generateRandomName(length: number): string { @@ -253,5 +292,55 @@ export function generateRandomName(length: number): string { return result; } +export function isValidAddress(address: string): boolean { + // check if the address is valid and not zero address + return ( + ethers.utils.isAddress(address) && address !== ethers.constants.AddressZero + ); +} + +export async function frameworkSupportsENS( + pluginRepoFactory: PluginRepoFactory +): Promise { + const [deployer] = await ethers.getSigners(); + const pluginRepoRegistry = PluginRepoRegistry__factory.connect( + await pluginRepoFactory.pluginRepoRegistry(), + deployer + ); + const subdomainRegistrar = await pluginRepoRegistry.subdomainRegistrar(); + + return subdomainRegistrar !== ethers.constants.AddressZero; +} + +export type Permission = { + operation: Operation; + where: {name: string; address: string}; + who: {name: string; address: string}; + permission: string; + condition?: string; + data?: string; +}; + +export async function isPermissionSetCorrectly( + permissionManagerContract: Contract, + {operation, where, who, permission, data = '0x'}: Permission +): Promise { + const permissionId = ethers.utils.id(permission); + const isGranted = await permissionManagerContract.isGranted( + where.address, + who.address, + permissionId, + data + ); + if (!isGranted && operation === Operation.Grant) { + return false; + } + + if (isGranted && operation === Operation.Revoke) { + return false; + } + return true; +} + export const AragonOSxAsciiArt = " ____ _____ \n /\\ / __ \\ / ____| \n / \\ _ __ __ _ __ _ ___ _ __ | | | | (_____ __ \n / /\\ \\ | '__/ _` |/ _` |/ _ \\| '_ \\ | | | |\\___ \\ \\/ / \n / ____ \\| | | (_| | (_| | (_) | | | | | |__| |____) > < \n /_/ \\_\\_| \\__,_|\\__, |\\___/|_| |_| \\____/|_____/_/\\_\\ \n __/ | \n |___/ \n"; diff --git a/packages/contracts/yarn.lock b/packages/contracts/yarn.lock index 22b13bdf..c9d641fc 100644 --- a/packages/contracts/yarn.lock +++ b/packages/contracts/yarn.lock @@ -2,39 +2,38 @@ # yarn lockfile v1 -"@aragon/osx-commons-configs@0.4.0", "@aragon/osx-commons-configs@^0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.4.0.tgz#5b6ae025de1ccf7f9a135bfbcb0aa822c774acf9" - integrity sha512-/2wIQCbv/spMRdOjRXK0RrXG1TK5aMcbD73RvMgMwQwSrKcA1dCntUuSxmTm2W8eEtOzs8E1VPjqZk0cXL4SSQ== +"@aragon/osx-commons-configs@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.8.0.tgz#71e27c7063c3ca7a26a2c5ae12594063800bd9db" + integrity sha512-O8CIKxm+jCN4sq8DBAISlo+Y75VKY03uMqGVP1bUxshqW3ax42r2vlYtTRE/0dJgS9Yh5ElO1tVfDutsGoYWoA== dependencies: tslib "^2.6.2" -"@aragon/osx-commons-contracts@1.4.0-alpha.5": - version "1.4.0-alpha.5" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-contracts/-/osx-commons-contracts-1.4.0-alpha.5.tgz#37a28085677c21216628ba0a05f5fe09489eb71c" - integrity sha512-F2JWWxmUNmiJsaXcTDyd6F2GUIgnc313vvWTp/cSmSVkccT2pfMleWqxIi4LIodX3ueFUYfE02rLj8Gjp1jljA== +"@aragon/osx-commons-contracts@1.4.0", "@aragon/osx-commons-contracts@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-contracts/-/osx-commons-contracts-1.4.0.tgz#61eda9fbfa19ab76cd7e33c688a2269c5e9f937b" + integrity sha512-EufkwHD9BfktIQcaLmvLfY0RXJ/ztbVtB0UtQTVyWZpWALbfUVtHtIC8D9/8JoYVbe8FyDi+btpbBQj79Iwrkg== dependencies: "@openzeppelin/contracts" "4.9.6" "@openzeppelin/contracts-upgradeable" "4.9.6" -"@aragon/osx-commons-sdk@0.0.1-alpha.11": - version "0.0.1-alpha.11" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-sdk/-/osx-commons-sdk-0.0.1-alpha.11.tgz#1e3e39aac3351b6649921a5e373a5146b86b2c92" - integrity sha512-/FgTLnS73QY0JxZYQlHwpd2ZuMPkPKdIlci9T4jgH9E4Eyyxh6GX7fJK+HekCcf6oMS5tTZ/Cb3sVyyzoO95iw== +"@aragon/osx-commons-sdk@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-sdk/-/osx-commons-sdk-0.2.0.tgz#e5ca5db172866775a02337c22af3a05ac16541fa" + integrity sha512-bxzvIbxdQtObqLPrJlJcPuy2cMzrE9TI7zlNMcbKnyr4swp44ZHZi5NKcwX1eFAf+oZc1aFlehb1gPwPMvaT8Q== dependencies: - "@aragon/osx-commons-configs" "^0.4.0" + "@aragon/osx-commons-configs" "^0.8.0" "@ethersproject/address" "5.7.0" "@ethersproject/bignumber" "5.7.0" "@ethersproject/contracts" "5.7.0" "@ethersproject/hash" "5.7.0" "@ethersproject/logger" "5.7.0" "@ethersproject/providers" "5.7.2" - ipfs-http-client "^51.0.0" -"@aragon/osx-ethers@1.4.0-alpha.0": - version "1.4.0-alpha.0" - resolved "https://registry.yarnpkg.com/@aragon/osx-ethers/-/osx-ethers-1.4.0-alpha.0.tgz#329f1ac27660b486fa0b296dddeb004ce352001c" - integrity sha512-fFsrG/XMIjZe3MxVQdf87gqAC4q0Z/eBp72QUuzXJQ0gMSTSj/4TvveFn1N8toLN6KsJolMEkaTamyCGYR+5iA== +"@aragon/osx-ethers@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@aragon/osx-ethers/-/osx-ethers-1.4.0.tgz#b36b61973ef50798706c2645056a4ff6dc357f4a" + integrity sha512-7DoidZNuFHkps9oYuU+da/NJ+ItPXpfu9wja5vHtS0ExFMcU8ZbCDJGBaDMc0m/Q0kNeFIinCeQS6e1lUDx7VA== dependencies: ethers "^5.6.2" @@ -52,14 +51,15 @@ "@openzeppelin/contracts" "4.8.1" "@openzeppelin/contracts-upgradeable" "4.8.1" -"@aragon/osx@1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@aragon/osx/-/osx-1.3.0.tgz#eee59963546016bb3b41b7c7a9b7c41d33b37de2" - integrity sha512-ziLmnhWEoFS/uthxAYfI9tSylesMLTDe69XggKP9LK/tIOKAhyYjfAJ2mbhWZcH558c9o0gzAEErkDhqh/wdog== +"@aragon/osx@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@aragon/osx/-/osx-1.4.0.tgz#36ff0671e214ff331625cbcc67cd7f80ce276153" + integrity sha512-FY9GizdhRXCLOuy/pADbLD3BsFZ9c+4JmvGC4DB7mauyck8yY2Itf4RhFG3X/afzVihex0bNths4wIf90ZaaUA== dependencies: + "@aragon/osx-commons-contracts" "1.4.0" "@ensdomains/ens-contracts" "0.0.11" - "@openzeppelin/contracts" "4.8.1" - "@openzeppelin/contracts-upgradeable" "4.8.1" + "@openzeppelin/contracts" "4.9.6" + "@openzeppelin/contracts-upgradeable" "4.9.6" "@aws-crypto/sha256-js@1.2.2": version "1.2.2" @@ -897,7 +897,7 @@ "@nomicfoundation/ethereumjs-rlp" "5.0.4" ethereum-cryptography "0.1.3" -"@nomicfoundation/hardhat-chai-matchers@^1.0.6": +"@nomicfoundation/hardhat-chai-matchers@^1.0.5": version "1.0.6" resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.6.tgz#72a2e312e1504ee5dd73fe302932736432ba96bc" integrity sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ== @@ -915,10 +915,20 @@ dependencies: ethereumjs-util "^7.1.4" -"@nomicfoundation/hardhat-toolbox@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.2.tgz#ec95f23b53cb4e71a1a7091380fa223aad18f156" - integrity sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg== +"@nomicfoundation/hardhat-verify@^1.0.4": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-1.1.1.tgz#6a433d777ce0172d1f0edf7f2d3e1df14b3ecfc1" + integrity sha512-9QsTYD7pcZaQFEA3tBb/D/oCStYDiEVDN7Dxeo/4SCyHRSm86APypxxdOMEPlGmXsAvd+p1j/dTODcpxb8aztA== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@ethersproject/address" "^5.0.2" + cbor "^8.1.0" + chalk "^2.4.2" + debug "^4.1.1" + lodash.clonedeep "^4.5.0" + semver "^6.3.0" + table "^6.8.0" + undici "^5.14.0" "@nomicfoundation/hardhat-verify@^2.0.8": version "2.0.12" @@ -999,7 +1009,7 @@ fs-extra "^7.0.1" node-fetch "^2.6.0" -"@nomiclabs/hardhat-ethers@^2.2.3": +"@nomiclabs/hardhat-ethers@^2.2.1": version "2.2.3" resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz#b41053e360c31a32c2640c9a45ee981a7e603fe0" integrity sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg== @@ -5209,6 +5219,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.startcase@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8" + integrity sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg== + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -6919,7 +6934,7 @@ solidity-coverage@^0.8.2: shelljs "^0.8.3" web3-utils "^1.3.6" -solidity-docgen@^0.6.0-beta.35: +solidity-docgen@^0.6.0-beta.29: version "0.6.0-beta.36" resolved "https://registry.yarnpkg.com/solidity-docgen/-/solidity-docgen-0.6.0-beta.36.tgz#9c76eda58580fb52e2db318c22fe3154e0c09dd1" integrity sha512-f/I5G2iJgU1h0XrrjRD0hHMr7C10u276vYvm//rw1TzFcYQ4xTOyAoi9oNAHRU0JU4mY9eTuxdVc2zahdMuhaQ== @@ -7624,16 +7639,16 @@ undici-types@~6.20.0: integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== undici@^5.14.0: - version "5.28.4" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" - integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + version "5.28.5" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.5.tgz#b2b94b6bf8f1d919bc5a6f31f2c01deb02e54d4b" + integrity sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA== dependencies: "@fastify/busboy" "^2.0.0" undici@^6.18.2, undici@^6.19.5: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.0.tgz#4b3d3afaef984e07b48e7620c34ed8a285ed4cd4" - integrity sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw== + version "6.21.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.1.tgz#336025a14162e6837e44ad7b819b35b6c6af0e05" + integrity sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ== unfetch@^4.2.0: version "4.2.0"