diff --git a/.env.example b/.env.example index d277cd52..2ffc6856 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,15 @@ # GENERAL ## The network used for testing purposes -NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon", "polygonMumbai", "base", "baseSepolia", "arbitrum", "arbitrumSepolia"] +NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon", "mumbai","baseMainnet", "baseGoerli", "baseSepolia", "arbitrum", "arbitrumSepolia"] # CONTRACTS ## One or multiple hex encoded private keys separated by commas `,` replacing the hardhat default accounts. PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Default hardhat account 0 private key. DON'T USE FOR DEPLOYMENTS -## Infura credentials -INFURA_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" +## Alchemy RPC endpoint credentials +ALCHEMY_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" ## Gas Reporting REPORT_GAS='true' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..82266ba3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,28 @@ +## Description + +Please include a summary of the change and be sure you follow the contributions rules we do provide [here](../CONTRIBUTIONS.md) + +Task ID: [OS-?](https://aragonassociation.atlassian.net/browse/OS-?) + +## Type of change + +See the framework lifecylce in `packages/contracts/docs/framework-lifecycle` to decide what kind of change this pull request is. + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + +## Checklist: + +- [ ] I have selected the correct base branch. +- [ ] I have performed a self-review of my own code. +- [ ] I have commented my code, particularly in hard-to-understand areas. +- [ ] My changes generate no new warnings. +- [ ] Any dependent changes have been merged and published in downstream modules. +- [ ] I ran all tests with success and extended them if necessary. +- [ ] I have updated the `CHANGELOG.md` file in the root folder. +- [ ] I have updated the `DEPLOYMENT_CHECKLIST` file in the root folder. +- [ ] I have updated the `UPDATE_CHECKLIST` file in the root folder. +- [ ] I have updated the Subgraph and added a QA URL to the description of this PR. diff --git a/.github/workflows/contracts-tests.yml b/.github/workflows/contracts-tests.yml index e4b3728b..fb34a28e 100644 --- a/.github/workflows/contracts-tests.yml +++ b/.github/workflows/contracts-tests.yml @@ -2,7 +2,7 @@ name: 'contracts' on: workflow_dispatch: - push: + pull_request: paths: - 'packages/contracts/**' - '.github/workflows/contracts-*.yml' @@ -31,10 +31,10 @@ jobs: - name: 'Build the contracts' run: 'yarn build' env: - INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} - name: 'Test the contracts and generate the coverage report' run: 'yarn coverage' env: NETWORK_NAME: ${{ vars.NETWORK_NAME }} - INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} diff --git a/.github/workflows/subgraph-tests.yml b/.github/workflows/subgraph-tests.yml index dd492b1e..ef013b86 100644 --- a/.github/workflows/subgraph-tests.yml +++ b/.github/workflows/subgraph-tests.yml @@ -2,7 +2,7 @@ name: 'subgraph' on: workflow_dispatch: - push: + pull_request: paths: - 'packages/subgraph/**' - 'packages/contracts/**' @@ -41,7 +41,7 @@ jobs: working-directory: packages/subgraph env: SUBGRAPH_NETWORK_NAME: ${{ vars.SUBGRAPH_NETWORK_NAME }} - INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} - name: 'Test the subgraph' run: yarn test diff --git a/.gitignore b/.gitignore index 8180f988..0e0fb157 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,9 @@ packages/subgraph/tests/.bin .pnp.* coverage.json +packages/contracts/createVersionProposalData*.json +packages/contracts/upgradeRepoProposalData*.json + packages/subgraph/deploy-output.txt packages/subgraph/subgraph.yaml packages/subgraph/tests/.latest.json diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md new file mode 100644 index 00000000..b46eca64 --- /dev/null +++ b/CONTRIBUTIONS.md @@ -0,0 +1,129 @@ +# Contribution Guide + +--- + +Thank you for being interested in contributing to Aragon! 🎉  We’re excited to have you building this vision with us. + +There are many ways to contribute! Some ideas are: + +- writing tutorials, blog posts or improving the documentation, +- submitting bug reports, +- submitting feature requests, +- refactoring code, +- or even writing new functionality to incorporate into the project. + +All community members are expected to adhere to our community guidelines. Please familiarize yourself with our [Terms and Conditions](https://aragon.org/terms-and-conditions) to ensure a welcoming and friendly environment across all our platforms. Adhering to these principles is essential. 💙 + +## Your first contribution + +Unsure where to begin contributing to Aragon? + +You can start with a [Good First Issue](https://github.com/aragon/admin-plugin/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). + +> Good first issues are usually for small features, additional tests, spelling / grammar fixes, formatting changes, or other clean up. + +Start small, pick a subject you care about, are familiar with, or want to learn. + +If you're not already familiar with git or Github, here are a couple of friendly tutorials: [First Contributions](https://github.com/firstcontributions/first-contributions), [Open Source Guide](https://opensource.guide/), and [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). + +Otherwise, you can also contribute through improving our documentation. You can find our documentation within the `docs` folder of each of our repositories. If you see something that could be better explained, or if you want to share better resources for each of the topics, we’d love to hear from you. Feel free to either submit a PR directly to the repo or simply [register here](https://aragonteam.typeform.com/to/QJyKtESU) and we’ll reach out. + +## How to file an issue or report a bug + +If you see a problem, you can report it in our [issue tracker](https://github.com/aragon/admin-plugin/issues) (or [here](https://aragonteam.typeform.com/to/QJyKtESU)). + +Please take a quick look to see if the issue doesn't already exist before filing yours. + +Do your best to include as many details as needed in order for someone else to fix the problem and resolve the issue. + +Some things that help us better understand: + +- Device: which device are you using, which version, how old is it, etc +- Browser: which browser are you using, which version.. +- Wallet: which wallet do you use to sign transactions +- Network: which network have you been testing on +- Logs: any specific transaction error or log that may support us in reviewing the error + +### If you find a security vulnerability, do NOT open an issue. Email [security@aragon.org](mailto:security@aragon.org) instead. + +In order to determine whether you are dealing with a security issue, ask yourself these two questions: + +- Can I access or steal something that's not mine, or access something I shouldn't have access to? +- Can I disable something for other people? + +If the answer to either of those two questions are "yes", then you're probably dealing with a security issue. Note that even if you answer "no" to both questions, you may still be dealing with a security issue, so if you're unsure, please send a email. + +## Fixing issues + +1. [Find an issue](https://github.com/aragon/admin-plugin/issues) that you are interested in. + - You may want to ask on the GitHub issue or in our [developer channel](https://discord.com/channels/672466989217873929/742442842474938478) if anyone has already started working on the issue. +2. Fork and clone a local copy of the repository. +3. Make the appropriate changes for the issue you are trying to address or the feature that you want to add. +4. Push the changes to the remote repository. +5. Submit a pull request to our repository on Github, explaining any changes and further questions you may have. + + We kindly ask that every PR follows this format and checks all checkboxes. + + ```markdown + ## Description + + ## Type of change + + ## Checklist + + [ ] I have selected the correct base branch. + [ ] I have performed a self-review of my own code. + [ ] I have commented my code, particularly in hard-to-understand areas. + [ ] I have made corresponding changes to the documentation. + [ ] My changes generate no new warnings. + [ ] Any dependent changes have been merged and published in downstream modules. + [ ] I ran all tests with success and extended them if necessary. + [ ] I have updated the CHANGELOG.md file in the root folder of the package after the [UPCOMING] title and before the latest version. + [ ] I have tested my code on the test network. + ``` + +6. Wait for the pull request to be reviewed by the team. +7. Make adjustments to the pull request if they were requested by the maintainer. +8. Celebrate your success after your pull request is merged! You will be able to claim your POAP for the hard work and join our Developer Inner Circle community. + +**Disclaimer** + +It's OK if your pull request is not perfect (no pull request is). The reviewer will be able to help you address any issues and improve it. + +### Tips and Tricks + +Windows users may need to install the [windows-build-tools](https://www.npmjs.com/package/windows-build-tools) or [Windows Subsystems for Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux) before installing this project's dependencies. + +### Documentation + +The documentation on how to run the code locally will be found within the `docs` folder of every repository. + +You can also access our [Developer Portal](https://devs.aragon.org) where you’ll learn about our architecture, how it works, and useful tutorials to get you started. + +### Style guide and development processes + +You can find our documentation [Style Guide here](https://www.notion.so/Documentation-Style-Guide-07c88cec18114b0aac88e8f0ba289976). + +For the frontends, we use [prettier](https://prettier.io/) and [eslint](https://eslint.org/) to automatically lint and format the project. + +For the contracts, we use [eth-lint](https://github.com/duaraghav8/Ethlint) and [prettier](https://prettier.io/) to automatically lint the project. + +For the SDK, we use [Prettier JS/TS formatter](https://prettier.io/docs/en/editors.html). + +Handy `npm` scripts (usually `npm run lint`) are provided at all levels to help automate these checks. + +We generally avoid adding external dependencies if they can be ported over easily, due to numerous NPM-related security issues in the past (e.g. `[event-stream](https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident)`). + +### Git branch convention + +Due to the unconventional release process of smart contracts, this repo utilizes a slightly different flow of git. + +The main challenge is that smart contracts should be heavily scrutinized before a release, making the process cumbersome and unlike the release process for "normal" dependencies or apps. [See here](https://forum.aragon.org/t/git-branch-convention-for-aragon-repos/298/3) for a more detailed explanation. + +Thus, we use the following convention: any change that can be release immediately, base it on the [develop branch](https://github.com/aragon/admin-plugin/tree/develop). + +As `next` becomes ready, merge `next` onto `master` with a rebase. + +## Community + +If you need help, please reach out to Aragon core contributors and community members in our [Discord](https://discord.gg/aragon-672466989217873929). We'd love to hear from you and know what you're working on! diff --git a/README.md b/README.md index ae1c72e1..2c2a4388 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,13 @@ -# Aragon OSX Plugin Template [![Hardhat][hardhat-badge]][hardhat] [![License: AGPL v3][license-badge]][license] +# Admin Plugin [![Hardhat][hardhat-badge]][hardhat] [![License: AGPL v3][license-badge]][license] [hardhat]: https://hardhat.org/ [hardhat-badge]: https://img.shields.io/badge/Built%20with-Hardhat-FFDB1C.svg [license]: https://opensource.org/licenses/AGPL-v3 [license-badge]: https://img.shields.io/badge/License-AGPL_v3-blue.svg -## Quickstart - -After [creating a new repository from this template](https://github.com/new?template_name=osx-plugin-template-hardhat&template_owner=aragon), cloning, and opening it in your IDE, run - -```sh -yarn install && cd packages/contracts && yarn install && yarn build && yarn typechain -``` - -Meanwhile, create an `.env` file from the `.env.example` file and put in the API keys for the services that you want to use. -You can now develop a plugin by changing the `src/MyPlugin.sol` and `src/MyPluginSetup.sol` files. You can directly import contracts from [Aragon OSx](https://github.com/aragon/osx) as well as OpenZeppelin's [openzeppelin-contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) and [openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable) that are already set up for you. - -```sol -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.17; - -import {IDAO, PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; -import {SafeCastUpgradeable} from '@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol'; - -contract MyPlugin is PluginUUPSUpgradeable { - //... -}; -``` - -The initial `MyPlugin` and `MyPluginSetup` example comes with unit test, integration test, and test helpers in the `package/contracts/test` folder that you can reuse. - -To build and test your contracts, run - -```sh -yarn clean && yarn build && yarn test -``` - ## Project -The root folder of the repo includes three subfolders: +The root folder of the repo includes two subfolders: ```markdown . @@ -99,7 +68,7 @@ yarn lint To be able to work on the contracts, make sure that you have created an `.env` file from the `.env.example` file and put in the API keys for -- [Infura](https://www.infura.io/) that we use as the web3 provider +- [Alchemy](https://www.alchemy.com) that we use as the web3 provider - [Alchemy Subgraphs](https://www.alchemy.com/subgraphs) that we use as the subgraph provider - the block explorer that you want to use depending on the networks that you want to deploy to @@ -202,15 +171,29 @@ Deploy the contracts to the local Hardhat Network (being forked from the network yarn deploy --tags CreateRepo,NewVersion ``` -This will create a plugin repo and publish the the first version (`v1.1`) of your plugin. +This will create a plugin repo and publish the first version (`v1.1`) of your plugin. +By adding the tag `TransferOwnershipToManagmentDao`, the `ROOT_PERMISSION_ID`, `MAINTAINER_PERMISSION_ID`, and +`UPGRADE_REPO_PERMISSION_ID` are granted to the management DAO and revoked from the deployer. +You can do this directly + +```sh +yarn deploy --tags CreateRepo,NewVersion,TransferOwnershipToManagmentDao +``` + +or at a later point by executing -Deploy the contracts to sepolia with +```sh +yarn deploy --tags TransferOwnershipToManagmentDao +``` + +To deploy the contracts to a production network use the `--network` option, for example ```sh -yarn deploy --network sepolia --tags CreateRepo,NewVersion,Verification +yarn deploy --network sepolia --tags CreateRepo,NewVersion,TransferOwnershipToManagmentDao,Verification ``` -This will create a plugin repo, publish the the first version (`v1.1`) of your plugin, and verfiy the contracts on sepolia. +This will create a plugin repo, publish the first version (`v1.1`) of your plugin, transfer permissions to the +management DAO, and lastly verfiy the contracts on sepolia. If you want to deploy a new version of your plugin afterwards (e.g., `1.2`), simply change the `VERSION` entry in the `packages/contracts/plugin-settings.ts` file and use @@ -218,6 +201,8 @@ If you want to deploy a new version of your plugin afterwards (e.g., `1.2`), sim yarn deploy --network sepolia --tags NewVersion,Verification ``` +Note, that if the deploying account doesn't own the repo anymore, this will create a `createVersionProposalData-sepolia.json` containing the data for a management DAO signer to create a proposal publishing a new version. + Note, that if you include the `CreateRepo` tag after you've created your plugin repo already, this part of the script will be skipped. #### Upgrading Your Plugin Repository @@ -237,6 +222,8 @@ yarn deploy --network sepolia --tags UpgradeRepo This will upgrade your plugin repo to the latest Aragon OSx protocol version implementation, which might include new features and security updates. **For this to work, make sure that you are using the latest version of [this repository](https://github.com/aragon/osx-plugin-template-hardhat) in your fork.** +Note, that if the deploying account doesn't own the repo anymore, this will create a `upgradeRepoProposalData-sepolia.json` containing the data for a management DAO signer to create a proposal upgrading the repo. + ## Subgraph ### Installing @@ -281,6 +268,10 @@ and finally the subgraph itself with yarn build:subgraph ``` +When running `yarn build`, it requires a plugin address, which is obtained from the configuration file located +at `subgraph/manifest/data/.json`, based on the network specified in your `.env` file under the `SUBGRAPH_NETWORK_NAME` variable. +You do not need to provide a plugin address for building or testing purposes, but it becomes necessary when deploying the subgraph. + During development of the subgraph, you might want to clean outdated files that were build, imported, and generated. To do this, run ```sh @@ -313,7 +304,8 @@ yarn coverage ### Deployment -To deploy the subgraph to the subgraph provider, write your intended subgraph name and version into the `SUBGRAPH_NAME` and `SUBGRAPH_VERSION` variables [in the `.env` file that you created in the beginning](environment-variables) and pick a network name `SUBGRAPH_NETWORK_NAME` [being supported by the subgraph provider](https://docs.alchemy.com/reference/supported-subgraph-chains). Then run +To deploy the subgraph to the subgraph provider, write your intended subgraph name and version into the `SUBGRAPH_NAME` and `SUBGRAPH_VERSION` variables [in the `.env` file that you created in the beginning](environment-variables) and pick a network name `SUBGRAPH_NETWORK_NAME` [being supported by the subgraph provider](https://docs.alchemy.com/reference/supported-subgraph-chains). Remember to place correctly the Plugin address on the network you are going to deploy to, you can do that by adding it on `subgraph/manifest/data/.json`. +Then run ```sh yarn deploy diff --git a/USAGE_GUIDE.md b/USAGE_GUIDE.md deleted file mode 100644 index 57bcc289..00000000 --- a/USAGE_GUIDE.md +++ /dev/null @@ -1,196 +0,0 @@ -# Template Usage Guide - -This guide will walk you through the process of writing the smart contract for a plugin and also creating the subgraph. It will cover the following topics: - -- [Dependency Installation](#dependency-installation) -- [Contracts](#contracts) - - - [Adapt the contracts](#adapt-template-contracts) - - [testing](#testing) - - [Unit Testing](#unit-testing) - - [Integration Testing](#integration-testing) - - [Deployment Scripts](#deployment-scripts) - -- [Subgraph](#subgraph) - - [Included scripts and `.env` file](#included-scripts-and-env-file) - - [Creating a Subgraph](#creating-a-subgraph) - - [`manifest/subgraph.placeholder.yaml`](#manifestsubgraphplaceholderyaml) - - [`schema.graphql`](#schemagraphql) - - [Handlers](#handlers) - - [testing](#testing-1) - -## Dependency Installation - -Before you begin, make sure you installed the necessary dependencies. -For detailed instructions, refer to the [README](README.md). - -# Contracts - -## Adapt template contracts - -This template contains the boilerplate and it uses `MyPlugin` as the contracts names, in order to adapt them according to your needs follow the following steps: - -1. Go to the `packages/contracts/src` folder and - - - adapt and rename the `MyPlugin.sol` plugin implementation contract (see [our docs](https://devs.aragon.org/docs/osx/how-to-guides/plugin-development/upgradeable-plugin/implementation)). - - adapt and rename the `MyPluginSetup.sol` plugin setup contract (see [our docs](https://devs.aragon.org/docs/osx/how-to-guides/plugin-development/upgradeable-plugin/setup)). - -2. adapt the release and build metadata for the plugin: - - - `build-metadata.json` and - - `release-metadata.json` - - in the same folder. [Check our documentation](https://devs.aragon.org/docs/osx/how-to-guides/plugin-development/publication/metadata) on what the metadata files are about. - -3. Finally, write the file names into the `packages/contracts/plugin-settings.ts` file and pick an ENS subdomain name according to the rules described in [our docs on ENS subdomain names](https://devs.aragon.org/docs/osx/how-it-works/framework/ens-names). - - ```ts - export const PLUGIN_CONTRACT_NAME = 'MyPlugin'; // Replace this with plugin contract name you chose - export const PLUGIN_SETUP_CONTRACT_NAME = 'MyPluginSetup'; // Replace this with the plugin setup contract name you chose. - export const PLUGIN_REPO_ENS_NAME = 'my'; // Pick an ENS subdomain name under that will live under `plugin.dao.eth` domain (e.g., 'my' will result in 'my.plugin.dao.eth') for the plugin repository that will be created during deployment. Make sure that the subdomain is not already taken on the chain(s) you are planning to deploy to. - ``` - - When deploying the first version of your plugin, you don't need to change the following lines. - - ```ts - export const VERSION = { - release: 1, // Increment this number ONLY if breaking/incompatible changes were made. Updates between releases are NOT possible. - build: 1, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. - }; - ``` - - If you deploy upcoming versions of your plugin, you must increment the build or release number accordingly (see [our docs on versioning your plugin](https://devs.aragon.org/docs/osx/how-to-guides/plugin-development/publication/versioning)). - -### Testing - -The `packages/contracts/test` folder contains pre-written unit and integration tests that you can adapt and extend. - -#### Unit Testing - -The `packages/contracts/test/10_unit-testing` folder contains - -- plugin implementation contract unit tests in the `11_plugin.ts` file -- containing plugin setup contract unit tests in the `12_plugin-setup.ts` file - -Adapt and extend the tests according to your changes and plugin features. - -#### Integration Testing - -The `packages/contracts/test/20_integration-testing` folder contains - -- deployment tests in the `21_deployment.ts` file - - testing that the deploy scripts publishes the plugin and sets the maintainer permissions correctly -- setup processing tests in the `22_setup-processing.ts` file - - testing that Aragon OSx `PluginSetupProcessor` can [apply a plugin setup](https://devs.aragon.org/docs/osx/how-it-works/framework/plugin-management/plugin-setup/#what-happens-during-the-preparation-application) correctly - -The prior tests if your plugin can be deployed - -### Deployment Scripts - -The standard deploy scripts in the `packages/contracts/deploy` should already be sufficient to deploy the first and upcoming versions of your plugin as well as upgrade your plugin repo. If your deployment has special requirements, adapt the files. - -- `00_info/01_account_info.ts` - - Prints information on the used networks and used account. -- `10_create_repo/11_create_repo.ts` - - Creates a plugin repo with an ENS subdomain name under the `plugin.dao.eth` parent domain if the ENS name is not taken already. -- `20_new_version/21_setup.ts` - - Deploys the plugin setup contract -- `20_new_version/22_setup_conclude.ts` - - Fetches the plugin setup and implementation contract and queues it for block explorer verification. -- `20_new_version/23_publish.ts` - - Publishes the plugin setup contract on the plugin repo created in `10_repo/11_create_repo.ts` -- `30_upgrade_repo/31_upgrade_repo.ts` - - Upgrades the plugin repo to the latest Aragon OSx protocol version. -- `40_conclude/41_conclude.ts` - - Prints information on the used account's balance after deployment. -- `50_verification/51_verify_contracts.ts` - - Verifies all deployed contracts. - -# Subgraph - -## Included scripts and `.env` file - -The current repo contains a set of 3 scripts to help you get started with your subgraph. These scripts are located in the `scripts` folder and are the following: - -- `build-manifest.sh`: Builds the `subgraph.yaml` file from the `subgraph.template.yaml` file. -- `build-subgraph.sh`: Builds the subgraph from the `subgraph.yaml` file generated by the `build-manifest.sh` script. -- `deploy-subgraph.sh`: Deploys the subgraph. - -These 3 scripts can be called with the `yarn` command: - -```bash -# Build the subgraph.yaml file -yarn build:manifest -# Build the subgraph -yarn build:subgraph -# Deploy the subgraph -yarn deploy -``` - -**REMEMBER**: You need to build the contracts before building the subgraph. You can do that by running `yarn build:contracts` - -If you use `yarn build` it will build the contracts, the subgraph.yaml file and the subgraph in that order. - -This scripts depend on the `.env` file located in the root of the repo. This file contains some variables that are relevant for this scripts. The variables are the following: - -```bash -# SUBGRAPH - -## The Graph credentials -GRAPH_KEY="zzzzzzzzzzzz" - -## Subgraph -SUBGRAPH_NAME="osx" -SUBGRAPH_VERSION="v1.0.0" -SUBGRAPH_NETWORK_NAME="mainnet" # ["mainnet", "goerli", "sepolia", "polygon", "polygonMumbai", "base", "baseSepolia", "arbitrum", "arbitrumSepolia"] -``` - -- `GRAPH_KEY`: This key will be used for deploying the subgraph. -- `SUBGRAPH_NAME`: The name of the subgraph. -- `SUBGRAPH_VERSION`: The version of the subgraph. -- `SUBGRAPH_NETWORK_NAME`: The network where the subgraph will be deployed. This will be used for generating the subgraph.yaml file. - -Editing this variables you can change how the subgraph is built and deployed. - -## Creating a Subgraph - -### `manifest/subgraph.placeholder.yaml` - -The first step to create a subgraph is to create or edit the `subgraph.placeholder.yaml` file. This file is located in the `manifest` folder. This file contains the template for the `subgraph.yaml` file. This file is used by the `build-manifest.sh` script to generate the `subgraph.yaml` file. You can find more information about the `subgraph.yaml` file [here](https://thegraph.com/docs/define-a-subgraph#the-subgraph-manifest). - -You will see that the `subgraph.placeholder.yaml` file contains some placeholders that will be replaced by the `build-manifest.sh` script. These placeholders are the following: - -- `{{dataSources.PluginSetupProcessors}}`: The object containing the processors for the plugin setup events. -- `{{startBlock}}`: The block number where the subgraph will start indexing. -- `{{address}}`: The address of the contract that will be indexed. -- `{{network}}`: The network where the contract is deployed. - -These placeholders are substituted using [mustache](https://mustache.github.io/). - -The files containing the replacement values are stored in `manifest/data`. Here you can find a JSON file for each network. The name of the file is the name of the network. For example, the file for the mainnet network is `mainnet.json`. This file contains a property called `dataSources` which holds the information about the contract that will be indexed. In this case, we are only indexing one contract but you can index multiple contracts by adding more objects to the `dataSources` object. - -### `schema.graphql` - -The second step to create a subgraph is to define the schema. The schema is defined in the `schema.graphql` file located in the `subgraph` folder. This file contains the GraphQL schema that will be used for querying the subgraph. The schema is defined using the GraphQL Schema Definition Language (SDL). You can find more information about it [here](https://graphql.org/learn/schema/). - -Here you can add your custom entities and how they are related to each other. You can find more information about the schema [here](https://thegraph.com/docs/define-a-subgraph#defining-the-schema). - -## Handlers - -Events handler functions are associated with the events in the `subgraph.yaml` file and defined in the `src` folder. - -The `src` folder contains a `plugin` and `osx` subfolder. -In `plugin.ts` inside the `plugin` folder, you can add plugin-related event handlers. -In `pluginSetupProcessor.ts` inside the `osx` folder, you can add plugin-setup-processor-related event handlers that your plugin might require. - -For the `MyPlugin` example, two handlers are already provided: - -- `handleInstallationPrepared`: Handles the `InstallationPrepared` event. This event is emitted when the installation is prepared. This event contains the `installationId` and the handler will create a new `PluginEntity` entity with the `installationId` as the id. -- `handleNumberStored`: Handles the `NumberStored` event. And stores the number in the `pluginEntity` entity in the `number` property. - -## Testing - -This template uses [matchstick-as](https://github.com/LimeChain/matchstick) framework for unit testing. -Similar to the `src` folder, the `test` folder contains a `plugin` and `osx` subfolder, where the tests for the event handlers from the previous section are written. - -In `plugin.test.ts` inside the `plugin` folder, you can test the plugin-related event handlers. In `pluginSetupProcessor.test.ts` inside the `osx` folder, you can test plugin-setup-processor-related event handlers that your plugin might require. diff --git a/package.json b/package.json index 26e1cb04..e5e7f657 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@aragon/osx-plugin-template", + "name": "@aragon/admin-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", diff --git a/packages/contracts/CHANGELOG.md b/packages/contracts/CHANGELOG.md new file mode 100644 index 00000000..fa2565a3 --- /dev/null +++ b/packages/contracts/CHANGELOG.md @@ -0,0 +1,17 @@ +# Admin Plugin + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to the [Aragon OSx Plugin Versioning Convention](https://devs.aragon.org/docs/osx/how-to-guides/plugin-development/publication/versioning). + +## v1.2 + +### Added + +- Copied files from [aragon/osx commit 1130df](https://github.com/aragon/osx/commit/1130dfce94fd294c4341e91a8f3faccc54cf43b7) + +### Changed + +- Used `ProxyLib` from `osx-commons-contracts` for the minimal proxy deployment in `AdminSetup`. +- Hard-coded the `bytes32 internal constant EXECUTE_PERMISSION_ID` constant in `AdminSetup` until it is available in `PermissionLib`. diff --git a/packages/contracts/deploy/20_new_version/21_setup.ts b/packages/contracts/deploy/20_new_version/21_setup.ts index 7ef97b63..a52a1b28 100644 --- a/packages/contracts/deploy/20_new_version/21_setup.ts +++ b/packages/contracts/deploy/20_new_version/21_setup.ts @@ -21,7 +21,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }); console.log( - `Deployed '${PLUGIN_SETUP_CONTRACT_NAME}' contract at '${res.address}'` + `Deployed contract '${PLUGIN_SETUP_CONTRACT_NAME}' at ${res.address}.` ); }; diff --git a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts index 1b514b68..8639c348 100644 --- a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts +++ b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts @@ -1,5 +1,5 @@ import {PLUGIN_SETUP_CONTRACT_NAME} from '../../plugin-settings'; -import {MyPluginSetup__factory, MyPlugin__factory} from '../../typechain'; +import {AdminSetup__factory, Admin__factory} from '../../typechain'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; @@ -17,12 +17,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Get the plugin setup address const setupDeployment = await deployments.get(PLUGIN_SETUP_CONTRACT_NAME); - const setup = MyPluginSetup__factory.connect( - setupDeployment.address, - deployer - ); + const setup = AdminSetup__factory.connect(setupDeployment.address, deployer); // Get the plugin implementation address - const implementation = MyPlugin__factory.connect( + const implementation = Admin__factory.connect( await setup.implementation(), deployer ); diff --git a/packages/contracts/deploy/20_new_version/23_publish.ts b/packages/contracts/deploy/20_new_version/23_publish.ts index ea146b9b..0dbba782 100644 --- a/packages/contracts/deploy/20_new_version/23_publish.ts +++ b/packages/contracts/deploy/20_new_version/23_publish.ts @@ -1,5 +1,6 @@ import { METADATA, + PLUGIN_CONTRACT_NAME, PLUGIN_REPO_ENS_SUBDOMAIN_NAME, PLUGIN_SETUP_CONTRACT_NAME, VERSION, @@ -7,6 +8,8 @@ import { import { findPluginRepo, getPastVersionCreatedEvents, + impersonatedManagementDaoSigner, + isLocal, pluginEnsDomain, } from '../../utils/helpers'; import { @@ -14,6 +17,7 @@ import { toHex, uploadToIPFS, } from '@aragon/osx-commons-sdk'; +import {writeFile} from 'fs/promises'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; @@ -77,25 +81,42 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ); } - // Create Version + if (setup == undefined || setup?.receipt == undefined) { + throw Error('setup deployment unavailable'); + } + + const isDeployerMaintainer = await pluginRepo.isGranted( + pluginRepo.address, + deployer.address, + PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, + [] + ); + + // If this is a local deployment and the deployer doesn't have `MAINTAINER_PERMISSION_ID` permission + // we impersonate the management DAO for integration testing purposes. + const signer = + isDeployerMaintainer || !isLocal(hre) + ? deployer + : await impersonatedManagementDaoSigner(hre); + + // Check if the signer has the permission to maintain the plugin repo if ( - await pluginRepo.callStatic.isGranted( + await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + signer.address, PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, [] ) ) { - const tx = await pluginRepo.createVersion( - VERSION.release, - setup.address, - toHex(buildMetadataURI), - toHex(releaseMetadataURI) - ); - - if (setup == undefined || setup?.receipt == undefined) { - throw Error('setup deployment unavailable'); - } + // Create the new version + const tx = await pluginRepo + .connect(signer) + .createVersion( + VERSION.release, + setup.address, + toHex(buildMetadataURI), + toHex(releaseMetadataURI) + ); await tx.wait(); @@ -110,8 +131,31 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { `Published ${PLUGIN_SETUP_CONTRACT_NAME} at ${setup.address} in PluginRepo ${PLUGIN_REPO_ENS_SUBDOMAIN_NAME} at ${pluginRepo.address}.` ); } else { - throw Error( - `The new version cannot be published because the deployer ('${deployer.address}') is lacking the ${PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID} permission on repo (${pluginRepo.address}).` + // 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 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.`, + proposalDescription: `Publishes the '${PLUGIN_SETUP_CONTRACT_NAME}' deployed at '${setup.address}' + as v${VERSION.release}.${VERSION.build} in the '${ensDomain}' plugin repo at '${pluginRepo.address}', + with release metadata '${releaseMetadataURI}' and (immutable) build metadata '${buildMetadataURI}'.`, + actions: [ + { + to: pluginRepo.address, + createVersion: { + _release: VERSION.release, + _pluginSetup: setup.address, + _buildMetadata: toHex(buildMetadataURI), + _releaseMetadata: toHex(releaseMetadataURI), + }, + }, + ], + }; + + 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}.` ); } }; diff --git a/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts b/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts index f862559c..d8cf19c8 100644 --- a/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts +++ b/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts @@ -1,4 +1,9 @@ -import {findPluginRepo, getProductionNetworkName} from '../../utils/helpers'; +import { + findPluginRepo, + getProductionNetworkName, + impersonatedManagementDaoSigner, + isLocal, +} from '../../utils/helpers'; import { getLatestNetworkDeployment, getNetworkNameByAlias, @@ -9,12 +14,17 @@ import { } from '@aragon/osx-commons-sdk'; import {PluginRepo__factory} from '@aragon/osx-ethers'; import {BytesLike} from 'ethers'; +import {writeFile} from 'fs/promises'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; type SemVer = [number, number, number]; +/** + * Upgrades the plugin repo to the latest implementation. + * @param {HardhatRuntimeEnvironment} hre + */ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const [deployer] = await hre.ethers.getSigners(); const productionNetworkName: string = getProductionNetworkName(hre); @@ -74,11 +84,25 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { */ const initializeFromCalldata: BytesLike = []; - // Check if deployer has the permission to upgrade the plugin repo + const isDeployerUpgrader = await pluginRepo.isGranted( + pluginRepo.address, + deployer.address, + PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, + [] + ); + + // If this is a local deployment and the deployer doesn't have `UPGRADE_REPO_PERMISSION_ID` permission + // we impersonate the management DAO for integration testing purposes. + const signer = + isDeployerUpgrader || !isLocal(hre) + ? deployer + : await impersonatedManagementDaoSigner(hre); + + // Check if the signer has the permission to upgrade the plugin repo if ( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + signer.address, PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, [] ) @@ -94,9 +118,35 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await pluginRepo.upgradeTo(latestPluginRepoImplementation.address); } } else { - throw Error( - `The new version cannot be published because the deployer ('${deployer.address}') - is lacking the ${PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID} permission.` + // The deployer does not have `UPGRADE_REPO_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 upgradeAction = + initializeFromCalldata.length === 0 + ? { + to: pluginRepo.address, + upgradeTo: { + NewImplementation: latestPluginRepoImplementation.address, + }, + } + : { + to: pluginRepo.address, + upgradeToAndCall: { + NewImplementation: latestPluginRepoImplementation.address, + Data: initializeFromCalldata, + PayableAmount: 0, + }, + }; + const data = { + proposalTitle: `Upgrade the '${ensDomain}' plugin repo`, + proposalSummary: `Upgrades '${ensDomain}' plugin repo at '${pluginRepo.address}',' plugin in the '${ensDomain}' plugin repo.`, + proposalDescription: `TODO: Describe the changes to the 'PluginRepo' implementation.`, + actions: [upgradeAction], + }; + + const path = `./upgradeRepoProposalData-${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 'upgradeTo' or 'upgradeToAndCall' function on the ${ensDomain} plugin repo deployed at ${pluginRepo.address}.` ); } }; diff --git a/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts b/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts new file mode 100644 index 00000000..aac32467 --- /dev/null +++ b/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts @@ -0,0 +1,111 @@ +import {findPluginRepo, getManagementDao} from '../../utils/helpers'; +import { + DAO_PERMISSIONS, + Operation, + PERMISSION_MANAGER_FLAGS, + PLUGIN_REPO_PERMISSIONS, +} from '@aragon/osx-commons-sdk'; +import {DAOStructs} from '@aragon/osx-ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import path from 'path'; + +/** + * Creates a plugin repo under Aragon's ENS base domain with subdomain requested in the `./plugin-settings.ts` file. + * @param {HardhatRuntimeEnvironment} hre + */ +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + // Get PluginRepo + const {pluginRepo, ensDomain} = await findPluginRepo(hre); + if (pluginRepo === null) { + throw `PluginRepo '${ensDomain}' does not exist yet.`; + } + + // Get the management DAO address + const managementDao = await getManagementDao(hre); + const [deployer] = await hre.ethers.getSigners(); + + console.log( + `Transferring ownership of the '${ensDomain}' plugin repo at '${pluginRepo.address}' from the deployer '${deployer.address}' to the management DAO at '${managementDao.address}'...` + ); + + const permissions: DAOStructs.MultiTargetPermissionStruct[] = [ + // Grant to the managment DAO + { + operation: Operation.Grant, + where: pluginRepo.address, + who: managementDao.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, + }, + { + operation: Operation.Grant, + where: pluginRepo.address, + who: managementDao.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, + }, + { + operation: Operation.Grant, + where: pluginRepo.address, + who: managementDao.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: DAO_PERMISSIONS.ROOT_PERMISSION_ID, + }, + // Revoke from deployer + { + operation: Operation.Revoke, + where: pluginRepo.address, + who: deployer.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, + }, + { + operation: Operation.Revoke, + where: pluginRepo.address, + who: deployer.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, + }, + { + operation: Operation.Revoke, + where: pluginRepo.address, + who: deployer.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: DAO_PERMISSIONS.ROOT_PERMISSION_ID, + }, + ]; + + await pluginRepo.connect(deployer).applyMultiTargetPermissions(permissions); +}; + +export default func; +func.tags = ['TransferOwnershipToManagmentDao']; + +/** + * Skips the transfer of ownership if it has already been transferred to the management DAO + * @param {HardhatRuntimeEnvironment} hre + */ +func.skip = async (hre: HardhatRuntimeEnvironment) => { + console.log(`\n🏗️ ${path.basename(__filename)}:`); + + const {pluginRepo, ensDomain} = await findPluginRepo(hre); + if (pluginRepo === null) { + throw `PluginRepo '${ensDomain}' does not exist yet.`; + } + const managementDao = await getManagementDao(hre); + + const mgmtDaoHasRootPerm = await pluginRepo.isGranted( + pluginRepo.address, + managementDao.address, + DAO_PERMISSIONS.ROOT_PERMISSION_ID, + [] + ); + + if (mgmtDaoHasRootPerm) + console.log( + `The ownership of the plugin repo '${ensDomain}' has already been transferred to the management DAO. Skipping...` + ); + + return mgmtDaoHasRootPerm; +}; diff --git a/packages/contracts/deploy/40_conclude/41_conclude.ts b/packages/contracts/deploy/40_conclude/42_info.ts similarity index 100% rename from packages/contracts/deploy/40_conclude/41_conclude.ts rename to packages/contracts/deploy/40_conclude/42_info.ts diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 3608e253..d1e9092a 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -1,7 +1,6 @@ import { - NetworkConfigs, - NetworkConfig, - networks, + addRpcUrlToNetwork, + networks as osxCommonsConfigNetworks, SupportedNetworks, } from '@aragon/osx-commons-configs'; import '@nomicfoundation/hardhat-chai-matchers'; @@ -18,14 +17,18 @@ import { HardhatNetworkAccountsUserConfig, HardhatRuntimeEnvironment, } from 'hardhat/types'; +import type {NetworkUserConfig} from 'hardhat/types'; import {resolve} from 'path'; import 'solidity-coverage'; const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || '../../.env'; dotenvConfig({path: resolve(__dirname, dotenvConfigPath), override: true}); -if (!process.env.INFURA_API_KEY) { - throw new Error('INFURA_API_KEY in .env not set'); +// check alchemy Api key existence +if (process.env.ALCHEMY_API_KEY) { + addRpcUrlToNetwork(process.env.ALCHEMY_API_KEY); +} else { + throw new Error('ALCHEMY_API_KEY in .env not set'); } // Fetch the accounts specified in the .env file @@ -67,15 +70,10 @@ function getHardhatNetworkAccountsConfig( return accountsConfig; } -type HardhatNetworksExtension = NetworkConfig & { - accounts?: string[]; -}; - // Add the accounts specified in the `.env` file to the networks from osx-commons-configs -const osxCommonsConfigNetworks: NetworkConfigs = - networks; +const networks: {[index: string]: NetworkUserConfig} = osxCommonsConfigNetworks; for (const network of Object.keys(networks) as SupportedNetworks[]) { - osxCommonsConfigNetworks[network].accounts = specifiedAccounts(); + networks[network].accounts = specifiedAccounts(); } // Extend HardhatRuntimeEnvironment @@ -110,7 +108,7 @@ const config: HardhatUserConfig = { Object.keys(namedAccounts).length ), }, - ...osxCommonsConfigNetworks, + ...networks, }, defaultNetwork: 'hardhat', diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 4587efee..e0832a0e 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,5 +1,5 @@ { - "name": "@aragon/osx-plugin-template-contracts", + "name": "@aragon/admin-plugin-contracts", "version": "1.0.0", "license": "AGPL-3.0-or-later", "scripts": { @@ -19,10 +19,9 @@ "@openzeppelin/contracts-upgradeable": "^4.9.5" }, "devDependencies": { - "@aragon/osx-commons-configs": "0.1.0", + "@aragon/osx-commons-configs": "0.4.0", "@aragon/osx-ethers": "1.4.0-alpha.0", "@aragon/osx-commons-sdk": "0.0.1-alpha.5", - "@aragon/osx-artifacts": "1.3.1", "@ethersproject/abi": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", diff --git a/packages/contracts/plugin-settings.ts b/packages/contracts/plugin-settings.ts index f19ee5bf..1d693e38 100644 --- a/packages/contracts/plugin-settings.ts +++ b/packages/contracts/plugin-settings.ts @@ -2,16 +2,17 @@ import buildMetadata from './src/build-metadata.json'; import releaseMetadata from './src/release-metadata.json'; import {VersionTag} from '@aragon/osx-commons-sdk'; -export const PLUGIN_CONTRACT_NAME = 'MyPlugin'; // This must match the filename `packages/contracts/src/MyPlugin.sol` and the contract name `MyPlugin` within. -export const PLUGIN_SETUP_CONTRACT_NAME = 'MyPluginSetup'; // This must match the filename `packages/contracts/src/MyPluginSetup.sol` and the contract name `MyPluginSetup` within. -export const PLUGIN_REPO_ENS_SUBDOMAIN_NAME = 'my'; // This will result in the ENS domain name 'my.plugin.dao.eth' +export const PLUGIN_CONTRACT_NAME = 'Admin'; +export const PLUGIN_SETUP_CONTRACT_NAME = 'AdminSetup'; +export const PLUGIN_REPO_ENS_SUBDOMAIN_NAME = 'admin'; // 'admin.plugin.dao.eth' +// Specify the version of your plugin that you are currently working on. The first version is v1.1. +// For more details, visit https://devs.aragon.org/docs/osx/how-it-works/framework/plugin-management/plugin-repo. export const VERSION: VersionTag = { release: 1, // Increment this number ONLY if breaking/incompatible changes were made. Updates between releases are NOT possible. - build: 1, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. + build: 2, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. }; -/* DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING */ export const METADATA = { build: buildMetadata, release: releaseMetadata, diff --git a/packages/contracts/src/Admin.sol b/packages/contracts/src/Admin.sol new file mode 100644 index 00000000..4addfd83 --- /dev/null +++ b/packages/contracts/src/Admin.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {SafeCastUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; + +import {IMembership} from "@aragon/osx-commons-contracts/src/plugin/extensions/membership/IMembership.sol"; + +// solhint-disable-next-line max-line-length +import {ProposalUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol"; +import {PluginCloneable} from "@aragon/osx-commons-contracts/src/plugin/PluginCloneable.sol"; +import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; + +/// @title Admin +/// @author Aragon Association - 2022-2023 +/// @notice The admin governance plugin giving execution permission on the DAO to a single address. +/// @dev v1.2 (Release 1, Build 2) +/// @custom:security-contact sirt@aragon.org +contract Admin is IMembership, PluginCloneable, ProposalUpgradeable { + using SafeCastUpgradeable for uint256; + + /// @notice The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract. + bytes4 internal constant ADMIN_INTERFACE_ID = + this.initialize.selector ^ this.executeProposal.selector; + + /// @notice The ID of the permission required to call the `executeProposal` function. + bytes32 public constant EXECUTE_PROPOSAL_PERMISSION_ID = + keccak256("EXECUTE_PROPOSAL_PERMISSION"); + + /// @notice Initializes the contract. + /// @param _dao The associated DAO. + /// @dev This method is required to support [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167). + function initialize(IDAO _dao) external initializer { + __PluginCloneable_init(_dao); + + emit MembershipContractAnnounced({definingContract: address(_dao)}); + } + + /// @notice Checks if this or the parent contract supports an interface by its ID. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface( + bytes4 _interfaceId + ) public view override(PluginCloneable, ProposalUpgradeable) returns (bool) { + return + _interfaceId == ADMIN_INTERFACE_ID || + _interfaceId == type(IMembership).interfaceId || + super.supportsInterface(_interfaceId); + } + + /// @inheritdoc IMembership + function isMember(address _account) external view returns (bool) { + return + dao().hasPermission({ + _where: address(this), + _who: _account, + _permissionId: EXECUTE_PROPOSAL_PERMISSION_ID, + _data: bytes("") + }); + } + + /// @notice Creates and executes a new proposal. + /// @param _metadata The metadata of the proposal. + /// @param _actions The actions to be executed. + /// @param _allowFailureMap A bitmap allowing the proposal to succeed, even if individual actions might revert. + /// If the bit at index `i` is 1, the proposal succeeds even if the `i`th action reverts. A failure map value + // of 0 requires every action to not revert. + function executeProposal( + bytes calldata _metadata, + IDAO.Action[] calldata _actions, + uint256 _allowFailureMap + ) external auth(EXECUTE_PROPOSAL_PERMISSION_ID) { + uint64 currentTimestamp64 = block.timestamp.toUint64(); + + uint256 proposalId = _createProposal({ + _creator: _msgSender(), + _metadata: _metadata, + _startDate: currentTimestamp64, + _endDate: currentTimestamp64, + _actions: _actions, + _allowFailureMap: _allowFailureMap + }); + _executeProposal(dao(), proposalId, _actions, _allowFailureMap); + } +} diff --git a/packages/contracts/src/AdminSetup.sol b/packages/contracts/src/AdminSetup.sol new file mode 100644 index 00000000..f8f357c2 --- /dev/null +++ b/packages/contracts/src/AdminSetup.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {IPluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol"; +import {PluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/PluginSetup.sol"; +import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; +import {ProxyLib} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyLib.sol"; +import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; + +import {Admin} from "./Admin.sol"; + +/// @title AdminAddressSetup +/// @author Aragon Association - 2022-2023 +/// @notice The setup contract of the `Admin` plugin. +/// @dev v1.2 (Release 1, Build 2) +/// @custom:security-contact sirt@aragon.org +contract AdminSetup is PluginSetup { + using ProxyLib for address; + + // TODO This permission identifier has to be moved inside `PermissionLib` as per task OS-954. + /// @notice The ID of the permission required to call the `execute` function. + bytes32 internal constant EXECUTE_PERMISSION_ID = keccak256("EXECUTE_PERMISSION"); + + /// @notice Thrown if the admin address is zero. + /// @param admin The admin address. + error AdminAddressInvalid(address admin); + + /// @notice The constructor setting the `Admin` implementation contract to clone from. + constructor() PluginSetup(address(new Admin())) {} + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes calldata _data + ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { + // Decode `_data` to extract the params needed for cloning and initializing the `Admin` plugin. + address admin = abi.decode(_data, (address)); + + if (admin == address(0)) { + revert AdminAddressInvalid({admin: admin}); + } + + // Clone and initialize the plugin contract. + bytes memory initData = abi.encodeCall(Admin.initialize, (IDAO(_dao))); + plugin = IMPLEMENTATION.deployMinimalProxy(initData); + + // Prepare permissions + PermissionLib.MultiTargetPermission[] + memory permissions = new PermissionLib.MultiTargetPermission[](2); + + // Grant `ADMIN_EXECUTE_PERMISSION` of the plugin to the admin. + permissions[0] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: plugin, + who: admin, + condition: PermissionLib.NO_CONDITION, + permissionId: Admin(plugin).EXECUTE_PROPOSAL_PERMISSION_ID() + }); + + // Grant `EXECUTE_PERMISSION` on the DAO to the plugin. + permissions[1] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: _dao, + who: plugin, + condition: PermissionLib.NO_CONDITION, + permissionId: EXECUTE_PERMISSION_ID + }); + + preparedSetupData.permissions = permissions; + } + + /// @inheritdoc IPluginSetup + /// @dev Currently, there is no reliable way to revoke the `ADMIN_EXECUTE_PERMISSION_ID` from all addresses + /// it has been granted to. Accordingly, only the `EXECUTE_PERMISSION_ID` is revoked for this uninstallation. + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external pure returns (PermissionLib.MultiTargetPermission[] memory permissions) { + // Prepare permissions + permissions = new PermissionLib.MultiTargetPermission[](1); + + permissions[0] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _dao, + who: _payload.plugin, + condition: PermissionLib.NO_CONDITION, + permissionId: EXECUTE_PERMISSION_ID + }); + } +} diff --git a/packages/contracts/src/MyPlugin.sol b/packages/contracts/src/MyPlugin.sol deleted file mode 100644 index ec8739f7..00000000 --- a/packages/contracts/src/MyPlugin.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.8; - -import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/PluginUUPSUpgradeable.sol"; -import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; - -/// @title MyPlugin -/// @dev Release 1, Build 1 -contract MyPlugin is PluginUUPSUpgradeable { - /// @notice The ID of the permission required to call the `storeNumber` function. - bytes32 public constant STORE_PERMISSION_ID = keccak256("STORE_PERMISSION"); - - uint256 public number; // added in build 1 - - /// @notice Emitted when a number is stored. - /// @param number The number. - event NumberStored(uint256 number); - - /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized. - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the plugin when build 1 is installed. - /// @param _number The number to be stored. - function initialize(IDAO _dao, uint256 _number) external initializer { - __PluginUUPSUpgradeable_init(_dao); - number = _number; - - emit NumberStored({number: _number}); - } - - /// @notice Stores a new number to storage. Caller needs STORE_PERMISSION. - /// @param _number The number to be stored. - function storeNumber(uint256 _number) external auth(STORE_PERMISSION_ID) { - number = _number; - - emit NumberStored({number: _number}); - } -} diff --git a/packages/contracts/src/MyPluginSetup.sol b/packages/contracts/src/MyPluginSetup.sol deleted file mode 100644 index 01993a20..00000000 --- a/packages/contracts/src/MyPluginSetup.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; -import {ProxyLib} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyLib.sol"; -import {PluginUpgradeableSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/PluginUpgradeableSetup.sol"; -import {IPluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol"; -import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; - -import {MyPlugin} from "./MyPlugin.sol"; - -/// @title MyPluginSetup -/// @dev Release 1, Build 1 -contract MyPluginSetup is PluginUpgradeableSetup { - using ProxyLib for address; - - /// @notice Constructs the `PluginUpgradeableSetup` by storing the `MyPlugin` implementation address. - /// @dev The implementation address is used to deploy UUPS proxies referencing it and - /// to verify the plugin on the respective block explorers. - constructor() PluginUpgradeableSetup(address(new MyPlugin())) {} - - /// @notice The ID of the permission required to call the `storeNumber` function. - bytes32 internal constant STORE_PERMISSION_ID = keccak256("STORE_PERMISSION"); - - /// @inheritdoc IPluginSetup - function prepareInstallation( - address _dao, - bytes memory _data - ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { - uint256 number = abi.decode(_data, (uint256)); - - plugin = IMPLEMENTATION.deployUUPSProxy( - abi.encodeCall(MyPlugin.initialize, (IDAO(_dao), number)) - ); - - PermissionLib.MultiTargetPermission[] - memory permissions = new PermissionLib.MultiTargetPermission[](1); - - permissions[0] = PermissionLib.MultiTargetPermission({ - operation: PermissionLib.Operation.Grant, - where: plugin, - who: _dao, - condition: PermissionLib.NO_CONDITION, - permissionId: STORE_PERMISSION_ID - }); - - preparedSetupData.permissions = permissions; - } - - /// @inheritdoc IPluginSetup - /// @dev The default implementation for the initial build 1 that reverts because no earlier build exists. - function prepareUpdate( - address _dao, - uint16 _fromBuild, - SetupPayload calldata _payload - ) external pure virtual returns (bytes memory, PreparedSetupData memory) { - (_dao, _fromBuild, _payload); - revert InvalidUpdatePath({fromBuild: 0, thisBuild: 1}); - } - - /// @inheritdoc IPluginSetup - function prepareUninstallation( - address _dao, - SetupPayload calldata _payload - ) external pure returns (PermissionLib.MultiTargetPermission[] memory permissions) { - permissions = new PermissionLib.MultiTargetPermission[](1); - - permissions[0] = PermissionLib.MultiTargetPermission({ - operation: PermissionLib.Operation.Revoke, - where: _payload.plugin, - who: _dao, - condition: PermissionLib.NO_CONDITION, - permissionId: STORE_PERMISSION_ID - }); - } -} diff --git a/packages/contracts/src/build-metadata.json b/packages/contracts/src/build-metadata.json index ba36b8b4..c479f4b5 100644 --- a/packages/contracts/src/build-metadata.json +++ b/packages/contracts/src/build-metadata.json @@ -1,21 +1,20 @@ { "ui": {}, - "change": "Initial build.", + "change": "v1.2\n [PLACEHOLDER for describing the changes in the upcoming build.]", "pluginSetup": { "prepareInstallation": { - "description": "The information required for the installation of build 1.", + "description": "The information required for the installation.", "inputs": [ { - "name": "number", - "type": "uint256", - "internalType": "uint256", - "description": "The initial number to be stored." + "internalType": "address", + "name": "admin", + "type": "address", + "description": "The address of the admin account receiving the `EXECUTE_PERMISSION_ID` permission." } ] }, - "prepareUpdate": {}, "prepareUninstallation": { - "description": "The information required for the uninstallation of build 1.", + "description": "No input is required for the uninstallation.", "inputs": [] } } diff --git a/packages/contracts/src/mocks/DAOMock.sol b/packages/contracts/src/mocks/DAOMock.sol deleted file mode 100644 index 008fcecf..00000000 --- a/packages/contracts/src/mocks/DAOMock.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; -import {IPermissionCondition} from "@aragon/osx-commons-contracts/src/permission/condition/IPermissionCondition.sol"; -import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; - -contract DAOMock is IDAO { - address internal constant NO_CONDITION = address(0); - - event Granted( - bytes32 indexed permissionId, - address indexed here, - address where, - address indexed who, - address condition - ); - - event Revoked( - bytes32 indexed permissionId, - address indexed here, - address where, - address indexed who - ); - - bool public hasPermissionReturnValueMock; - - function setHasPermissionReturnValueMock(bool _hasPermissionReturnValueMock) external { - hasPermissionReturnValueMock = _hasPermissionReturnValueMock; - } - - function hasPermission( - address _where, - address _who, - bytes32 _permissionId, - bytes memory _data - ) external view override returns (bool) { - (_where, _who, _permissionId, _data); - return hasPermissionReturnValueMock; - } - - function applyMultiTargetPermissions( - PermissionLib.MultiTargetPermission[] calldata _items - ) external { - for (uint256 i; i < _items.length; ) { - PermissionLib.MultiTargetPermission memory item = _items[i]; - - if (item.operation == PermissionLib.Operation.Grant) { - grant({_where: item.where, _who: item.who, _permissionId: item.permissionId}); - } else if (item.operation == PermissionLib.Operation.Revoke) { - revoke({_where: item.where, _who: item.who, _permissionId: item.permissionId}); - } else if (item.operation == PermissionLib.Operation.GrantWithCondition) { - grantWithCondition({ - _where: item.where, - _who: item.who, - _permissionId: item.permissionId, - _condition: IPermissionCondition(item.condition) - }); - } - - unchecked { - ++i; - } - } - } - - function grant(address _where, address _who, bytes32 _permissionId) public { - (_where, _who, _permissionId); - - emit Granted({ - permissionId: _permissionId, - here: msg.sender, - where: _where, - who: _who, - condition: NO_CONDITION - }); - } - - function revoke(address _where, address _who, bytes32 _permissionId) public { - (_where, _who, _permissionId); - - emit Revoked({permissionId: _permissionId, here: msg.sender, where: _where, who: _who}); - } - - function grantWithCondition( - address _where, - address _who, - bytes32 _permissionId, - IPermissionCondition _condition - ) public { - emit Granted({ - permissionId: _permissionId, - here: msg.sender, - where: _where, - who: _who, - condition: address(_condition) - }); - } - - function getTrustedForwarder() public pure override returns (address) { - return address(0); - } - - function setTrustedForwarder(address _trustedForwarder) external pure override { - (_trustedForwarder); - } - - function setMetadata(bytes calldata _metadata) external pure override { - (_metadata); - } - - function execute( - bytes32 callId, - Action[] memory _actions, - uint256 allowFailureMap - ) external override returns (bytes[] memory execResults, uint256 failureMap) { - emit Executed(msg.sender, callId, _actions, allowFailureMap, failureMap, execResults); - } - - function deposit( - address _token, - uint256 _amount, - string calldata _reference - ) external payable override { - (_token, _amount, _reference); - } - - function setSignatureValidator(address _signatureValidator) external pure override { - (_signatureValidator); - } - - function isValidSignature( - bytes32 _hash, - bytes memory _signature - ) external pure override returns (bytes4) { - (_hash, _signature); - return 0x0; - } - - function registerStandardCallback( - bytes4 _interfaceId, - bytes4 _callbackSelector, - bytes4 _magicNumber - ) external pure override { - (_interfaceId, _callbackSelector, _magicNumber); - } -} diff --git a/packages/contracts/src/mocks/Migration.sol b/packages/contracts/src/mocks/Migration.sol new file mode 100644 index 00000000..93db8ab6 --- /dev/null +++ b/packages/contracts/src/mocks/Migration.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +/** + * @title Migration + * + * @dev This file allows importing contracts to obtain compiler artifacts for testing purposes. + * + * After a contract is imported here and the project is compiled, an associated artifact will be + * generated inside artifacts/@aragon/{version-name}/*, + * and TypeChain typings will be generated inside typechain/osx-version/{version-name}/* + * for type-safe interactions with the contract in our tests. + */ + +/* solhint-disable no-unused-import */ + +import {ProxyFactory} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory.sol"; + +/* solhint-enable no-unused-import */ diff --git a/packages/contracts/src/release-metadata.json b/packages/contracts/src/release-metadata.json index a99ed49b..698ab04f 100644 --- a/packages/contracts/src/release-metadata.json +++ b/packages/contracts/src/release-metadata.json @@ -1,5 +1,5 @@ { - "name": "PluginName", - "description": "Description.", + "name": "Admin", + "description": "", "images": {} } diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index 715b0922..8c429ac9 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -1,102 +1,434 @@ +import {createDaoProxy} from '../20_integration-testing/test-helpers'; import {PLUGIN_CONTRACT_NAME} from '../../plugin-settings'; import { - DAOMock, - DAOMock__factory, - MyPlugin, - MyPlugin__factory, + Admin, + Admin__factory, + IERC165Upgradeable__factory, + IMembership__factory, + IPlugin__factory, + IProposal__factory, + IProtocolVersion__factory, + ProxyFactory__factory, } from '../../typechain'; -import '../../typechain/src/MyPlugin'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; +import {ProposalCreatedEvent} from '../../typechain/src/Admin'; +import { + ADMIN_INTERFACE, + EXECUTE_PROPOSAL_PERMISSION_ID, +} from '../admin-constants'; +import { + findEvent, + findEventTopicLog, + proposalIdToBytes32, + getInterfaceId, + DAO_PERMISSIONS, +} from '@aragon/osx-commons-sdk'; +import {DAO, DAOEvents, DAOStructs} from '@aragon/osx-ethers'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {BigNumber} from 'ethers'; -import {ethers, upgrades} from 'hardhat'; - -export type InitData = {number: BigNumber}; -export const defaultInitData: InitData = { - number: BigNumber.from(123), -}; - -export const STORE_PERMISSION_ID = ethers.utils.id('STORE_PERMISSION'); - -type FixtureResult = { - deployer: SignerWithAddress; - alice: SignerWithAddress; - bob: SignerWithAddress; - plugin: MyPlugin; - daoMock: DAOMock; -}; - -async function fixture(): Promise { - const [deployer, alice, bob] = await ethers.getSigners(); - const daoMock = await new DAOMock__factory(deployer).deploy(); - const plugin = (await upgrades.deployProxy( - new MyPlugin__factory(deployer), - [daoMock.address, defaultInitData.number], - { - kind: 'uups', - initializer: 'initialize', - unsafeAllow: ['constructor'], - constructorArgs: [], - } - )) as unknown as MyPlugin; - - return {deployer, alice, bob, plugin, daoMock}; -} +import {ethers} from 'hardhat'; describe(PLUGIN_CONTRACT_NAME, function () { describe('initialize', async () => { it('reverts if trying to re-initialize', async () => { - const {plugin, daoMock} = await loadFixture(fixture); - await expect( - plugin.initialize(daoMock.address, defaultInitData.number) - ).to.be.revertedWith('Initializable: contract is already initialized'); + const {initializedPlugin: plugin, dao} = await loadFixture(fixture); + await expect(plugin.initialize(dao.address)).to.be.revertedWith( + 'Initializable: contract is already initialized' + ); }); - it('stores the number', async () => { - const {plugin} = await loadFixture(fixture); + it('emits the `MembershipContractAnnounced` event', async () => { + const {uninitializedPlugin: plugin, dao} = await loadFixture(fixture); + await expect(plugin.initialize(dao.address)) + .to.emit( + plugin, + plugin.interface.getEvent('MembershipContractAnnounced').name + ) + .withArgs(dao.address); + }); + }); + + describe('membership', async () => { + it('returns the admins having the `EXECUTE_PROPOSAL_PERMISSION_ID` permission as members', async () => { + const { + alice, + bob, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); - expect(await plugin.number()).to.equal(defaultInitData.number); + expect(await plugin.isMember(alice.address)).to.be.true; // Alice has `EXECUTE_PROPOSAL_PERMISSION_ID` + expect(await plugin.isMember(bob.address)).to.be.false; // Bob has not }); }); - describe('storeNumber', async () => { - it('reverts if sender lacks permission', async () => { - const newNumber = BigNumber.from(456); + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); + expect(await plugin.supportsInterface('0xffffffff')).to.be.false; + }); - const {bob, plugin, daoMock} = await loadFixture(fixture); + it('supports the `IERC165Upgradeable` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); + const iface = IERC165Upgradeable__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); - expect(await daoMock.hasPermissionReturnValueMock()).to.equal(false); + it('supports the `IPlugin` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); + const iface = IPlugin__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); + + it('supports the `IProtocolVersion` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); + const iface = IProtocolVersion__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); + + it('supports the `IProposal` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); + const iface = IProposal__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); + + it('supports the `IMembership` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); + const iface = IMembership__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); + + it('supports the `Admin` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); + const interfaceId = getInterfaceId(ADMIN_INTERFACE); + expect(await plugin.supportsInterface(interfaceId)).to.be.true; + }); + }); + + describe('execute proposal: ', async () => { + it('reverts when calling `executeProposal()` if `EXECUTE_PROPOSAL_PERMISSION_ID` is not granted to the admin address', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + dummyActions, + dummyMetadata, + } = await loadFixture(fixture); - await expect(plugin.connect(bob).storeNumber(newNumber)) + // Check that the Alice hasn't `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + expect( + await dao.hasPermission( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID, + [] + ) + ).to.be.false; + + // Expect Alice's `executeProposal` call to be reverted because she hasn't `EXECUTE_PROPOSAL_PERMISSION_ID` on the Admin plugin + await expect( + plugin.connect(alice).executeProposal(dummyMetadata, dummyActions, 0) + ) .to.be.revertedWithCustomError(plugin, 'DaoUnauthorized') .withArgs( - daoMock.address, + dao.address, plugin.address, - bob.address, - STORE_PERMISSION_ID + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID ); }); - it('stores the number', async () => { - const newNumber = BigNumber.from(456); + it('reverts when calling `executeProposal()` if the `EXECUTE_PERMISSION_ID` on the DAO is not granted to the plugin address', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + dummyActions, + dummyMetadata, + } = await loadFixture(fixture); - const {plugin, daoMock} = await loadFixture(fixture); - await daoMock.setHasPermissionReturnValueMock(true); + // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); - await expect(plugin.storeNumber(newNumber)).to.not.be.reverted; - expect(await plugin.number()).to.equal(newNumber); + // Check that the Admin plugin hasn't `EXECUTE_PERMISSION_ID` on the DAO + expect( + await dao.hasPermission( + plugin.address, + alice.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID, + [] + ) + ).to.be.false; + + // Expect Alice's the `executeProposal` call to be reverted because the Admin plugin hasn't `EXECUTE_PERMISSION_ID` on the DAO + await expect( + plugin.connect(alice).executeProposal(dummyMetadata, dummyActions, 0) + ) + .to.be.revertedWithCustomError(dao, 'Unauthorized') + .withArgs( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); }); - it('emits the NumberStored event', async () => { - const newNumber = BigNumber.from(456); + it('emits the ProposalCreated event', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + dummyActions, + dummyMetadata, + } = await loadFixture(fixture); + + // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); + // Grant the Admin plugin the `EXECUTE_PERMISSION_ID` permission on the DAO + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + const currentExpectedProposalId = 0; - const {plugin, daoMock} = await loadFixture(fixture); - await daoMock.setHasPermissionReturnValueMock(true); + const allowFailureMap = 1; - await expect(plugin.storeNumber(newNumber)) - .to.emit(plugin, 'NumberStored') - .withArgs(newNumber); + const tx = await plugin + .connect(alice) + .executeProposal(dummyMetadata, dummyActions, allowFailureMap); + + const eventName = plugin.interface.getEvent('ProposalCreated').name; + await expect(tx).to.emit(plugin, eventName); + const event = await findEvent(tx, eventName); + expect(event.args.proposalId).to.equal(currentExpectedProposalId); + expect(event.args.creator).to.equal(alice.address); + expect(event.args.metadata).to.equal(dummyMetadata); + expect(event.args.actions.length).to.equal(1); + expect(event.args.actions[0].to).to.equal(dummyActions[0].to); + expect(event.args.actions[0].value).to.equal(dummyActions[0].value); + expect(event.args.actions[0].data).to.equal(dummyActions[0].data); + expect(event.args.allowFailureMap).to.equal(allowFailureMap); + }); + + it('emits the `ProposalExecuted` event', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + dummyActions, + dummyMetadata, + } = await loadFixture(fixture); + + // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); + // Grant the Admin plugin the `EXECUTE_PERMISSION_ID` permission on the DAO + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + const currentExpectedProposalId = 0; + + await expect( + plugin.connect(alice).executeProposal(dummyMetadata, dummyActions, 0) + ) + .to.emit(plugin, plugin.interface.getEvent('ProposalExecuted').name) + .withArgs(currentExpectedProposalId); + }); + + it('correctly increments the proposal ID', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + dummyActions, + dummyMetadata, + } = await loadFixture(fixture); + // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); + // Grant the Admin plugin the `EXECUTE_PERMISSION_ID` permission on the DAO + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + const currentExpectedProposalId = 0; + + await plugin + .connect(alice) + .executeProposal(dummyMetadata, dummyActions, 0); + + const nextExpectedProposalId = currentExpectedProposalId + 1; + + const tx = await plugin + .connect(alice) + .executeProposal(dummyMetadata, dummyActions, 0); + + const eventName = plugin.interface.getEvent('ProposalCreated').name; + await expect(tx).to.emit(plugin, eventName); + + const event = await findEvent(tx, eventName); + expect(event.args.proposalId).to.equal(nextExpectedProposalId); + }); + + it("calls the DAO's execute function using the proposal ID as the call ID", async () => { + const { + alice, + initializedPlugin: plugin, + dao, + dummyActions, + dummyMetadata, + } = await loadFixture(fixture); + + // Grant Alice the `EXECUTE_PROPOSAL_PERMISSION_ID` permission on the Admin plugin + await dao.grant( + plugin.address, + alice.address, + EXECUTE_PROPOSAL_PERMISSION_ID + ); + // Grant the Admin plugin the `EXECUTE_PERMISSION_ID` permission on the DAO + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + const newPlugin = plugin.connect(alice); + { + const proposalId = 0; + const allowFailureMap = 1; + + const tx = await newPlugin + .connect(alice) + .executeProposal(dummyMetadata, dummyActions, allowFailureMap); + + const event = await findEventTopicLog( + tx, + dao.interface, + dao.interface.getEvent('Executed').name + ); + + expect(event.args.actor).to.equal(plugin.address); + expect(event.args.callId).to.equal(proposalIdToBytes32(proposalId)); + expect(event.args.actions.length).to.equal(1); + expect(event.args.actions[0].to).to.equal(dummyActions[0].to); + expect(event.args.actions[0].value).to.equal(dummyActions[0].value); + expect(event.args.actions[0].data).to.equal(dummyActions[0].data); + // note that failureMap is different than allowFailureMap. See `DAO.sol` for details + expect(event.args.failureMap).to.equal(0); + } + + { + const proposalId = 1; + + const tx = await newPlugin + .connect(alice) + .executeProposal(dummyMetadata, dummyActions, 0); + + const event = await findEventTopicLog( + tx, + dao.interface, + dao.interface.getEvent('Executed').name + ); + expect(event.args.callId).to.equal(proposalIdToBytes32(proposalId)); + } }); }); }); + +type FixtureResult = { + deployer: SignerWithAddress; + alice: SignerWithAddress; + bob: SignerWithAddress; + initializedPlugin: Admin; + uninitializedPlugin: Admin; + dao: DAO; + dummyActions: DAOStructs.ActionStruct[]; + dummyMetadata: string; +}; + +async function fixture(): Promise { + const [deployer, alice, bob] = await ethers.getSigners(); + + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + const dao = await createDaoProxy(deployer, dummyMetadata); + + const adminPluginImplementation = await new Admin__factory(deployer).deploy(); + const adminProxyFactory = await new ProxyFactory__factory(deployer).deploy( + adminPluginImplementation.address + ); + + // Create an initialized plugin clone + const adminPluginInitdata = + adminPluginImplementation.interface.encodeFunctionData('initialize', [ + dao.address, + ]); + const deploymentTx1 = await adminProxyFactory.deployMinimalProxy( + adminPluginInitdata + ); + const proxyCreatedEvent1 = await findEvent( + deploymentTx1, + adminProxyFactory.interface.getEvent('ProxyCreated').name + ); + const initializedPlugin = Admin__factory.connect( + proxyCreatedEvent1.args.proxy, + deployer + ); + + const deploymentTx2 = await adminProxyFactory.deployMinimalProxy([]); + const proxyCreatedEvent2 = await findEvent( + deploymentTx2, + adminProxyFactory.interface.getEvent('ProxyCreated').name + ); + const uninitializedPlugin = Admin__factory.connect( + proxyCreatedEvent2.args.proxy, + deployer + ); + + const dummyActions: DAOStructs.ActionStruct[] = [ + { + to: deployer.address, + data: '0x1234', + value: 0, + }, + ]; + + return { + deployer, + alice, + bob, + initializedPlugin, + uninitializedPlugin, + dao, + dummyActions, + dummyMetadata, + }; +} diff --git a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts index 381580b2..f4e79dfc 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -1,19 +1,18 @@ +import {createDaoProxy} from '../20_integration-testing/test-helpers'; import {PLUGIN_SETUP_CONTRACT_NAME} from '../../plugin-settings'; import buildMetadata from '../../src/build-metadata.json'; +import {AdminSetup, Admin__factory, AdminSetup__factory} from '../../typechain'; import { - DAOMock, - DAOMock__factory, - MyPluginSetup, - MyPluginSetup__factory, - MyPlugin__factory, -} from '../../typechain'; -import {STORE_PERMISSION_ID, defaultInitData} from './11_plugin'; + ADMIN_INTERFACE, + EXECUTE_PROPOSAL_PERMISSION_ID, +} from '../admin-constants'; import { - ADDRESS, Operation, - PERMISSION_MANAGER_FLAGS, + DAO_PERMISSIONS, + getInterfaceId, getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; +import {DAO} from '@aragon/osx-ethers'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; @@ -23,24 +22,27 @@ type FixtureResult = { deployer: SignerWithAddress; alice: SignerWithAddress; bob: SignerWithAddress; - pluginSetup: MyPluginSetup; + pluginSetup: AdminSetup; prepareInstallationInputs: string; prepareUninstallationInputs: string; - daoMock: DAOMock; + dao: DAO; }; async function fixture(): Promise { const [deployer, alice, bob] = await ethers.getSigners(); - const daoMock = await new DAOMock__factory(deployer).deploy(); - const pluginSetup = await new MyPluginSetup__factory(deployer).deploy(); + + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + const dao = await createDaoProxy(deployer, dummyMetadata); + const pluginSetup = await new AdminSetup__factory(deployer).deploy(); const prepareInstallationInputs = ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( buildMetadata.pluginSetup.prepareInstallation.inputs ), - [defaultInitData.number] + [alice.address] ); - const prepareUninstallationInputs = ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( buildMetadata.pluginSetup.prepareUninstallation.inputs @@ -55,14 +57,68 @@ async function fixture(): Promise { pluginSetup, prepareInstallationInputs, prepareUninstallationInputs, - daoMock, + dao, }; } describe(PLUGIN_SETUP_CONTRACT_NAME, function () { + it('does not support the empty interface', async () => { + const {pluginSetup} = await loadFixture(fixture); + expect(await pluginSetup.supportsInterface('0xffffffff')).to.be.false; + }); + + it('has an admin plugin implementation base with the correct interface', async () => { + const {deployer, pluginSetup} = await loadFixture(fixture); + + const admin = Admin__factory.connect( + await pluginSetup.implementation(), + deployer + ); + expect( + await admin.supportsInterface(getInterfaceId(ADMIN_INTERFACE)) + ).to.be.eq(true); + }); + describe('prepareInstallation', async () => { - it('returns the plugin, helpers, and permissions', async () => { - const {deployer, pluginSetup, prepareInstallationInputs, daoMock} = + it('fails if data is empty, or not of minimum length', async () => { + const {pluginSetup, prepareInstallationInputs, dao} = await loadFixture( + fixture + ); + + await expect(pluginSetup.prepareInstallation(dao.address, [])).to.be + .reverted; + + const trimmedData = prepareInstallationInputs.substring( + 0, + prepareInstallationInputs.length - 2 + ); + await expect(pluginSetup.prepareInstallation(dao.address, trimmedData)).to + .be.reverted; + + await expect( + pluginSetup.prepareInstallation(dao.address, prepareInstallationInputs) + ).not.to.be.reverted; + }); + + it('reverts if encoded address in `_data` is zero', async () => { + const {pluginSetup, dao} = await loadFixture(fixture); + + const dataWithAddressZero = ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + buildMetadata.pluginSetup.prepareInstallation.inputs + ), + [ethers.constants.AddressZero] + ); + + await expect( + pluginSetup.prepareInstallation(dao.address, dataWithAddressZero) + ) + .to.be.revertedWithCustomError(pluginSetup, 'AdminAddressInvalid') + .withArgs(ethers.constants.AddressZero); + }); + + it('returns the plugin, helpers and permissions', async () => { + const {alice, pluginSetup, prepareInstallationInputs, dao} = await loadFixture(fixture); const nonce = await ethers.provider.getTransactionCount( @@ -77,46 +133,70 @@ describe(PLUGIN_SETUP_CONTRACT_NAME, function () { plugin, preparedSetupData: {helpers, permissions}, } = await pluginSetup.callStatic.prepareInstallation( - daoMock.address, + dao.address, prepareInstallationInputs ); expect(plugin).to.be.equal(anticipatedPluginAddress); expect(helpers.length).to.be.equal(0); - expect(permissions.length).to.be.equal(1); + expect(permissions.length).to.be.equal(2); expect(permissions).to.deep.equal([ [ Operation.Grant, plugin, - daoMock.address, - PERMISSION_MANAGER_FLAGS.NO_CONDITION, - STORE_PERMISSION_ID, + alice.address, + ethers.constants.AddressZero, + EXECUTE_PROPOSAL_PERMISSION_ID, + ], + [ + Operation.Grant, + dao.address, + plugin, + ethers.constants.AddressZero, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID, ], ]); + }); + + it('sets the dao for prepared plugins', async () => { + const {deployer, pluginSetup, prepareInstallationInputs, dao} = + await loadFixture(fixture); + + // Check the nonce of the setup contract to obtain the address of the next deployed contract, which will be the plugin clone + const nonce = await ethers.provider.getTransactionCount( + pluginSetup.address + ); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: pluginSetup.address, + nonce, + }); await pluginSetup.prepareInstallation( - daoMock.address, + dao.address, prepareInstallationInputs ); - const myPlugin = new MyPlugin__factory(deployer).attach(plugin); - // initialization is correct - expect(await myPlugin.dao()).to.eq(daoMock.address); - expect(await myPlugin.number()).to.be.eq(defaultInitData.number); + const adminAddressContract = Admin__factory.connect( + anticipatedPluginAddress, + deployer + ); + + expect(await adminAddressContract.dao()).to.be.equal(dao.address); }); }); describe('prepareUninstallation', async () => { it('returns the permissions', async () => { - const {pluginSetup, daoMock, prepareUninstallationInputs} = - await loadFixture(fixture); + const {pluginSetup, prepareUninstallationInputs, dao} = await loadFixture( + fixture + ); - const dummyAddr = ADDRESS.ZERO; + const plugin = ethers.Wallet.createRandom().address; const permissions = await pluginSetup.callStatic.prepareUninstallation( - daoMock.address, + dao.address, { - plugin: dummyAddr, + plugin, currentHelpers: [], data: prepareUninstallationInputs, } @@ -126,10 +206,10 @@ describe(PLUGIN_SETUP_CONTRACT_NAME, function () { expect(permissions).to.deep.equal([ [ Operation.Revoke, - dummyAddr, - daoMock.address, - PERMISSION_MANAGER_FLAGS.NO_CONDITION, - STORE_PERMISSION_ID, + dao.address, + plugin, + ethers.constants.AddressZero, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID, ], ]); }); diff --git a/packages/contracts/test/20_integration-testing/21_deployment.ts b/packages/contracts/test/20_integration-testing/21_deployment.ts index 1bdcfb7f..eb6509e9 100644 --- a/packages/contracts/test/20_integration-testing/21_deployment.ts +++ b/packages/contracts/test/20_integration-testing/21_deployment.ts @@ -13,6 +13,8 @@ import { uploadToIPFS, } from '@aragon/osx-commons-sdk'; import { + DAO, + DAO__factory, PluginRepo, PluginRepoRegistry, PluginRepoRegistry__factory, @@ -31,13 +33,13 @@ describe(`Deployment on network '${productionNetworkName}'`, function () { expect(await pluginRepoRegistry.entries(pluginRepo.address)).to.be.true; }); - it('makes the deployer the repo maintainer', async () => { - const {deployer, pluginRepo} = await loadFixture(fixture); + it('gives the management DAO permissions over the repo', async () => { + const {pluginRepo, managementDaoProxy} = await loadFixture(fixture); expect( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + managementDaoProxy.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID, PERMISSION_MANAGER_FLAGS.NO_CONDITION ) @@ -46,7 +48,7 @@ describe(`Deployment on network '${productionNetworkName}'`, function () { expect( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + managementDaoProxy.address, PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, PERMISSION_MANAGER_FLAGS.NO_CONDITION ) @@ -55,7 +57,7 @@ describe(`Deployment on network '${productionNetworkName}'`, function () { expect( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + managementDaoProxy.address, PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, PERMISSION_MANAGER_FLAGS.NO_CONDITION ) @@ -89,16 +91,17 @@ type FixtureResult = { deployer: SignerWithAddress; pluginRepo: PluginRepo; pluginRepoRegistry: PluginRepoRegistry; + managementDaoProxy: DAO; }; async function fixture(): Promise { // Deploy all - const tags = ['CreateRepo', 'NewVersion']; - await deployments.fixture(tags); + const tags = ['CreateRepo', 'NewVersion', 'TransferOwnershipToManagmentDao']; + await deployments.fixture(tags); const [deployer] = await ethers.getSigners(); - // Plugin Repo + // Plugin repo const {pluginRepo, ensDomain} = await findPluginRepo(env); if (pluginRepo === null) { throw `PluginRepo '${ensDomain}' does not exist yet.`; @@ -119,5 +122,11 @@ async function fixture(): Promise { deployer ); - return {deployer, pluginRepo, pluginRepoRegistry}; + // Management DAO proxy + const managementDaoProxy = DAO__factory.connect( + networkDeployments.ManagementDAOProxy.address, + deployer + ); + + return {deployer, pluginRepo, pluginRepoRegistry, managementDaoProxy}; } diff --git a/packages/contracts/test/20_integration-testing/22_setup-processing.ts b/packages/contracts/test/20_integration-testing/22_setup-processing.ts index bb9da6af..ec159a88 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -1,18 +1,14 @@ -import {METADATA} from '../../plugin-settings'; -import { - DAOMock, - DAOMock__factory, - MyPluginSetup, - MyPluginSetup__factory, - MyPlugin__factory, -} from '../../typechain'; +import {METADATA, VERSION} from '../../plugin-settings'; +import {AdminSetup, AdminSetup__factory, Admin__factory} from '../../typechain'; import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; -import {installPLugin, uninstallPLugin} from './test-helpers'; +import {createDaoProxy, installPLugin, uninstallPLugin} from './test-helpers'; import { getLatestNetworkDeployment, getNetworkNameByAlias, } from '@aragon/osx-commons-configs'; import { + DAO_PERMISSIONS, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS, UnsupportedNetworkError, getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; @@ -21,55 +17,67 @@ import { PluginRepo, PluginSetupProcessorStructs, PluginSetupProcessor__factory, + DAO, } from '@aragon/osx-ethers'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {BigNumber} from 'ethers'; import env, {deployments, ethers} from 'hardhat'; const productionNetworkName = getProductionNetworkName(env); describe(`PluginSetup processing on network '${productionNetworkName}'`, function () { it('installs & uninstalls the current build', async () => { - const {deployer, psp, daoMock, pluginSetup, pluginSetupRef} = - await loadFixture(fixture); + const {alice, deployer, psp, dao, pluginSetupRef} = await loadFixture( + fixture + ); - // Allow all authorized calls to happen - await daoMock.setHasPermissionReturnValueMock(true); + // Grant deployer all required permissions + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_UNINSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); // Install the current build. const results = await installPLugin( deployer, psp, - daoMock, + dao, pluginSetupRef, ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareInstallation.inputs ), - [123] + [alice.address] ) ); - const plugin = MyPlugin__factory.connect( + const plugin = Admin__factory.connect( results.preparedEvent.args.plugin, deployer ); - // Check implementation. - expect(await plugin.implementation()).to.be.eq( - await pluginSetup.implementation() - ); - - // Check state. - expect(await plugin.number()).to.eq(123); + // Check that the setup worked + expect(await plugin.isMember(alice.address)).to.be.true; // Uninstall the current build. await uninstallPLugin( deployer, psp, - daoMock, + dao, plugin, pluginSetupRef, ethers.utils.defaultAbiCoder.encode( @@ -87,10 +95,10 @@ type FixtureResult = { deployer: SignerWithAddress; alice: SignerWithAddress; bob: SignerWithAddress; - daoMock: DAOMock; + dao: DAO; psp: PluginSetupProcessor; pluginRepo: PluginRepo; - pluginSetup: MyPluginSetup; + pluginSetup: AdminSetup; pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct; }; @@ -100,7 +108,10 @@ async function fixture(): Promise { await deployments.fixture(tags); const [deployer, alice, bob] = await ethers.getSigners(); - const daoMock = await new DAOMock__factory(deployer).deploy(); + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + const dao = await createDaoProxy(deployer, dummyMetadata); const network = getNetworkNameByAlias(productionNetworkName); if (network === null) { @@ -124,15 +135,15 @@ async function fixture(): Promise { } const release = 1; - const pluginSetup = MyPluginSetup__factory.connect( + const pluginSetup = AdminSetup__factory.connect( (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, deployer ); const pluginSetupRef = { versionTag: { - release: BigNumber.from(1), - build: BigNumber.from(1), + release: VERSION.release, + build: VERSION.build, }, pluginSetupRepo: pluginRepo.address, }; @@ -142,7 +153,7 @@ async function fixture(): Promise { alice, bob, psp, - daoMock, + dao, pluginRepo, pluginSetup, pluginSetupRef, diff --git a/packages/contracts/test/20_integration-testing/test-helpers.ts b/packages/contracts/test/20_integration-testing/test-helpers.ts index a8712834..a9bac891 100644 --- a/packages/contracts/test/20_integration-testing/test-helpers.ts +++ b/packages/contracts/test/20_integration-testing/test-helpers.ts @@ -1,4 +1,5 @@ -import {DAOMock, IPlugin} from '../../typechain'; +import {IPlugin, ProxyFactory__factory} from '../../typechain'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; import {hashHelpers} from '../../utils/helpers'; import { DAO_PERMISSIONS, @@ -10,15 +11,18 @@ import { PluginSetupProcessorStructs, PluginSetupProcessor, DAOStructs, + DAO, + DAO__factory, } from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ContractTransaction} from 'ethers'; +import {ethers} from 'hardhat'; export async function installPLugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct, data: string ): Promise<{ @@ -68,7 +72,7 @@ export async function installPLugin( export async function uninstallPLugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, plugin: IPlugin, pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct, data: string, @@ -123,7 +127,7 @@ export async function uninstallPLugin( export async function updatePlugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, plugin: IPlugin, currentHelpers: string[], pluginSetupRefCurrent: PluginSetupProcessorStructs.PluginSetupRefStruct, @@ -183,7 +187,7 @@ export async function updatePlugin( async function checkPermissions( preparedPermissions: DAOStructs.MultiTargetPermissionStruct[], - dao: DAOMock, + dao: DAO, psp: PluginSetupProcessor, signer: SignerWithAddress, applyPermissionId: string @@ -212,3 +216,31 @@ async function checkPermissions( throw `The used signer does not have the permission with ID '${applyPermissionId}' granted and thus cannot apply the setup`; } } + +// TODO Move into OSX commons as part of Task OS-928. +export async function createDaoProxy( + deployer: SignerWithAddress, + dummyMetadata: string +): Promise { + const daoImplementation = await new DAO__factory(deployer).deploy(); + const daoProxyFactory = await new ProxyFactory__factory(deployer).deploy( + daoImplementation.address + ); + + const daoInitData = daoImplementation.interface.encodeFunctionData( + 'initialize', + [ + dummyMetadata, + deployer.address, + ethers.constants.AddressZero, + dummyMetadata, + ] + ); + const tx = await daoProxyFactory.deployUUPSProxy(daoInitData); + const event = await findEvent( + tx, + daoProxyFactory.interface.getEvent('ProxyCreated').name + ); + const dao = DAO__factory.connect(event.args.proxy, deployer); + return dao; +} diff --git a/packages/contracts/test/admin-constants.ts b/packages/contracts/test/admin-constants.ts new file mode 100644 index 00000000..799d0d8e --- /dev/null +++ b/packages/contracts/test/admin-constants.ts @@ -0,0 +1,11 @@ +import {ethers} from 'hardhat'; + +export const ADMIN_INTERFACE = new ethers.utils.Interface([ + 'function initialize(address)', + 'function executeProposal(bytes,tuple(address,uint256,bytes)[],uint256)', +]); + +// Permissions +export const EXECUTE_PROPOSAL_PERMISSION_ID = ethers.utils.id( + 'EXECUTE_PROPOSAL_PERMISSION' +); diff --git a/packages/contracts/utils/helpers.ts b/packages/contracts/utils/helpers.ts index c76f6992..6b13eb8d 100644 --- a/packages/contracts/utils/helpers.ts +++ b/packages/contracts/utils/helpers.ts @@ -10,6 +10,8 @@ import { findEvent, } from '@aragon/osx-commons-sdk'; import { + DAO, + DAO__factory, ENSSubdomainRegistrar__factory, ENS__factory, IAddrResolver__factory, @@ -17,7 +19,9 @@ import { PluginRepoEvents, PluginRepo__factory, } from '@aragon/osx-ethers'; -import {ContractTransaction} from 'ethers'; +import {setBalance} from '@nomicfoundation/hardhat-network-helpers'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {BigNumber, ContractTransaction} from 'ethers'; import {LogDescription, defaultAbiCoder, keccak256} from 'ethers/lib/utils'; import {ethers} from 'hardhat'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -68,6 +72,7 @@ export async function findPluginRepo( ): 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); @@ -107,6 +112,61 @@ export async function findPluginRepo( } } +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}.`; + } + + return DAO__factory.connect( + networkDeployments.ManagementDAOProxy.address, + deployer + ); +} + +export async function getManagementDaoMultisig( + 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}.`; + } + + return DAO__factory.connect( + networkDeployments.ManagementDAOProxy.address, + deployer + ); +} + +export async function impersonatedManagementDaoSigner( + hre: HardhatRuntimeEnvironment +): Promise { + return await (async () => { + const managementDaoProxy = getManagementDao(hre); + const signer = await hre.ethers.getImpersonatedSigner( + ( + await managementDaoProxy + ).address + ); + await setBalance(signer.address, BigNumber.from(10).pow(18)); + return signer; + })(); +} + export type EventWithBlockNumber = { event: LogDescription; blockNumber: number; diff --git a/packages/contracts/yarn.lock b/packages/contracts/yarn.lock index 43944771..5b5ad769 100644 --- a/packages/contracts/yarn.lock +++ b/packages/contracts/yarn.lock @@ -2,15 +2,10 @@ # yarn lockfile v1 -"@aragon/osx-artifacts@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@aragon/osx-artifacts/-/osx-artifacts-1.3.1.tgz#68fa04844086a92d74351df2e9392ade3c8696dc" - integrity sha512-u6IFP8fQZIS65Ks5Sl1DKlw8Qp9s5I7DSn9n/odQohWnN65A17HwHaCPTEcXl2AL3r71rFawldQ8i5/2yU3UGA== - -"@aragon/osx-commons-configs@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.1.0.tgz#21bbc5a964eb144e30033a44cc352d35c62982f9" - integrity sha512-qTs/loihwqALBGmhZngORb+p7pjuQJY5UEd8TLNiEW/BGHEpAJPp4GeQu7GSnigRGEKWpPD5W96kfEsaPtLkuQ== +"@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== dependencies: tslib "^2.6.2" @@ -92,12 +87,12 @@ tslib "^1.11.1" "@aws-sdk/types@^3.1.0": - version "3.468.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.468.0.tgz#f97b34fc92a800d1d8b866f47693ae8f3d46517b" - integrity sha512-rx/9uHI4inRbp2tw3Y4Ih4PNZkVj32h7WneSg3MVgVjAoVD5Zti9KhS5hkvsBxfgmQmg0AQbE+b1sy5WGAgntA== + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.535.0.tgz#5e6479f31299dd9df170e63f4d10fe739008cf04" + integrity sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg== dependencies: - "@smithy/types" "^2.7.0" - tslib "^2.5.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" "@aws-sdk/util-utf8-browser@^3.0.0": version "3.259.0" @@ -903,9 +898,9 @@ node-fetch "^2.6.0" "@openzeppelin/upgrades-core@^1.27.0": - version "1.32.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.32.0.tgz#2f5761b554e96f7b011eab3f0422b71bfb156a20" - integrity sha512-ZjYB5Ks5Haz8yzJDd9VzTtJyqm746+WYFOi8jeVljyGxC4Xm2wuizf/n1lw0CmCw9seNhD1J1tA4fA6ScXYPDg== + version "1.32.5" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.32.5.tgz#2496174fd1f47be4dd8f36b29714d4e1f8240632" + integrity sha512-R0wprsyJ4xWiRW05kaTfZZkRVpG2g0af3/hpjE7t2mX0Eb2n40MQLokTwqIk4LDzpp910JfLSpB0vBuZ6WNPog== dependencies: cbor "^9.0.0" chalk "^4.1.0" @@ -1081,12 +1076,12 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" -"@smithy/types@^2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.7.0.tgz#6ed9ba5bff7c4d28c980cff967e6d8456840a4f3" - integrity sha512-1OIFyhK+vOkMbu4aN2HZz/MomREkrAC/HqY5mlJMUJfGrPRwijJDTeiN8Rnj9zUaB8ogXAfIOtZrrgqZ4w7Wnw== +"@smithy/types@^2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.12.0.tgz#c44845f8ba07e5e8c88eda5aed7e6a0c462da041" + integrity sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw== dependencies: - tslib "^2.5.0" + tslib "^2.6.2" "@solidity-parser/parser@^0.14.0": version "0.14.5" @@ -1407,9 +1402,9 @@ ajv@^8.0.1: uri-js "^4.2.2" amazon-cognito-identity-js@^6.0.1: - version "6.3.7" - resolved "https://registry.yarnpkg.com/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.7.tgz#65c3d7ee4e0c0a1ffea01927248989c5bd1d1868" - integrity sha512-tSjnM7KyAeOZ7UMah+oOZ6cW4Gf64FFcc7BE2l7MTcp7ekAPrXaCbpcW2xEpH1EiDS4cPcAouHzmCuc2tr72vQ== + version "6.3.12" + resolved "https://registry.yarnpkg.com/amazon-cognito-identity-js/-/amazon-cognito-identity-js-6.3.12.tgz#af73df033094ad4c679c19cf6122b90058021619" + integrity sha512-s7NKDZgx336cp+oDeUtB2ZzT8jWJp/v2LWuYl+LQtMEODe22RF1IJ4nRiDATp+rp1pTffCZcm44Quw4jx2bqNg== dependencies: "@aws-crypto/sha256-js" "1.2.2" buffer "4.9.2" @@ -1521,13 +1516,13 @@ array-back@^4.0.1, array-back@^4.0.2: resolved "https://registry.yarnpkg.com/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== +array-buffer-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" + integrity sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg== dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" + call-bind "^1.0.5" + is-array-buffer "^3.0.4" array-union@^2.1.0: version "2.1.0" @@ -1540,27 +1535,29 @@ array-uniq@1.0.3: integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== array.prototype.findlast@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.3.tgz#4e4b375de5adf4897fed155e2d2771564865cc3b" - integrity sha512-kcBubumjciBg4JKp5KTKtI7ec7tRefPk88yjkWJwaVKYd9QfTaxcsOxoMNKd7iBr447zCfDV0z1kOF47umv42g== + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" -arraybuffer.prototype.slice@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" - integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== +arraybuffer.prototype.slice@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6" + integrity sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A== dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - is-array-buffer "^3.0.2" + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.5" + define-properties "^1.2.1" + es-abstract "^1.22.3" + es-errors "^1.2.1" + get-intrinsic "^1.2.3" + is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" asap@~2.0.6: @@ -1605,6 +1602,13 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + axios@^0.21.1, axios@^0.21.2: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -1612,7 +1616,16 @@ axios@^0.21.1, axios@^0.21.2: dependencies: follow-redirects "^1.14.0" -axios@^1.4.0, axios@^1.5.1: +axios@^1.4.0: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axios@^1.5.1: version "1.6.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.2.tgz#de67d42c755b571d3e698df1b6504cde9b0ee9f2" integrity sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A== @@ -1796,7 +1809,7 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== @@ -1805,6 +1818,17 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" +call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -1833,9 +1857,9 @@ cbor@^8.1.0: nofilter "^3.1.0" cbor@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.1.tgz#b16e393d4948d44758cd54ac6151379d443b37ae" - integrity sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ== + version "9.0.2" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.2.tgz#536b4f2d544411e70ec2b19a2453f10f83cd9fdb" + integrity sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ== dependencies: nofilter "^3.1.0" @@ -2118,6 +2142,33 @@ data-uri-to-buffer@^3.0.1: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== +data-view-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" + integrity sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" + integrity sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +data-view-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" + integrity sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA== + dependencies: + call-bind "^1.0.6" + es-errors "^1.3.0" + is-data-view "^1.0.1" + death@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" @@ -2152,7 +2203,16 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -define-data-property@^1.0.1, define-data-property@^1.1.1: +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-data-property@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== @@ -2291,61 +2351,87 @@ err-code@^3.0.1: resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920" integrity sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA== -es-abstract@^1.22.1: - version "1.22.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" - integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== - dependencies: - array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.2" - available-typed-arrays "^1.0.5" - call-bind "^1.0.5" - es-set-tostringtag "^2.0.1" +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: + version "1.23.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" + integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== + dependencies: + array-buffer-byte-length "^1.0.1" + arraybuffer.prototype.slice "^1.0.3" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + data-view-buffer "^1.0.1" + data-view-byte-length "^1.0.1" + data-view-byte-offset "^1.0.0" + es-define-property "^1.0.0" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-set-tostringtag "^2.0.3" es-to-primitive "^1.2.1" function.prototype.name "^1.1.6" - get-intrinsic "^1.2.2" - get-symbol-description "^1.0.0" + get-intrinsic "^1.2.4" + get-symbol-description "^1.0.2" globalthis "^1.0.3" gopd "^1.0.1" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" has-symbols "^1.0.3" - hasown "^2.0.0" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" is-callable "^1.2.7" - is-negative-zero "^2.0.2" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" + is-shared-array-buffer "^1.0.3" is-string "^1.0.7" - is-typed-array "^1.1.12" + is-typed-array "^1.1.13" is-weakref "^1.0.2" object-inspect "^1.13.1" object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.1" - safe-array-concat "^1.0.1" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.8" - string.prototype.trimend "^1.0.7" + object.assign "^4.1.5" + regexp.prototype.flags "^1.5.2" + safe-array-concat "^1.1.2" + safe-regex-test "^1.0.3" + string.prototype.trim "^1.2.9" + string.prototype.trimend "^1.0.8" string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.0" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" + typed-array-buffer "^1.0.2" + typed-array-byte-length "^1.0.1" + typed-array-byte-offset "^1.0.2" + typed-array-length "^1.0.5" unbox-primitive "^1.0.2" - which-typed-array "^1.1.13" + which-typed-array "^1.1.15" -es-set-tostringtag@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" - integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== dependencies: - get-intrinsic "^1.2.2" - has-tostringtag "^1.0.0" - hasown "^2.0.0" + get-intrinsic "^1.2.4" + +es-errors@^1.2.1, es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" + integrity sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" + integrity sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ== + dependencies: + get-intrinsic "^1.2.4" + has-tostringtag "^1.0.2" + hasown "^2.0.1" -es-shim-unscopables@^1.0.0: +es-shim-unscopables@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== @@ -2661,6 +2747,11 @@ follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.15.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -2811,7 +2902,7 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== @@ -2821,6 +2912,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-iterator@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-iterator/-/get-iterator-1.0.2.tgz#cd747c02b4c084461fac14f48f6b45a80ed25c82" @@ -2831,13 +2933,14 @@ get-port@^3.1.0: resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== +get-symbol-description@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" + integrity sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" + call-bind "^1.0.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" ghost-testrpc@^0.0.2: version "0.0.2" @@ -3107,11 +3210,23 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.2.2" +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + has-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== +has-proto@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -3124,6 +3239,13 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + hash-base@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" @@ -3148,6 +3270,13 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" +hasown@^2.0.1, hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -3280,12 +3409,12 @@ interface-store@^1.0.2: resolved "https://registry.yarnpkg.com/interface-store/-/interface-store-1.0.2.tgz#1ebd6cbbae387039a3a2de0cae665da52474800f" integrity sha512-rUBLYsgoWwxuUpnQoSUr+DR/3dH3reVeIu5aOHFZK31lAexmb++kR6ZECNRgrx6WvoaM3Akdo0A7TDrqgCzZaQ== -internal-slot@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" - integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== +internal-slot@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" + integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== dependencies: - get-intrinsic "^1.2.2" + es-errors "^1.3.0" hasown "^2.0.0" side-channel "^1.0.4" @@ -3404,14 +3533,13 @@ is-arguments@^1.0.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== +is-array-buffer@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" + integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" + get-intrinsic "^1.2.1" is-bigint@^1.0.1: version "1.0.4" @@ -3452,6 +3580,13 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.0" +is-data-view@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" + integrity sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w== + dependencies: + is-typed-array "^1.1.13" + is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -3505,10 +3640,10 @@ is-ip@^3.1.0: dependencies: ip-regex "^4.0.0" -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== is-number-object@^1.0.4: version "1.0.7" @@ -3535,12 +3670,12 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== +is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688" + integrity sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" @@ -3556,7 +3691,14 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed-array@^1.1.9: +is-typed-array@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" + integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== + dependencies: + which-typed-array "^1.1.14" + +is-typed-array@^1.1.3: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -4208,7 +4350,7 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.4: +object.assign@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== @@ -4379,6 +4521,11 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +possible-typed-array-names@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" + integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -4535,14 +4682,15 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== -regexp.prototype.flags@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" - integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== +regexp.prototype.flags@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" + integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - set-function-name "^2.0.0" + call-bind "^1.0.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" req-cwd@^2.0.0: version "2.0.0" @@ -4669,13 +4817,13 @@ rustbn.js@~0.2.0: resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" integrity sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA== -safe-array-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" - integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== +safe-array-concat@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" + integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + get-intrinsic "^1.2.4" has-symbols "^1.0.3" isarray "^2.0.5" @@ -4689,13 +4837,13 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== +safe-regex-test@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" + integrity sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" + call-bind "^1.0.6" + es-errors "^1.3.0" is-regex "^1.1.4" "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": @@ -4771,14 +4919,27 @@ set-function-length@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" -set-function-name@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" - integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== dependencies: - define-data-property "^1.0.1" + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" functions-have-names "^1.2.3" - has-property-descriptors "^1.0.0" + has-property-descriptors "^1.0.2" setimmediate@^1.0.5: version "1.0.5" @@ -4876,9 +5037,9 @@ solc@0.7.3: tmp "0.0.33" solidity-ast@^0.4.51: - version "0.4.55" - resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.55.tgz#00b685e6eefb2e8dfb67df1fe0afbe3b3bfb4b28" - integrity sha512-qeEU/r/K+V5lrAw8iswf2/yfWAnSGs3WKPHI+zAFKFjX0dIBVXEU/swQ8eJQYHf6PJWUZFO2uWV4V1wEOkeQbA== + version "0.4.56" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.56.tgz#94fe296f12e8de1a3bed319bc06db8d05a113d7a" + integrity sha512-HgmsA/Gfklm/M8GFbCX/J1qkVH0spXHgALCNZ8fA8x5X+MFdn/8CP2gr5OVyXjXw6RZTPC/Sxl2RUDQOXyNMeA== dependencies: array.prototype.findlast "^1.2.2" @@ -4983,32 +5144,33 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.trim@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" - integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== +string.prototype.trim@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" + integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" -string.prototype.trimend@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" - integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== +string.prototype.trimend@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" + integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string.prototype.trimstart@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" - integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string_decoder@^1.1.1: version "1.3.0" @@ -5239,7 +5401,7 @@ tslib@^1.11.1, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.3.1, tslib@^2.5.0, tslib@^2.6.2: +tslib@^2.3.1, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -5302,44 +5464,49 @@ typechain@^8.3.2: ts-command-line-args "^2.2.0" ts-essentials "^7.0.1" -typed-array-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" - integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== +typed-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" + integrity sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-typed-array "^1.1.10" + call-bind "^1.0.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" -typed-array-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" - integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== +typed-array-byte-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" + integrity sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" -typed-array-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" - integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== +typed-array-byte-offset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" + integrity sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== +typed-array-length@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== dependencies: - call-bind "^1.0.2" + call-bind "^1.0.7" for-each "^0.3.3" - is-typed-array "^1.1.9" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" typedarray@^0.0.6: version "0.0.6" @@ -5517,7 +5684,7 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.2: +which-typed-array@^1.1.11, which-typed-array@^1.1.2: version "1.1.13" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== @@ -5528,6 +5695,17 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.2: gopd "^1.0.1" has-tostringtag "^1.0.0" +which-typed-array@^1.1.14, which-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + which@^1.1.1, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" diff --git a/packages/subgraph/manifest/data/arbitrum.json b/packages/subgraph/manifest/data/arbitrum.json new file mode 100644 index 00000000..c5a43e55 --- /dev/null +++ b/packages/subgraph/manifest/data/arbitrum.json @@ -0,0 +1,16 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "arbitrum-one", + "PluginRepo": { + "address": "0x326A2aee6A8eE78D79E7E956DE60C6E452f76a8e" + }, + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", + "startBlock": 145462184 + } + ] + } +} diff --git a/packages/subgraph/manifest/data/arbitrumSepolia.json b/packages/subgraph/manifest/data/arbitrumSepolia.json new file mode 100644 index 00000000..c1f13274 --- /dev/null +++ b/packages/subgraph/manifest/data/arbitrumSepolia.json @@ -0,0 +1,16 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "arbitrum-sepolia", + "PluginRepo": { + "address": "0x152c9E28995E418870b85cbbc0AEE4e53020edb2" + }, + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", + "startBlock": 2827173 + } + ] + } +} diff --git a/packages/subgraph/manifest/data/goerli.json b/packages/subgraph/manifest/data/baseMainnet.json similarity index 56% rename from packages/subgraph/manifest/data/goerli.json rename to packages/subgraph/manifest/data/baseMainnet.json index 5f82e606..f8fb2057 100644 --- a/packages/subgraph/manifest/data/goerli.json +++ b/packages/subgraph/manifest/data/baseMainnet.json @@ -1,12 +1,15 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "goerli", + "network": "base", + "PluginRepo": { + "address": "0x212eF339C77B3390599caB4D46222D79fAabcb5c" + }, "dataSources": { "PluginSetupProcessors": [ { "name": "PluginSetupProcessor", - "address": "0xE8B5d8D66a02CD1b9Bd32a4064D7ABa45F51305e", - "startBlock": 8548226 + "address": "0x91a851E9Ed7F2c6d41b15F76e4a88f5A37067cC9", + "startBlock": 2094737 } ] } diff --git a/packages/subgraph/manifest/data/baseSepolia.json b/packages/subgraph/manifest/data/baseSepolia.json new file mode 100644 index 00000000..b99f6c59 --- /dev/null +++ b/packages/subgraph/manifest/data/baseSepolia.json @@ -0,0 +1,16 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "base-sepolia", + "PluginRepo": { + "address": "0x152c9E28995E418870b85cbbc0AEE4e53020edb2" + }, + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", + "startBlock": 3654401 + } + ] + } +} diff --git a/packages/subgraph/manifest/data/localhost.json b/packages/subgraph/manifest/data/localhost.json index fc78a36a..8f071725 100644 --- a/packages/subgraph/manifest/data/localhost.json +++ b/packages/subgraph/manifest/data/localhost.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "rinkeby", + "network": "sepolia", + "PluginRepo": { + "address": null + }, "dataSources": { "PluginSetupProcessors": [ { diff --git a/packages/subgraph/manifest/data/mainnet.json b/packages/subgraph/manifest/data/mainnet.json index b7969f87..8b9f8e47 100644 --- a/packages/subgraph/manifest/data/mainnet.json +++ b/packages/subgraph/manifest/data/mainnet.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "mainnet", + "PluginRepo": { + "address": "0xA4371a239D08bfBA6E8894eccf8466C6323A52C3" + }, "dataSources": { "PluginSetupProcessors": [ { diff --git a/packages/subgraph/manifest/data/mumbai.json b/packages/subgraph/manifest/data/mumbai.json index 9b1a7847..2a3b4be2 100644 --- a/packages/subgraph/manifest/data/mumbai.json +++ b/packages/subgraph/manifest/data/mumbai.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "mumbai", + "PluginRepo": { + "address": "0x0DF9b15550fF39149e491dDD154b28f587e0cD16" + }, "dataSources": { "PluginSetupProcessors": [ { diff --git a/packages/subgraph/manifest/data/polygon.json b/packages/subgraph/manifest/data/polygon.json index 7675c9ba..08c3f35e 100644 --- a/packages/subgraph/manifest/data/polygon.json +++ b/packages/subgraph/manifest/data/polygon.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "matic", + "PluginRepo": { + "address": "0x7fF570473d0876db16A59e8F04EE7F17Ab117309" + }, "dataSources": { "PluginSetupProcessors": [ { diff --git a/packages/subgraph/manifest/data/sepolia.json b/packages/subgraph/manifest/data/sepolia.json index 18a9af1e..32f38535 100644 --- a/packages/subgraph/manifest/data/sepolia.json +++ b/packages/subgraph/manifest/data/sepolia.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "sepolia", + "PluginRepo": { + "address": "0x152c9E28995E418870b85cbbc0AEE4e53020edb2" + }, "dataSources": { "PluginSetupProcessors": [ { diff --git a/packages/subgraph/manifest/subgraph.placeholder.yaml b/packages/subgraph/manifest/subgraph.placeholder.yaml index 2de5289f..1bfd2665 100644 --- a/packages/subgraph/manifest/subgraph.placeholder.yaml +++ b/packages/subgraph/manifest/subgraph.placeholder.yaml @@ -1,7 +1,7 @@ {{info}} specVersion: 0.0.2 -description: A template for Plugin subgraphs -repository: https://github.com/aragon/osx-plugin-subgraph +description: The Subgraph of the Admin Plugin +repository: https://github.com/aragon/admin-plugin schema: file: ./schema.graphql dataSources: @@ -32,23 +32,48 @@ dataSources: {{/dataSources.PluginSetupProcessors}} # templates templates: - # Plugin (package) + # Plugin - name: Plugin kind: ethereum/contract network: {{network}} source: - abi: Plugin + abi: Admin mapping: kind: ethereum/events apiVersion: 0.0.5 language: wasm/assemblyscript entities: - - Dao + - AdminPlugin + - AdminProposal + - Action + file: ./src/plugin/plugin.ts abis: - - name: Plugin - file: $PLUGIN_MODULE/contracts/artifacts/src/MyPlugin.sol/MyPlugin.json + - name: Admin + file: $PLUGIN_MODULE/contracts/artifacts/src/Admin.sol/Admin.json eventHandlers: - - event: NumberStored(uint256) - handler: handleNumberStored - file: ./src/plugin/plugin.ts - + - event: MembershipContractAnnounced(indexed address) + handler: handleMembershipContractAnnounced + - event: ProposalExecuted(indexed uint256) + handler: handleProposalExecuted + - event: ProposalCreated(indexed uint256,indexed address,uint64,uint64,bytes,(address,uint256,bytes)[],uint256) + handler: handleProposalCreated + - name: AdminMembers + kind: ethereum/contract + network: {{network}} + source: + abi: PermissionManager + mapping: + kind: ethereum/events + apiVersion: 0.0.5 + language: wasm/assemblyscript + entities: + - Administrator + file: ./src/plugin/adminMembers.ts + abis: + - name: PermissionManager + file: ./imported/PermissionManager.json + eventHandlers: + - event: Granted(indexed bytes32,indexed address,address,indexed address,address) + handler: handleGranted + - event: Revoked(indexed bytes32,indexed address,address,indexed address) + handler: handleRevoked diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 6fd0d40e..bb7ad026 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -1,5 +1,5 @@ { - "name": "@aragon/osx-plugin-template-subgraph", + "name": "@aragon/admin-plugin-subgraph", "version": "1.0.0", "license": "AGPL-3.0-or-later", "scripts": { @@ -17,18 +17,18 @@ }, "devDependencies": { "@aragon/osx-ethers": "1.4.0-alpha.0", - "@aragon/osx-commons-configs": "0.2.0", + "@aragon/osx-commons-configs": "0.4.0", "@graphprotocol/graph-cli": "^0.51.0", "@graphprotocol/graph-ts": "^0.31.0", "cross-env": "^7.0.3", "dotenv": "^16.3.1", - "matchstick-as": "^0.5.2", + "matchstick-as": "^0.6.0", "mustache": "^4.2.0", "ts-morph": "^17.0.1", "ts-node": "^10.9.1", "typescript": "^5.2.2" }, "dependencies": { - "@aragon/osx-commons-subgraph": "^0.0.4" + "@aragon/osx-commons-subgraph": "^0.0.5" } } diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index bb175125..e4dbf2c1 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -1,10 +1,77 @@ -# Types -type DaoPlugin @entity { - "OSX related data" - id: ID! # psp installationId - dao: Bytes! +interface IPlugin { + id: ID! # Plugin address + daoAddress: Bytes! pluginAddress: Bytes! +} + +type AdminPlugin implements IPlugin @entity { + id: ID! # Plugin address + daoAddress: Bytes! + pluginAddress: Bytes! + proposals: [AdminProposal!]! @derivedFrom(field: "plugin") + administrators: [AdministratorAdminPlugin!]! @derivedFrom(field: "plugin") +} + +type Administrator @entity(immutable: true) { + id: ID! # Administrator address + address: String # address as string to facilitate filtering by address on the UI + proposals: [AdminProposal!]! @derivedFrom(field: "administrator") + plugins: [AdministratorAdminPlugin!]! @derivedFrom(field: "administrator") +} + +type AdministratorAdminPlugin @entity { + "for Many-to-Many" + id: ID! # Plugin address + Administrator address + administrator: Administrator! + plugin: AdminPlugin! +} + +interface IProposal { + id: ID! # Plugin address + Plugin proposal ID + daoAddress: Bytes! + creator: Bytes! + metadata: String + actions: [Action!]! @derivedFrom(field: "proposal") + allowFailureMap: BigInt! + executed: Boolean! + createdAt: BigInt! + startDate: BigInt! + endDate: BigInt! + executionTxHash: Bytes +} + +type AdminProposal implements IProposal @entity { + id: ID! # Plugin address + Plugin proposal ID + daoAddress: Bytes! + creator: Bytes! # Administrator address + metadata: String + actions: [Action!]! @derivedFrom(field: "proposal") + allowFailureMap: BigInt! + executed: Boolean! + createdAt: BigInt! + startDate: BigInt! + endDate: BigInt! + plugin: AdminPlugin! + pluginProposalId: BigInt! + administrator: Administrator! + executionTxHash: Bytes +} + +interface IAction { + id: ID! # Plugin address + DAO address + Plugin proposal ID + Action index + to: Bytes! + value: BigInt! + data: Bytes! + daoAddress: Bytes! +} + +type Action implements IAction @entity(immutable: true) { + id: ID! # Plugin address + DAO address + Plugin proposal ID + Action index + to: Bytes! + value: BigInt! + data: Bytes! + daoAddress: Bytes! - "Set plugin specific related data below:" - number: BigInt + # proposal data + proposal: IProposal! } diff --git a/packages/subgraph/scripts/deploy-subgraph.sh b/packages/subgraph/scripts/deploy-subgraph.sh index fd15a08c..3f7a9db3 100755 --- a/packages/subgraph/scripts/deploy-subgraph.sh +++ b/packages/subgraph/scripts/deploy-subgraph.sh @@ -38,6 +38,17 @@ echo '' echo '> Deploying subgraph: '$FULLNAME echo '> Subgraph version: '$SUBGRAPH_VERSION +# check if the repo address is null or zero address +FILE=manifest/data/$SUBGRAPH_NETWORK_NAME'.json' + +address=$(jq -r '.PluginRepo.address' "$FILE") + +if [ "$address" = "null" ] || [ "$address" = "0x0000000000000000000000000000000000000000" ]; + then + echo "Repo address is not set properly, exiting..." + exit -1 +fi + # Deploy subgraph if [ "$LOCAL" ] then diff --git a/packages/subgraph/scripts/import-plugin-repo.ts b/packages/subgraph/scripts/import-plugin-repo.ts index 8f9d655c..b24590fe 100644 --- a/packages/subgraph/scripts/import-plugin-repo.ts +++ b/packages/subgraph/scripts/import-plugin-repo.ts @@ -1,29 +1,45 @@ +import {SupportedNetworks} from '@aragon/osx-commons-configs'; import dotenv from 'dotenv'; import fs from 'fs'; import path from 'path'; +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + // Specify the path to the .env file at the root const rootDir = path.join(__dirname, '../../../'); // Adjust the path as necessary dotenv.config({path: path.join(rootDir, '.env')}); +// path to the networks manifests +const manifestsPath = path.join(__dirname, '../manifest/data'); -// Extract Repo address from the production-network-deployments.json -function extractAndWriteAddressToTS(jsonPath: string): void { - // Read the production-network-deployments.json file - const aragonDeploymentsInfo = JSON.parse(fs.readFileSync(jsonPath, 'utf8')); - +function extractAndWriteAddressToTS(): void { // Get the network from environment variables const network = process.env.SUBGRAPH_NETWORK_NAME; - // Check if the network is defined in aragonDeploymentsInfo - if (!network || !aragonDeploymentsInfo[network]) { - throw new Error( - `Network '${network}' not found in production-network-deployments.json` + // Check if the network is provided and supported + if ( + !network || + !Object.values(SupportedNetworks).includes(network as SupportedNetworks) + ) { + throw new Error(`Network '${network}' invalid or not Supported.`); + } + + // get the plugin address from the network manifest + const networkManifestPath = path.join(manifestsPath, `${network}.json`); + let networkRepoAddress = JSON.parse( + fs.readFileSync(networkManifestPath, 'utf8') + ).PluginRepo.address; + + // check if address is null and throw warning and continue with zero address + if (!networkRepoAddress) { + console.warn( + '\x1b[33m%s\x1b[0m', + `WARNING: Plugin address for network '${network}' is null. Using zero address.` ); + networkRepoAddress = ZERO_ADDRESS; } - // Start the Map creation code with the specific network address const tsContent: string[] = [ - `export const PLUGIN_REPO_ADDRESS = '${aragonDeploymentsInfo[network].address}';`, + `export const PLUGIN_REPO_ADDRESS = '${networkRepoAddress}';`, ]; const outputDir = path.join(__dirname, '../imported'); @@ -42,8 +58,4 @@ function extractAndWriteAddressToTS(jsonPath: string): void { ); } -const aragonDeploymentsInfoPath = path.join( - rootDir, - 'production-network-deployments.json' -); -extractAndWriteAddressToTS(aragonDeploymentsInfoPath); +extractAndWriteAddressToTS(); diff --git a/packages/subgraph/scripts/postInstall.ts b/packages/subgraph/scripts/postInstall.ts index 05e01f48..0a0090d1 100644 --- a/packages/subgraph/scripts/postInstall.ts +++ b/packages/subgraph/scripts/postInstall.ts @@ -1,10 +1,16 @@ // Import the osx-ethers module to access OSX contracts and generate their ABIs for any needed version. -import {PluginSetupProcessor__factory} from '@aragon/osx-ethers'; +import { + PermissionManager__factory, + PluginSetupProcessor__factory, +} from '@aragon/osx-ethers'; import fs from 'fs'; import path from 'path'; // Add the contract factories to this array for the contracts you want to generate ABIs for. -const contractFactories = [PluginSetupProcessor__factory]; +const contractFactories = [ + PluginSetupProcessor__factory, + PermissionManager__factory, +]; function generateABIFiles(contractFactories: any[]): void { // Iterate through each contract factory passed. diff --git a/packages/subgraph/src/osx/pluginSetupProcessor.ts b/packages/subgraph/src/osx/pluginSetupProcessor.ts index ac0aabdf..7c70e6a2 100644 --- a/packages/subgraph/src/osx/pluginSetupProcessor.ts +++ b/packages/subgraph/src/osx/pluginSetupProcessor.ts @@ -1,9 +1,9 @@ import {InstallationPrepared} from '../../generated/PluginSetupProcessor/PluginSetupProcessor'; -import {DaoPlugin} from '../../generated/schema'; +import {AdminPlugin} from '../../generated/schema'; import {Plugin as PluginTemplate} from '../../generated/templates'; import {PLUGIN_REPO_ADDRESS} from '../../imported/repo-address'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, DataSourceContext, log} from '@graphprotocol/graph-ts'; +import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; +import {Address, DataSourceContext} from '@graphprotocol/graph-ts'; export function handleInstallationPrepared(event: InstallationPrepared): void { const pluginRepo = event.params.pluginSetupRepo; @@ -16,35 +16,26 @@ export function handleInstallationPrepared(event: InstallationPrepared): void { return; } - const dao = event.params.dao; - const plugin = event.params.plugin; - - // Generate a unique ID for the plugin installation. - const installationId = generatePluginInstallationEntityId(dao, plugin); - // Log an error and exit if unable to generate the installation ID. - if (!installationId) { - log.error('Failed to generate installationId', [ - dao.toHexString(), - plugin.toHexString(), - ]); - return; - } - // Load or create a new entry for the this plugin using the generated installation ID. - let pluginEntity = DaoPlugin.load(installationId!); + const daoAddress = event.params.dao; + const pluginAddress = event.params.plugin; + const pluginId = generatePluginEntityId(pluginAddress); + + // Load or create a new entry for the this plugin using the generated plugin ID. + let pluginEntity = AdminPlugin.load(pluginId); if (!pluginEntity) { - pluginEntity = new DaoPlugin(installationId!); + pluginEntity = new AdminPlugin(pluginId); } // Set the DAO and plugin address for the plugin entity. - pluginEntity.dao = dao; - pluginEntity.pluginAddress = plugin; + pluginEntity.daoAddress = daoAddress; + pluginEntity.pluginAddress = pluginAddress; // Initialize a context for the plugin data source to enable indexing from the moment of preparation. const context = new DataSourceContext(); // Include the DAO address in the context for future reference. - context.setString('daoAddress', dao.toHexString()); + context.setString('daoAddress', daoAddress.toHexString()); // Deploy a template for the plugin to facilitate individual contract indexing. - PluginTemplate.createWithContext(plugin, context); + PluginTemplate.createWithContext(pluginAddress, context); pluginEntity.save(); } diff --git a/packages/subgraph/src/plugin/adminMembers.ts b/packages/subgraph/src/plugin/adminMembers.ts new file mode 100644 index 00000000..9c6abe52 --- /dev/null +++ b/packages/subgraph/src/plugin/adminMembers.ts @@ -0,0 +1,81 @@ +import {Administrator, AdministratorAdminPlugin} from '../../generated/schema'; +import { + Granted, + Revoked, +} from '../../generated/templates/AdminMembers/PermissionManager'; +import {generateAdministratorAdminPluginEntityId} from '../utils/ids'; +import { + generateEntityIdFromAddress, + generatePluginEntityId, +} from '@aragon/osx-commons-subgraph'; +import {dataSource, store} from '@graphprotocol/graph-ts'; + +export function handleGranted(event: Granted): void { + if ( + isCorrectEvent( + event.params.permissionId.toHexString(), + event.params.where.toHexString() + ) + ) { + const pluginAddress = event.params.where; + const administratorMemberAddress = event.params.who; + const pluginEntityId = generatePluginEntityId(pluginAddress); + const administratorMemberEntityId = generateEntityIdFromAddress( + administratorMemberAddress + ); + let administrator = Administrator.load(administratorMemberEntityId); + if (!administrator) { + administrator = new Administrator(administratorMemberEntityId); + administrator.address = administratorMemberEntityId; + administrator.save(); + } + + const administratorAdminMappingId = + generateAdministratorAdminPluginEntityId( + pluginAddress, + administratorMemberAddress + ); + let administratorPluginMapping = AdministratorAdminPlugin.load( + administratorAdminMappingId + ); + if (!administratorPluginMapping) { + administratorPluginMapping = new AdministratorAdminPlugin( + administratorAdminMappingId + ); + administratorPluginMapping.administrator = administratorMemberEntityId; + administratorPluginMapping.plugin = pluginEntityId; + administratorPluginMapping.save(); + } + } +} + +export function handleRevoked(event: Revoked): void { + if ( + isCorrectEvent( + event.params.permissionId.toHexString(), + event.params.where.toHexString() + ) + ) { + const pluginAddress = event.params.where; + const administratorMemberAddress = event.params.who; + const mappingId = generateAdministratorAdminPluginEntityId( + pluginAddress, + administratorMemberAddress + ); + if (AdministratorAdminPlugin.load(mappingId)) { + store.remove('AdministratorAdminPlugin', mappingId); + } + } +} + +function isCorrectEvent(permissionId: string, where: string): boolean { + const context = dataSource.context(); + const requiredPermissionId = context.getString('permissionId'); + if (permissionId == requiredPermissionId) { + const pluginAddress = context.getString('pluginAddress'); + if (where == pluginAddress) { + return true; + } + } + return false; +} diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index aa004d71..c2966dd5 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -1,24 +1,120 @@ -import {DaoPlugin} from '../../generated/schema'; -import {NumberStored} from '../../generated/templates/Plugin/Plugin'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, dataSource} from '@graphprotocol/graph-ts'; - -export function handleNumberStored(event: NumberStored): void { - const pluginAddress = event.address; +import { + Action, + AdministratorAdminPlugin, + AdminProposal, + Administrator, +} from '../../generated/schema'; +import {AdminMembers} from '../../generated/templates'; +import { + MembershipContractAnnounced, + ProposalCreated, + ProposalExecuted, +} from '../../generated/templates/Plugin/Admin'; +import {EXECUTE_PROPOSAL_PERMISSION_ID} from '../utils/constants'; +import {generateAdministratorAdminPluginEntityId} from '../utils/ids'; +import { + generateActionEntityId, + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, dataSource, DataSourceContext} from '@graphprotocol/graph-ts'; +export function handleProposalCreated(event: ProposalCreated): void { const context = dataSource.context(); - const daoId = context.getString('daoAddress'); + const daoAddress = context.getString('daoAddress'); + const metadata = event.params.metadata.toString(); + _handleProposalCreated(event, daoAddress, metadata); +} - const installationId = generatePluginInstallationEntityId( - Address.fromString(daoId), +export function _handleProposalCreated( + event: ProposalCreated, + daoAddress: string, + metadata: string +): void { + const pluginProposalId = event.params.proposalId; + const pluginAddress = event.address; + const pluginEntityId = generatePluginEntityId(pluginAddress); + const proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId + ); + const administratorAddress = event.params.creator; + + const proposalEntity = new AdminProposal(proposalEntityId); + proposalEntity.daoAddress = Address.fromHexString(daoAddress); + proposalEntity.plugin = pluginEntityId; + proposalEntity.pluginProposalId = pluginProposalId; + proposalEntity.creator = administratorAddress; + proposalEntity.metadata = metadata; + proposalEntity.executed = false; + proposalEntity.createdAt = event.block.timestamp; + proposalEntity.startDate = event.params.startDate; + proposalEntity.endDate = event.params.endDate; + proposalEntity.administrator = administratorAddress.toHexString(); + proposalEntity.allowFailureMap = event.params.allowFailureMap; + const administratorEntityId = generateAdministratorAdminPluginEntityId( + administratorAddress, pluginAddress ); + let adminMemberEntity = AdministratorAdminPlugin.load(administratorEntityId); + if (!adminMemberEntity) { + adminMemberEntity = new AdministratorAdminPlugin(administratorEntityId); + adminMemberEntity.administrator = administratorAddress.toHexString(); + adminMemberEntity.plugin = pluginEntityId; + adminMemberEntity.save(); + } + let administratorEntity = Administrator.load( + administratorAddress.toHexString() + ); + if (!administratorEntity) { + administratorEntity = new Administrator(administratorAddress.toHexString()); + administratorEntity.address = administratorAddress.toHexString(); + administratorEntity.save(); + } + + // actions + const actions = event.params.actions; + for (let index = 0; index < actions.length; index++) { + const action = actions[index]; + + const actionEntityId = generateActionEntityId( + pluginAddress, + Address.fromString(daoAddress), + pluginProposalId.toString(), + index + ); + const actionEntity = new Action(actionEntityId); + actionEntity.to = action.to; + actionEntity.value = action.value; + actionEntity.data = action.data; + actionEntity.daoAddress = Address.fromHexString(daoAddress); + actionEntity.proposal = proposalEntityId; + actionEntity.save(); + } + + proposalEntity.save(); +} + +export function handleProposalExecuted(event: ProposalExecuted): void { + const pluginProposalId = event.params.proposalId; + const proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); - if (installationId) { - const pluginEntity = DaoPlugin.load(installationId); - if (pluginEntity) { - pluginEntity.number = event.params.number; - pluginEntity.save(); - } + const proposalEntity = AdminProposal.load(proposalEntityId); + if (proposalEntity) { + proposalEntity.executed = true; + proposalEntity.executionTxHash = event.transaction.hash; + proposalEntity.save(); } } + +export function handleMembershipContractAnnounced( + event: MembershipContractAnnounced +): void { + const context = new DataSourceContext(); + context.setString('pluginAddress', event.address.toHexString()); + context.setString('permissionId', EXECUTE_PROPOSAL_PERMISSION_ID); + AdminMembers.createWithContext(event.params.definingContract, context); +} diff --git a/packages/subgraph/src/utils/constants.ts b/packages/subgraph/src/utils/constants.ts new file mode 100644 index 00000000..7b4547f1 --- /dev/null +++ b/packages/subgraph/src/utils/constants.ts @@ -0,0 +1,3 @@ +// keccack256 of EXECUTE_PROPOSAL_PERMISSION +export const EXECUTE_PROPOSAL_PERMISSION_ID = + '0xf281525e53675515a6ba7cc7bea8a81e649b3608423ee2d73be1752cea887889'; diff --git a/packages/subgraph/src/utils/ids.ts b/packages/subgraph/src/utils/ids.ts new file mode 100644 index 00000000..8af8ca70 --- /dev/null +++ b/packages/subgraph/src/utils/ids.ts @@ -0,0 +1,12 @@ +import {generateEntityIdFromAddress} from '@aragon/osx-commons-subgraph'; +import {Address} from '@graphprotocol/graph-ts'; + +export function generateAdministratorAdminPluginEntityId( + pluginAddress: Address, + administratorAddress: Address +): string { + return [ + generateEntityIdFromAddress(pluginAddress), + generateEntityIdFromAddress(administratorAddress), + ].join('_'); +} diff --git a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts index 920203aa..446d2286 100644 --- a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts +++ b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts @@ -12,7 +12,7 @@ import { PLUGIN_SETUP_ID, } from '../utils/constants'; import {createInstallationPreparedEvent} from '../utils/events'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; +import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; import {assert, afterEach, clearStore, test, describe} from 'matchstick-as'; @@ -27,12 +27,11 @@ describe('OSx', () => { // Create event const daoAddress = DAO_ADDRESS; const pluginAddress = CONTRACT_ADDRESS; - const installationId = generatePluginInstallationEntityId( - Address.fromString(daoAddress), + const pluginId = generatePluginEntityId( Address.fromString(pluginAddress) ); - if (!installationId) { - throw new Error('Failed to get installationId'); + if (!pluginId) { + throw new Error('Failed to get pluginId'); } const setupId = PLUGIN_SETUP_ID; const versionTuple = new ethereum.Tuple(); @@ -76,8 +75,8 @@ describe('OSx', () => { handleInstallationPrepared(event1); - assert.notInStore('DaoPlugin', installationId!); - assert.entityCount('DaoPlugin', 0); + assert.notInStore('AdminPlugin', pluginId!); + assert.entityCount('AdminPlugin', 0); const thisPluginRepoAddress = PLUGIN_REPO_ADDRESS; @@ -95,8 +94,8 @@ describe('OSx', () => { handleInstallationPrepared(event2); - assert.entityCount('DaoPlugin', 1); - assert.fieldEquals('DaoPlugin', installationId!, 'id', installationId!); + assert.entityCount('AdminPlugin', 1); + assert.fieldEquals('AdminPlugin', pluginId!, 'id', pluginId!); }); }); }); diff --git a/packages/subgraph/tests/plugin/adminMembers.test.ts b/packages/subgraph/tests/plugin/adminMembers.test.ts new file mode 100644 index 00000000..6997987e --- /dev/null +++ b/packages/subgraph/tests/plugin/adminMembers.test.ts @@ -0,0 +1,127 @@ +import {Administrator, AdministratorAdminPlugin} from '../../generated/schema'; +import {handleGranted, handleRevoked} from '../../src/plugin/adminMembers'; +import {EXECUTE_PROPOSAL_PERMISSION_ID} from '../../src/utils/constants'; +import {generateAdministratorAdminPluginEntityId} from '../../src/utils/ids'; +import {ADDRESS_ONE, ADDRESS_TWO, DAO_ADDRESS} from '../utils/constants'; +import {createGrantedEvent, createRevokedEvent} from '../utils/events/plugin'; +import {generateEntityIdFromAddress} from '@aragon/osx-commons-subgraph'; +import {Address, DataSourceContext} from '@graphprotocol/graph-ts'; +import { + assert, + clearStore, + dataSourceMock, + test, + describe, + beforeEach, + afterEach, +} from 'matchstick-as/assembly/index'; + +const adminAddress = Address.fromString(ADDRESS_ONE); +const administratorEntityId = generateEntityIdFromAddress(adminAddress); +const pluginAddress = Address.fromString(ADDRESS_TWO); +const pluginEntityId = generateEntityIdFromAddress(pluginAddress); + +describe('AdminMembers', function () { + // keccack256 of EXECUTE_PROPOSAL_PERMISSION + + beforeEach(function () { + let context = new DataSourceContext(); + context.setString('permissionId', EXECUTE_PROPOSAL_PERMISSION_ID); + context.setString('pluginAddress', pluginEntityId); + dataSourceMock.setContext(context); + }); + + afterEach(function () { + clearStore(); + }); + + test('handleGranted', function () { + // check the entities are not in the store + assert.entityCount('Administrator', 0); + assert.entityCount('AdministratorAdminPlugin', 0); + + // create the event and handle it + let event = createGrantedEvent( + EXECUTE_PROPOSAL_PERMISSION_ID, + DAO_ADDRESS, + pluginEntityId, + administratorEntityId + ); + handleGranted(event); + + // check the administrator entity + assert.entityCount('Administrator', 1); + assert.fieldEquals( + 'Administrator', + administratorEntityId, + 'id', + administratorEntityId + ); + assert.fieldEquals( + 'Administrator', + administratorEntityId, + 'address', + administratorEntityId + ); + + // check the mapping with the admin pluging entity + assert.entityCount('AdministratorAdminPlugin', 1); + let administratorAdminPluginId = generateAdministratorAdminPluginEntityId( + pluginAddress, + adminAddress + ); + assert.fieldEquals( + 'AdministratorAdminPlugin', + administratorAdminPluginId, + 'id', + administratorAdminPluginId + ); + assert.fieldEquals( + 'AdministratorAdminPlugin', + administratorAdminPluginId, + 'administrator', + administratorEntityId + ); + assert.fieldEquals( + 'AdministratorAdminPlugin', + administratorAdminPluginId, + 'plugin', + pluginEntityId + ); + }); + + test('handleRevoked', function () { + let administrator = new Administrator(administratorEntityId); + administrator.address = administratorEntityId; + administrator.save(); + + let administratorAdminPluginId = generateAdministratorAdminPluginEntityId( + pluginAddress, + adminAddress + ); + let administratorAdminPluginEntity = new AdministratorAdminPlugin( + administratorAdminPluginId + ); + administratorAdminPluginEntity.administrator = administratorEntityId; + administratorAdminPluginEntity.plugin = pluginEntityId; + administratorAdminPluginEntity.save(); + + // check the entities are in the store + assert.entityCount('Administrator', 1); + assert.entityCount('AdministratorAdminPlugin', 1); + + // create revoke event and handle it + let revokedEvent = createRevokedEvent( + EXECUTE_PROPOSAL_PERMISSION_ID, + DAO_ADDRESS, + pluginEntityId, + administratorEntityId + ); + handleRevoked(revokedEvent); + + // when revoking the permission the admin is not removed, only the mapping with the admin plugin + assert.entityCount('Administrator', 1); + assert.entityCount('AdministratorAdminPlugin', 0); + assert.notInStore('AdministratorAdminPlugin', administratorAdminPluginId); + }); +}); diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 14db4cc4..74d332f0 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -1,9 +1,39 @@ -import {DaoPlugin} from '../../generated/schema'; -import {handleNumberStored} from '../../src/plugin/plugin'; -import {CONTRACT_ADDRESS, DAO_ADDRESS} from '../utils/constants'; -import {createNewNumberStoredEvent} from '../utils/events'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, DataSourceContext} from '@graphprotocol/graph-ts'; +import {Action} from '../../generated/schema'; +import { + handleProposalExecuted, + _handleProposalCreated, + handleMembershipContractAnnounced, +} from '../../src/plugin/plugin'; +import { + ADDRESS_ONE, + ADDRESS_TWO, + STRING_DATA, + PLUGIN_PROPOSAL_ID, + START_DATE, + ALLOW_FAILURE_MAP, + CONTRACT_ADDRESS, + DAO_ADDRESS, +} from '../utils/constants'; +import { + createNewProposalCreatedEvent, + createProposalExecutedEvent, + createMembershipContractAnnouncedEvent, + createAdminPluginState, + createAdminProposalState, +} from '../utils/events/plugin'; +import { + generateActionEntityId, + generatePluginEntityId, + generateProposalEntityId, + createDummyAction, +} from '@aragon/osx-commons-subgraph'; +import { + Address, + Bytes, + BigInt, + DataSourceContext, +} from '@graphprotocol/graph-ts'; +import {dataSource} from '@graphprotocol/graph-ts'; import { assert, afterEach, @@ -14,6 +44,18 @@ import { dataSourceMock, } from 'matchstick-as'; +const actionValue = '0'; +const actionData = '0x00000000'; +const actions = [createDummyAction(ADDRESS_TWO, actionValue, actionData)]; + +const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginEntityId = generatePluginEntityId(pluginAddress); +const pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID); +const proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId +); + describe('Plugin', () => { beforeEach(function () { let context = new DataSourceContext(); @@ -25,36 +67,199 @@ describe('Plugin', () => { clearStore(); }); - describe('NumberStored Event', () => { - test('it should store the correct number emitted from the event', () => { - const daoAddress = Address.fromString(DAO_ADDRESS); - const pluginAddress = Address.fromString(CONTRACT_ADDRESS); + describe('handleProposalCreated', () => { + test('test the event', () => { + // check the entities are not in the store + assert.entityCount('AdminProposal', 0); + assert.entityCount('Action', 0); - const installationId = generatePluginInstallationEntityId( - daoAddress, - pluginAddress + // create state + createAdminPluginState(pluginEntityId); + + // create event + let event = createNewProposalCreatedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_ONE, + START_DATE, + START_DATE, + STRING_DATA, + actions, + ALLOW_FAILURE_MAP, + pluginEntityId + ); + + // handle event + _handleProposalCreated(event, DAO_ADDRESS, STRING_DATA); + + let proposalEntityId = generateProposalEntityId( + pluginAddress, + BigInt.fromString(PLUGIN_PROPOSAL_ID) ); - if (!installationId) { - throw new Error('Failed to get installationId'); - } - // Create state - let daoPlugin = new DaoPlugin(installationId!); - daoPlugin.dao = daoAddress; - daoPlugin.pluginAddress = pluginAddress; - daoPlugin.save(); - const number = '5'; + // checks proposal + assert.entityCount('AdminProposal', 1); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'id', + proposalEntityId + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'daoAddress', + DAO_ADDRESS + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'plugin', + pluginEntityId + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'pluginProposalId', + PLUGIN_PROPOSAL_ID + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'creator', + ADDRESS_ONE + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'metadata', + STRING_DATA + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'executed', + 'false' + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'createdAt', + event.block.timestamp.toString() + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'startDate', + START_DATE + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'endDate', + START_DATE + ); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'allowFailureMap', + ALLOW_FAILURE_MAP + ); - const event = createNewNumberStoredEvent( - number, - pluginAddress.toHexString() + // check action + assert.entityCount('Action', 1); + const actionEntityId = generateActionEntityId( + pluginAddress, + Address.fromString(DAO_ADDRESS), + pluginProposalId.toString(), + 0 + ); + const actionEntity = Action.load(actionEntityId); + assert.entityCount('Action', 1); + assert.fieldEquals('Action', actionEntityId, 'id', actionEntityId); + assert.fieldEquals('Action', actionEntityId, 'to', ADDRESS_TWO); + assert.fieldEquals('Action', actionEntityId, 'value', actionValue); + assert.fieldEquals('Action', actionEntityId, 'data', actionData); + assert.fieldEquals('Action', actionEntityId, 'daoAddress', DAO_ADDRESS); + assert.fieldEquals( + 'Action', + actionEntityId, + 'proposal', + proposalEntityId + ); + }); + }); + + describe('handleProposalExecuted', () => { + test('test the event', () => { + // create state + createAdminPluginState(pluginEntityId); + + let administratorAddress = Address.fromString(ADDRESS_ONE); + + createAdminProposalState(proposalEntityId, administratorAddress); + + const actionEntityId = generateActionEntityId( + pluginAddress, + Address.fromString(DAO_ADDRESS), + pluginProposalId.toString(), + 0 + ); + let action = new Action(actionEntityId); + action.to = Address.fromString(ADDRESS_TWO); + action.value = BigInt.fromString(actionValue); + action.data = Bytes.fromHexString(actionData); + action.daoAddress = Address.fromString(DAO_ADDRESS); + action.proposal = proposalEntityId; + action.save(); + + assert.entityCount('AdminProposal', 1); + + // create event + let event = createProposalExecutedEvent( + PLUGIN_PROPOSAL_ID, + pluginEntityId + ); + + // handle event + handleProposalExecuted(event); + + // checks + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'id', + proposalEntityId + ); + assert.fieldEquals('AdminProposal', proposalEntityId, 'executed', 'true'); + assert.fieldEquals( + 'AdminProposal', + proposalEntityId, + 'executionTxHash', + event.transaction.hash.toHexString() + ); + }); + }); + + describe('handleMembershipContractAnnounced', () => { + test('test the event', () => { + let context = dataSource.context(); + + assert.dataSourceCount('AdminMembers', 0); + assert.assertNull(context.get('pluginAddress')); + assert.assertNull(context.get('permissionId')); + + // create event + let event = createMembershipContractAnnouncedEvent( + DAO_ADDRESS, + pluginAddress ); - handleNumberStored(event); + // handle event + handleMembershipContractAnnounced(event); - assert.fieldEquals('DaoPlugin', installationId!, 'id', installationId!); - assert.fieldEquals('DaoPlugin', installationId!, 'number', number); - assert.entityCount('DaoPlugin', 1); + // check + assert.dataSourceCount('AdminMembers', 1); + assert.dataSourceExists('AdminMembers', DAO_ADDRESS); }); }); }); diff --git a/packages/subgraph/tests/utils/constants.ts b/packages/subgraph/tests/utils/constants.ts index f0ea51a9..1357a64b 100644 --- a/packages/subgraph/tests/utils/constants.ts +++ b/packages/subgraph/tests/utils/constants.ts @@ -3,6 +3,8 @@ // DAO_ADDRESS: A placeholder address for a DAO instance. // CONTRACT_ADDRESS: A placeholder address for a contract instance. // PLUGIN_SETUP_ID: A mock identifier for a plugin setup in test simulations. +export const ZERO = '0'; + export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000'; export const ADDRESS_ONE = '0x0000000000000000000000000000000000000001'; export const ADDRESS_TWO = '0x0000000000000000000000000000000000000002'; @@ -12,5 +14,14 @@ export const ADDRESS_FIVE = '0x0000000000000000000000000000000000000005'; export const ADDRESS_SIX = '0x0000000000000000000000000000000000000006'; export const DAO_ADDRESS = '0x00000000000000000000000000000000000000da'; export const CONTRACT_ADDRESS = '0x00000000000000000000000000000000000000Ad'; + export const PLUGIN_SETUP_ID = '0xfb3fd2c4cd4e19944dd3f8437e67476240cd9e3efb2294ebd10c59c8f1d6817c'; + +export const STRING_DATA = 'Some String Data ...'; + +export const PLUGIN_PROPOSAL_ID = ZERO; + +export const START_DATE = '1644851000'; + +export const ALLOW_FAILURE_MAP = '1'; diff --git a/packages/subgraph/tests/utils/events/plugin.ts b/packages/subgraph/tests/utils/events/plugin.ts index 58df89f7..2dba9f4d 100644 --- a/packages/subgraph/tests/utils/events/plugin.ts +++ b/packages/subgraph/tests/utils/events/plugin.ts @@ -1,22 +1,232 @@ -import {NumberStored} from '../../../generated/templates/Plugin/Plugin'; -import {Address, BigInt, ethereum} from '@graphprotocol/graph-ts'; +import {AdminPlugin, AdminProposal} from '../../../generated/schema'; +import { + Granted, + Revoked, +} from '../../../generated/templates/AdminMembers/PermissionManager'; +import { + ProposalCreated, + ProposalExecuted, + MembershipContractAnnounced, +} from '../../../generated/templates/Plugin/Admin'; +import { + ADDRESS_ZERO, + STRING_DATA, + PLUGIN_PROPOSAL_ID, + START_DATE, + ALLOW_FAILURE_MAP, + CONTRACT_ADDRESS, + DAO_ADDRESS, +} from '../constants'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; import {newMockEvent} from 'matchstick-as'; -export function createNewNumberStoredEvent( - number: string, +export function createNewProposalCreatedEvent( + proposalId: string, + creator: string, + startDate: string, + endDate: string, + metadata: string, + actions: ethereum.Tuple[], + allowFailureMap: string, contractAddress: string -): NumberStored { - let createNumberStoredEvent = changetype(newMockEvent()); +): ProposalCreated { + let createProposalCreatedEvent = changetype(newMockEvent()); - createNumberStoredEvent.address = Address.fromString(contractAddress); - createNumberStoredEvent.parameters = []; + createProposalCreatedEvent.address = Address.fromString(contractAddress); + createProposalCreatedEvent.parameters = []; let proposalIdParam = new ethereum.EventParam( - 'number', - ethereum.Value.fromSignedBigInt(BigInt.fromString(number)) + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) ); + let creatorParam = new ethereum.EventParam( + 'creator', + ethereum.Value.fromAddress(Address.fromString(creator)) + ); + let startDateParam = new ethereum.EventParam( + 'startDate', + ethereum.Value.fromSignedBigInt(BigInt.fromString(startDate)) + ); + let endDateParam = new ethereum.EventParam( + 'endDate', + ethereum.Value.fromSignedBigInt(BigInt.fromString(endDate)) + ); + let metadataParam = new ethereum.EventParam( + 'metadata', + ethereum.Value.fromBytes(Bytes.fromUTF8(metadata)) + ); + let actionsParam = new ethereum.EventParam( + 'actions', + ethereum.Value.fromTupleArray(actions) + ); + let allowFailureMapParam = new ethereum.EventParam( + 'allowFailureMap', + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)) + ); + + createProposalCreatedEvent.parameters.push(proposalIdParam); + createProposalCreatedEvent.parameters.push(creatorParam); + createProposalCreatedEvent.parameters.push(startDateParam); + createProposalCreatedEvent.parameters.push(endDateParam); + createProposalCreatedEvent.parameters.push(metadataParam); + createProposalCreatedEvent.parameters.push(actionsParam); + createProposalCreatedEvent.parameters.push(allowFailureMapParam); + + return createProposalCreatedEvent; +} + +export function createProposalExecutedEvent( + proposalId: string, + contractAddress: string +): ProposalExecuted { + let createProposalExecutedEvent = changetype( + newMockEvent() + ); + + createProposalExecutedEvent.address = Address.fromString(contractAddress); + createProposalExecutedEvent.parameters = []; + + let proposalIdParam = new ethereum.EventParam( + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) + ); + + createProposalExecutedEvent.parameters.push(proposalIdParam); + + return createProposalExecutedEvent; +} + +export function createGrantedEvent( + permissionId: string, + dao: string, + plugin: string, + member: string +): Granted { + let newGrantedEvent = changetype(newMockEvent()); + + newGrantedEvent.address = Address.fromString(dao); + newGrantedEvent.parameters = []; + + let permissionIdParam = new ethereum.EventParam( + 'permissionId', + ethereum.Value.fromBytes(Bytes.fromHexString(permissionId)) + ); + let hereParam = new ethereum.EventParam( + 'here', + ethereum.Value.fromAddress(Address.fromString(dao)) + ); + let whereParam = new ethereum.EventParam( + 'where', + ethereum.Value.fromAddress(Address.fromString(plugin)) + ); + let whoParam = new ethereum.EventParam( + 'who', + ethereum.Value.fromAddress(Address.fromString(member)) + ); + let conditionParam = new ethereum.EventParam( + 'condition', + ethereum.Value.fromAddress(Address.fromString(ADDRESS_ZERO)) + ); + + newGrantedEvent.parameters.push(permissionIdParam); + newGrantedEvent.parameters.push(hereParam); + newGrantedEvent.parameters.push(whereParam); + newGrantedEvent.parameters.push(whoParam); + newGrantedEvent.parameters.push(conditionParam); + + return newGrantedEvent; +} + +export function createRevokedEvent( + permissionId: string, + dao: string, + plugin: string, + member: string +): Revoked { + let newRevokedEvent = changetype(newMockEvent()); + + newRevokedEvent.address = Address.fromString(dao); + newRevokedEvent.parameters = []; + + let permissionIdParam = new ethereum.EventParam( + 'permissionId', + ethereum.Value.fromBytes(Bytes.fromHexString(permissionId)) + ); + let hereParam = new ethereum.EventParam( + 'here', + ethereum.Value.fromAddress(Address.fromString(dao)) + ); + let whereParam = new ethereum.EventParam( + 'where', + ethereum.Value.fromAddress(Address.fromString(plugin)) + ); + let whoParam = new ethereum.EventParam( + 'who', + ethereum.Value.fromAddress(Address.fromString(member)) + ); + + newRevokedEvent.parameters.push(permissionIdParam); + newRevokedEvent.parameters.push(hereParam); + newRevokedEvent.parameters.push(whereParam); + newRevokedEvent.parameters.push(whoParam); + + return newRevokedEvent; +} + +export function createMembershipContractAnnouncedEvent( + definingContract: string, + contractAddress: Address +): MembershipContractAnnounced { + let newMembershipContractAnnouncedEvent = + changetype(newMockEvent()); + + newMembershipContractAnnouncedEvent.address = contractAddress; + newMembershipContractAnnouncedEvent.parameters = []; + + let definingContractParam = new ethereum.EventParam( + 'definingContract', + ethereum.Value.fromAddress(Address.fromString(definingContract)) + ); + + newMembershipContractAnnouncedEvent.parameters.push(definingContractParam); + + return newMembershipContractAnnouncedEvent; +} + +// state + +export function createAdminPluginState( + pluginEntityId: string, + daoAddress: string = DAO_ADDRESS, + pluginAddress: string = CONTRACT_ADDRESS +): AdminPlugin { + let adminPlugin = new AdminPlugin(pluginEntityId); + adminPlugin.daoAddress = Address.fromString(daoAddress); + adminPlugin.pluginAddress = Address.fromString(pluginAddress); + adminPlugin.save(); + + return adminPlugin; +} - createNumberStoredEvent.parameters.push(proposalIdParam); +export function createAdminProposalState( + proposalEntityId: string, + administratorAddress: Address, + daoEntityId: string = DAO_ADDRESS, + pluginEntityId: string = CONTRACT_ADDRESS +): AdminProposal { + let adminProposal = new AdminProposal(proposalEntityId); + adminProposal.daoAddress = Address.fromString(daoEntityId); + adminProposal.plugin = pluginEntityId; + adminProposal.pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID); + adminProposal.creator = administratorAddress; + adminProposal.metadata = STRING_DATA; + adminProposal.executed = false; + adminProposal.createdAt = BigInt.fromString(START_DATE); + adminProposal.startDate = BigInt.fromString(START_DATE); + adminProposal.endDate = BigInt.fromString(START_DATE); + adminProposal.allowFailureMap = BigInt.fromString(ALLOW_FAILURE_MAP); + adminProposal.administrator = administratorAddress.toHexString(); + adminProposal.save(); - return createNumberStoredEvent; + return adminProposal; } diff --git a/packages/subgraph/yarn.lock b/packages/subgraph/yarn.lock index e209c607..2bbe4484 100644 --- a/packages/subgraph/yarn.lock +++ b/packages/subgraph/yarn.lock @@ -2,17 +2,17 @@ # yarn lockfile v1 -"@aragon/osx-commons-configs@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.2.0.tgz#32f83596f4a2e9e48aef61cf560c1c5b4d32a049" - integrity sha512-wCFtgmuGCzs8L5mCxVCYQ6uEu69IrofS7q2w7E1Fjk7/nWuSmRUpgmif3ki9BQq1qpOvDu2P+u3UNLnIz8J82g== +"@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== dependencies: tslib "^2.6.2" -"@aragon/osx-commons-subgraph@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-subgraph/-/osx-commons-subgraph-0.0.4.tgz#2aa52f3089d21189c9152d2f3d14c0d7c66d129f" - integrity sha512-cqhusJ3HNvMx+t9lXfN+Hy/5ipefNs1Tdxe+y0GvD4qgBMVU4tCbsxOpB9U2JEJNBCzFQj4E/872FFLpIErB4w== +"@aragon/osx-commons-subgraph@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-subgraph/-/osx-commons-subgraph-0.0.5.tgz#7e0c0f854e4ca52de1d937595c9bb6ef0370f840" + integrity sha512-M5edVTYyHbkcDLr2H8ySCbOpLA+5pUdN7tCYCif0pDP99Wb+/njgO23G2B2FjB4Q3hB0fCkLkQwNp9QplJjqGA== dependencies: "@graphprotocol/graph-ts" "0.31.0" @@ -2702,10 +2702,10 @@ make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -matchstick-as@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/matchstick-as/-/matchstick-as-0.5.2.tgz#6a6dde02d1d939c32458bd67bac688891a07a34c" - integrity sha512-fb1OVphDKEvJY06Ue02Eh1CNncuW95vp6b8tNAP7UIqplICSLoU/zgN6U7ge7R0upsoO78C7CRi4EyK/7Jxz7g== +matchstick-as@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/matchstick-as/-/matchstick-as-0.6.0.tgz#c65296b1f51b1014d605c52067d9b5321ea630e8" + integrity sha512-E36fWsC1AbCkBFt05VsDDRoFvGSdcZg6oZJrtIe/YDBbuFh8SKbR5FcoqDhNWqSN+F7bN/iS2u8Md0SM+4pUpw== dependencies: wabt "1.0.24"