diff --git a/.github/workflows/subgraph-tests.yml b/.github/workflows/subgraph-tests.yml index 3dbc32e90..44da3a29f 100644 --- a/.github/workflows/subgraph-tests.yml +++ b/.github/workflows/subgraph-tests.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-node@v3 with: cache: 'yarn' - node-version: 16 + node-version: 18 - name: Install dependencies run: yarn install --pure-lockfile - name: Build manifest @@ -56,7 +56,7 @@ jobs: uses: actions/setup-node@v3 with: cache: 'yarn' - node-version: 16 + node-version: 18 - name: Install dependencies run: yarn - name: Build contracts diff --git a/.gitignore b/.gitignore index 29c50aa8d..192225d29 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # deployments deployments +deployments-zk deployed_contracts.json managingDAOTX.json diff --git a/DEPLOYMENT_CHECKLIST.md b/DEPLOYMENT_CHECKLIST.md index 2d1e9e50b..6dba5ce52 100644 --- a/DEPLOYMENT_CHECKLIST.md +++ b/DEPLOYMENT_CHECKLIST.md @@ -1,108 +1,96 @@ # Deployment Checklist -This checklist is seen as a guide to deploy the stack to a new chain. - -## Pre-Deployment - -- [ ] Verify that the deployers wallet has enough funds. -- [ ] Check that the subgraph hoster supports the network OSx is deployed to. -- [ ] Make sure you are using Node v16 -- [ ] Bump the OSx protocol version in the `ProtocolVersion.sol` file. -- [ ] Check that version tags are set correctly in the plugin repo deploy scripts `packages/contracts/deploy/new/30_plugins/10_plugin-repos` to ensure synchronized version numbers across all supported networks. -- [ ] Choose an ENS domain for DAOs -- [ ] Choose an ENS domain for plugins -- [ ] Check if there is an official ENS deployment for the chosen chain and if yes: - - [ ] Check if there is already an entry for it in `packages/contracts/deploy/helpers.ts` - - [ ] Check that the owner of the DAO domain is the deployer - - [ ] Check that the owner of the plugin domain is the deployer -- [ ] Run `yarn` in the repository root to install the dependencies -- [ ] Run `yarn build` in `packages/contracts` to make sure the contracts compile - - [ ] Check that the compiler version in `hardhat.config.ts` is set to at least `0.8.17` and on the [known solidity bugs page](https://docs.soliditylang.org/en/latest/bugs.html) that no relevant vulnerabilities exist that are fixed in later versions. If the latter is not the case, consider updating the compiler pragmas to a safe version and rolling out fixes for affected contracts. -- [ ] Run `yarn test` in `packages/contracts` to make sure the contract tests succeed -- [ ] Run `yarn deploy --deploy-scripts deploy/new --network hardhat --reset` to make sure the deploy scripts work -- [ ] Set `ETH_KEY` in `.env` to the deployers private key -- [ ] Set the right API key for the chains blockchain explorer in `.env` (e.g. for mainnet it is `ETHERSCAN_KEY`) -- [ ] Set the chosen DAO ENS domain (in step 1) to `NETWORK_DAO_ENS_DOMAIN` in `.env` and replace `NETWORK` with the correct network name (e.g. for mainnet it is `MAINNET_DAO_ENS_DOMAIN`) -- [ ] Set the chosen Plugin ENS domain (in step 2) to `NETWORK_PLUGIN_ENS_DOMAIN` in `.env` and replace `NETWORK` with the correct network name (e.g. for mainnet it is `MAINNET_PLUGIN_ENS_DOMAIN`) -- [ ] Set the subdomain to be used of the managing DAO to `MANAGEMENT_DAO_SUBDOMAIN` in `.env`. If you want to use `management.dao.eth` put only `management` -- [ ] Set the multisig members of the managing DAO as a comma (`,`) separated list to `MANAGINGDAO_MULTISIG_APPROVERS` in `.env` -- [ ] Set the amount of minimum approvals the managing DAO needs to `MANAGINGDAO_MULTISIG_MINAPPROVALS` in `.env` -- [ ] If new plugin builds are released - - [ ] Double-check that the build- and release-metadata is published correctly by the deploy script and contracts +This checklist is seen as a guide to deploy the contracts to a new chain. + +## Pre deployment + +- [ ] Run `yarn` in the repository root to install the dependencies. +- [ ] Run `yarn build` in `packages/contracts` to make sure the contracts compile. +- [ ] Run `yarn test` in `packages/contracts` to make sure the contract tests succeed. + + - To run the tests, edit `packages/contracts/.env` to contain: + + ```env + HARDHAT_DAO_ENS_DOMAIN="testdao.eth" + HARDHAT_PLUGIN_ENS_DOMAIN="testpluginrepo.eth" + + MANAGEMENT_DAO_SUBDOMAIN="management" + ``` + +- [ ] Edit `packages/contracts/networks.json` and add your custom network to which you want to deploy to. + + If contract verification is not available for your chain: + + - Ensure that the `deploy` key for the new network looks exactly like:
+ `"deploy": ["./deploy/new"]` + + If contract verification is available: + + - Define the `deploy` key like:
+ `"deploy": ["./deploy/new", "./deploy/verification"]`. + - Define the `ETHERSCAN_KEY` variable for contract verification on the `.env` file. [Follow the Hardhat guide](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify) in this case. + +- [ ] Define the settings of the ENS domain used by OSx. + + - Define the following ENS names in the `packages/contracts/.env` file, by replacing `SEPOLIA` with the name of the network name you’re deploying to: + + ```env + SEPOLIA_DAO_ENS_DOMAIN="my-dao.eth" + SEPOLIA_PLUGIN_ENS_DOMAIN="my-dao.eth" + ``` + + - Ensure that domains end with a suffix like `.eth` + + - If the target chain does not have an official ENS registry: + - A new, unofficial ENS registry will be deployed, along with a resolver + - No ownership is needed, the Managing DAO will own them + - If the target chain does have an official ENS registry: + - Ensure that the wallet under `ETH_KEY` owns the domain + - If you created the domains via the ENS app, they will be owned by an ENS wrapper which would cause the script to fail + - [Open the ENS app](https://app.ens.domains/) and click `unwrap` for each of these domains. + - [Example](https://app.ens.domains/morpheusplugin3.eth?tab=more) + +- [ ] If desired, update `MANAGEMENT_DAO_SUBDOMAIN` on `packages/contracts/.env`. + - In case you want the Managing DAO to use a different subdomain than the default one (`management`): + ```env + MANAGEMENT_DAO_SUBDOMAIN="root" # would be root.my-dao.eth + ``` +- [ ] Define the the deployment wallet's private key on the `.env` file + ```env + ETH_KEY="your-private-key-here" # without the 0x prefix + ``` +- [ ] Verify that the deployment wallet has sufficient funds to complete a protocol deployment. ## Deployment -To deploy run `yarn deploy --network NETWORK` in `packages/contracts` and replace `NETWORK` with the correct network name (e.g. for mainnet it is `yarn deploy --network mainnet`) - -## After-Deployment - -### Configuration updates - -- [ ] Take the addresses from this file `packages/contracts/deployed_contracts.json` -- [ ] Update `active_contracts.json` with the new deployed addresses -- [ ] Update `packages/contracts/Releases.md` with the new deployed addresses -- [ ] Add the managing DAOs' multisig address to `packages/contracts/.env.example` in the format `{NETWORK}_MANAGINGDAO_MULTISIG` - -### Verification - -- [ ] Take the addresses from this file `packages/contracts/deployed_contracts.json` -- [ ] Wait for the deployment script finishing verification -- [ ] Go to the blockchain explorer and verify that each address is verified - - [ ] If it is not try to verfiy it with `npx hardhat verify --network NETWORK ADDRESS CONTRUCTOR-ARGS`. More infos on how to use this command can be found here: [https://hardhat.org/hardhat-runner/docs/guides/verifying](https://hardhat.org/hardhat-runner/docs/guides/verifying) - - [ ] If it is a proxy try to activate the blockchain explorer's proxy feature - - [ ] If the proxies are not verified with the `Similar Match Source Code` feature - - [ ] Verify one of the proxies - - [ ] Check if the other proxies are now verified with `Similar Match Source Code` - - [ ] If it is a `PluginSetup`, check that the implementation is verified. - -### Configurations - -- [ ] Check that all managing DAO signers are members of the managing DAO multisig and no one else. -- [ ] Check if the managing DAO is set in the `DAO_ENSSubdomainRegistrar` -- [ ] Check if the managing DAO is set in the `Plugin_ENSSubdomainRegistrar` -- [ ] Check if the managing DAO is set in the `DAORegistry` -- [ ] Check if the `DAO_ENSSubdomainRegistrar` is set in the `DAORegistry` -- [ ] Check if the managing DAO set in the `PluginRepoRegistry` -- [ ] Check if the `Plugin_ENSSubdomainRegistrar` is set in the `PluginRepoRegistry` -- [ ] Check if the `PluginRepoRegistry` is set in the `PluginRepoFactory` -- [ ] Check if the `PluginRepoRegistry` is set in the `PluginSetupProcessor` -- [ ] Check if the `DAORegistry` is set in the `DAOFactory` -- [ ] Check if the `PluginSetupProcessor` is set in the `DAOFactory` -- [ ] Check that the versions (and eventual `PlaceholderSetup` builds) are published correctly in the `token-voting-repo`, `address-list-voting-repo`, `multisig-repo`, and `admin-repo` and are synchronized across all supported networks. - -### Permissions - -- [ ] Check that the deployer has not the ROOT permission on the managing DAO -- [ ] Check if `DAO_ENSSubdomainRegistrar` is approved for all for the DAO' ENS domain. Call `isApprovedForAll` on the ENS registry with the managing DAO as the owner and the `DAO_ENSSubdomainRegistrar` as the operator. -- [ ] Check if `Plugin_ENSSubdomainRegistrar` is approved for all for the plugin' ENS domain. Call `isApprovedForAll` on the ENS registry with the managing DAO as the owner and the `Plugin_ENSSubdomainRegistrar` as the operator. -- [ ] Check if the `DAORegistry` has `REGISTER_ENS_SUBDOMAIN_PERMISSION` on `DAO_ENSSubdomainRegistrar` -- [ ] Check if the `PluginRepoRegistry` has `REGISTER_ENS_SUBDOMAIN_PERMISSION` on `Plugin_ENSSubdomainRegistrar` -- [ ] Check if the `DAOFactory` has `REGISTER_DAO_PERMISSION` on `DAORegistry` -- [ ] Check if the `PluginRepoFactory` has `REGISTER_PLUGIN_REPO_PERMISSION` on `PluginRepoRegistry` - -### Packages - -- [ ] Publish a new version of `@aragon/osx-artifacts` (`./packages/contracts`) to NPM -- [ ] Publish a new version of `@aragon/osx` (`./packages/contracts/src`) to NPM -- [ ] Publish a new version of `@aragon/osx-ethers` (`./packages/contracts-ethers`) to NPM -- [ ] Update the changelog with the new version - -### Subgraph - -- [ ] Update `packages/subgraph/manifest/data/NETWORK.json` where `NETWORK` is replaced with the deployed network with the new contract addresses. If the file doesn't exist create a new one. -- [ ] Update the version in `packages/subgraph/package.json` -- [ ] Update `packages/subgraph/.env` with the correct values - - [ ] set `NETWORK_NAME` to the deployed network - - [ ] set `SUBGRAPH_NAME` to `osx` - - [ ] set `GRAPH_KEY` with the value obtained from the [Satsuma Dashboard](https://app.satsuma.xyz/dashboard) - - [ ] set the `SUBGRAPH_VERSION` to the same value as in `packages/subgraph/package.json` -- [ ] Run `yarn manifest` in `packages/subgraph` to generate the manifest -- [ ] Run `yarn build` in `packages/subgraph` to build the subgraph -- [ ] Run `yarn test` in `packages/subgraph` to test the subgraph -- [ ] Run `yarn deploy` in `packages/subgraph` to deploy the subgraph -- [ ] Test the new deployed subgraph with the frontend team -- [ ] Promote the new subgraph to live in the [Satsuma Dashboard](https://app.satsuma.xyz/dashboard) - -## Appendix - -- Changing the owner of the chosen ENS domains will also revoke the permissions of the `DAO_ENSSubdomainRegistrar` and `Plugin_ENSSubdomainRegistrar`. Therefore if the ownership gets transfered, restore the approval for these 2 contracts. +When the settings above are correctly set: + +```sh +cd packages/contracts # if needed +yarn deploy --network # Replace with mainnet, polygon, sepolia, etc +``` + +## Post deployment + +- After the script has exited, the deployment wallet will be the only one with `ROOT_PERMISSION` on your Managing DAO. + - This allows the deployent wallet to manually install plugins on the Managing DAO. + - After the required plugins are installed, `ROOT_PERMISSION` has to be revoked on the deployment wallet. +- Should the script encounter any issues, the deployment should be re-run. + - The script will detect and re-use any previously deployed contracts. +- After the process completes, check out the `packages/contracts/deployed_contracts.json` file to see the deployed contract addresses. + +## Other + +### Rerunning the deployment script + +If you need to restart the redeployment process and want Hardhat to not reuse the existing contracts: + +```sh +rm -R deployments/ # replace with the actual name +``` + +### Running the deployment script on a testnet + +If you want to simulate an L3 deployment within a testnet (sepolia) which has official ENS support, you may want to force the deployment of a new ENS Registry. + +Edit the `packages/contracts/deploy/helpers.ts` file and comment out the relevant line from `ENS_ADDRESSES` and `ENS_PUBLIC_RESOLVERS`. diff --git a/packages/contracts/.env.example b/packages/contracts/.env.example index 3e1a479e6..d6b485c3a 100644 --- a/packages/contracts/.env.example +++ b/packages/contracts/.env.example @@ -28,22 +28,22 @@ ARBITRUMSEPOLIA_PLUGIN_ENS_DOMAIN= LOCALHOST_PLUGIN_ENS_DOMAIN= HARDHAT_PLUGIN_ENS_DOMAIN= -MANAGINGDAO_SUBDOMAIN= -MANAGINGDAO_MULTISIG_APPROVERS= -MANAGINGDAO_MULTISIG_MINAPPROVALS= -MANAGINGDAO_MULTISIG_LISTEDONLY= +MANAGEMENT_DAO_SUBDOMAIN= +MANAGEMENT_DAO_MULTISIG_APPROVERS= +MANAGEMENT_DAO_MULTISIG_MINAPPROVALS= +MANAGEMENT_DAO_MULTISIG_LISTEDONLY= -MAINNET_MANAGINGDAO_MULTISIG=0x0673c13d48023efa609c20e5e351763b99dd67de -GOERLI_MANAGINGDAO_MULTISIG=0x3263de63e70157c4b607982721026ffaa20e596c -SEPOLIA_MANAGINGDAO_MULTISIG=0xfcEAd61339e3e73090B587968FcE8b090e0600EF -POLYGON_MANAGINGDAO_MULTISIG=0x5db93850d843af581d8b87c350aa849a13a88e40 -MUMBAI_MANAGINGDAO_MULTISIG=0x944b067ccdbded94e64826747a5d72d4adcdf50a -BASESEPOLIA_MANAGINGDAO_MULTISIG=0xBFa3Ea5Bf7C6491b7f24f2a3658fF1d9eAE11c01 -BASEMAINNET_MANAGINGDAO_MULTISIG=0x549B739731dFDfe256f9A3014b30035C05b6D1a6 -ARBITRUM_MANAGINGDAO_MULTISIG=0x02bBc496BEBC9a06C239670Cea663C43ceAd899F -ARBITRUMSEPOLIA_MANAGINGDAO_MULTISIG=0xfcEAd61339e3e73090B587968FcE8b090e0600EF +MAINNET_MANAGEMENT_DAO_MULTISIG=0x0673c13d48023efa609c20e5e351763b99dd67de +GOERLI_MANAGEMENT_DAO_MULTISIG=0x3263de63e70157c4b607982721026ffaa20e596c +SEPOLIA_MANAGEMENT_DAO_MULTISIG=0xfcEAd61339e3e73090B587968FcE8b090e0600EF +POLYGON_MANAGEMENT_DAO_MULTISIG=0x5db93850d843af581d8b87c350aa849a13a88e40 +MUMBAI_MANAGEMENT_DAO_MULTISIG=0x944b067ccdbded94e64826747a5d72d4adcdf50a +BASESEPOLIA_MANAGEMENT_DAO_MULTISIG=0xBFa3Ea5Bf7C6491b7f24f2a3658fF1d9eAE11c01 +BASEMAINNET_MANAGEMENT_DAO_MULTISIG=0x549B739731dFDfe256f9A3014b30035C05b6D1a6 +ARBITRUM_MANAGEMENT_DAO_MULTISIG=0x02bBc496BEBC9a06C239670Cea663C43ceAd899F +ARBITRUMSEPOLIA_MANAGEMENT_DAO_MULTISIG=0xfcEAd61339e3e73090B587968FcE8b090e0600EF -HARDHAT_MANAGINGDAO_MULTISIG=0xe3ADd897e69010709498738e5116C06B4D81e672 # Changes with each new version +HARDHAT_MANAGEMENT_DAO_MULTISIG=0xe3ADd897e69010709498738e5116C06B4D81e672 # Changes with each new version # not using this variable will disable the feature. Anything else will enable it TEST_UPDATE_DEPLOY_SCRIPT= diff --git a/packages/contracts/.gitignore b/packages/contracts/.gitignore index fd38bc2ed..2b6e48677 100644 --- a/packages/contracts/.gitignore +++ b/packages/contracts/.gitignore @@ -10,3 +10,4 @@ docs/osx/03-reference-guide #Hardhat files cache artifacts +.upgradable diff --git a/packages/contracts/README-zkSync.md b/packages/contracts/README-zkSync.md new file mode 100644 index 000000000..a18006412 --- /dev/null +++ b/packages/contracts/README-zkSync.md @@ -0,0 +1,82 @@ +# ZkSync + +ZkSync uses a zkEVM and so has different requirements. + +1. Ensure contracts are compiled with the zkSolc compiler + +- In your `hardhat.config.ts`, ensure the zkSync packages are uncommented, and the non-zkSync contracts that cause conflicts are commented out. + +```bash +# Build your contracts with zkSolc +yarn build --network zkLocalTestnet + +# build with solc to ensure artifacts are present +yarn build +``` + +2. Start a zkNode + +```bash +# Start a zkSync node in a second terminal +yarn node-zkSync +``` + +3. Execute tests + +```bash +yard test --network zkLocalTestnet +``` + +You may need to configure tests to use custom matchers and wrappers. These are handled by wrapper and matcher files + +TODO: add more details on how to configure tests for zkSync + +1. Check that I correctly did skip tests as your suggestion/help. + +- I've reviewed and made a few small changes but otherwise they look good. +- The tests log immediately, so have changed the language a bit + +2. In tokenVotingSetupZkSync contract, you will notice what I have done and why I got setUpgradePermissions and so on. If we can find a way to write this contract simpler, would be good. + +- It looks sensible. I think we should do a thorough review in the morning when we both have fresh eyes. + +- I think we need to find a way to revoke the UPGRADE permission inside the uninstallation script + +- I removed the permissionIndex from the test. This should always resolve to the final permission in the array so we can just use the permissions array length: + - If we have a governance ERC20, then we lengthen the array to 4 + - We always add +1 to the permissions array if the upgrade permission is needed + +3. There are two tests that fail in dao.ts due to insufficient gas on zksync. Either skip it if you feel confident or on the weekends, I will also learn what the fuck they are. + +- Yep we need to look at these gas tests. On Monday I wasted ages on these and I'm honestly no closer to understanding wtf is going on. That being said I have said several times I think the gas logic of ZkSync doesn't make this a vulnerability. + +4. Take a look at GovernanceERC20Upgradeable and GovernanceWrappedERC20Upgradeable. What struck me is that the contracts they inherit from(i.e GovernanceERC20 and GovernanceWrappedERC20), they don’t have gaps which means that if we in the future decide to add new variable in GovernanceERC20Upgradeable and then in GovernanceERC20, we will mess up the storage. But let’s go with how it is. I really don’t care since this case won’t happen, but I need you to exactly understand what I mean so you’re aware of that too. + +- Good spot. So just reiterating: + - GovE20Up <- GovE20 (wrapped has similar chain) + - We can add new storage vars to the upgradeable version but not to the GovE20, because we have no gap, so it will start writing to storage slots inside GovERC20Up +- Would a workaround not be to first declare a `__gap` of reserved storage slots on GovERC20upgradeable THEN we add new storage variables (if needed) +- We would need to be careful this doesn't linearize unexpectedly and write to UUPSUpgradeable Storage. +- Why do you say "this case won't happen"? + +5. In hardhat-deploy script, we shouldn’t deploy Admin plugin if network is zksync. + +- I added a skip function to the admin deploy steps, you can take a look. + +6. Play with running tests on zksync and hardhat to see how it looks and if I missed anything or not. We can deal with deployment in the next days. Maybe you come across that skipped message sometimes prints ‘f’. You can change it as you see fit. + +- I removed the `f` tests +- I ran into some issues with matchers not firing, I brought these across into our testing lib +- Ran the whole test suite on 1.4.1, lgtm other than existing comments + +7. On hardhat-deploy script, we should deploy TokenVotingSetupZksync and also Governance contracts as upgradeable versions. + +8. Make psp upgradeable(create PSPZKSYNC), and deploy it on zksync network in hardhat-deploy script. Add some tests to test whether it can be upgraded. +9. Maybe we should write tests to check whether my GovernanceERC20Upgradeable and GovernanceWrappedERC20Upgradeable can be upgraded. +10. I skipped TokenFactory tests completely on all networks as we’re not using it at all(do we use it ?) + +11. To run tests on zksync, in hardhat.config.ts, you need to comment some stuff and to run tests on hardhat, you need to uncomment and comment other stuff. I asked about this and hardhat team said to use multiple configuration files. Just know that this is required to comment/uncomment. + +- I made a quick attempt to add `hardhat.config-zk.ts` +- I then used this in `yarn hardhat --config hardhat.config-zk.ts test --network zkLocalTestnet` but I got some weird errors related to the wrapper not being instantiated +- We can come back to this, but I don't think it's essential just yet diff --git a/packages/contracts/deploy/helpers.ts b/packages/contracts/deploy/helpers.ts index b6e3948ef..6f9866047 100644 --- a/packages/contracts/deploy/helpers.ts +++ b/packages/contracts/deploy/helpers.ts @@ -16,6 +16,7 @@ import { } from '../typechain'; import {VersionCreatedEvent} from '../typechain/PluginRepo'; import {PluginRepoRegisteredEvent} from '../typechain/PluginRepoRegistry'; +import {ZK_SYNC_NETWORKS} from '../utils/zkSync'; // TODO: Add support for L2 such as Arbitrum. (https://discuss.ens.domains/t/register-using-layer-2/688) // Make sure you own the ENS set in the {{NETWORK}}_ENS_DOMAIN variable in .env @@ -31,24 +32,6 @@ export const ENS_PUBLIC_RESOLVERS: {[key: string]: string} = { sepolia: '0x8FADE66B79cC9f707aB26799354482EB93a5B7dD', }; -export const MANAGING_DAO_METADATA = { - name: 'Aragon Managing DAO', - description: - 'Aragon OSx includes a group of global smart contracts that allow for a DAO ecosystem to be built on top. These contracts will require future improvements and general maintenance. The Managing DAO is intended to perform such maintenance tasks and holds the permissions to deliver any new capabilities that are added in the future.', - avatar: - 'https://ipfs.eth.aragon.network/ipfs/QmVyy3ci7F2zHG6JUJ1XbcwLKuxWrQ6hqNvSnjmDmdYJzP/', - links: [ - { - name: 'Web site', - url: 'https://www.aragon.org', - }, - { - name: 'Developer Portal', - url: 'https://devs.aragon.org/', - }, - ], -}; - export const DAO_PERMISSIONS = [ 'ROOT_PERMISSION', 'UPGRADE_DAO_PERMISSION', @@ -69,7 +52,11 @@ export async function uploadToIPFS( }, }); - if (networkName === 'hardhat' || networkName === 'localhost') { + if ( + networkName === 'hardhat' || + networkName === 'localhost' || + networkName === 'zkLocalTestnet' + ) { // return a dummy path return 'QmNnobxuyCjtYgsStCPhXKEiQR5cjsc3GtG9ZMTKFTTEFJ'; } @@ -189,21 +176,9 @@ export async function createPluginRepo( pluginName: string ): Promise { const {network} = hre; - const signers = await ethers.getSigners(); + const {deployer} = await hre.getNamedAccounts(); - const pluginDomain = - process.env[`${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`] || ''; - if ( - await isENSDomainRegistered( - `${pluginName}.${pluginDomain}`, - await getENSAddress(hre), - signers[0] - ) - ) { - // not beeing able to register the plugin repo means that something is not right with the framework deployment used. - // Either a frontrun happened or something else. Thus we abort here - throw new Error(`${pluginName} is already present! Aborting...`); - } + const signers = await ethers.getSigners(); const pluginRepoFactoryAddress = await getContractAddress( 'PluginRepoFactory', @@ -215,12 +190,58 @@ export async function createPluginRepo( pluginRepoFactoryAddress ); - const {deployer} = await hre.getNamedAccounts(); + const pluginDomain = + process.env[`${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`]; + + const node = ethers.utils.namehash(`${pluginName}.${pluginDomain}`); + + const pluginSubdomainRegistrar = await getContractAddress( + 'Plugin_ENSSubdomainRegistrar', + hre + ); + + const ensRegistryContract = ENSRegistry__factory.connect( + await getENSAddress(hre), + signers[0] + ); + + const owner = await ensRegistryContract.owner(node); + + // ENS subdomain has already been registered by pluginSubdomainRegistrar. + // Running the `createPluginRepo` will fail unless skipped. + // Since `aragonPluginRepos` is filled in run-time, we still need to refill it. + if (owner === pluginSubdomainRegistrar) { + const pluginRepoRegistryFactory = new PluginRepoRegistry__factory( + signers[0] + ); + const pluginRepoRegistry = pluginRepoRegistryFactory.attach( + await getContractAddress('PluginRepoRegistry', hre) + ); + let events = await pluginRepoRegistry.queryFilter( + pluginRepoRegistry.filters.PluginRepoRegistered(null, null) + ); + const found = events.filter(event => event?.args?.subdomain == pluginName); + if (found && found.length == 1) { + hre.aragonPluginRepos[pluginName] = found[0].args.pluginRepo; + return; + } + throw new Error( + `Critical: Either the event was not found or none of the plugin repo deployment corresponds to your domain.` + ); + } + + // The owner of the node is already set to someone who is not pluginSubdomainRegistrar. + if (owner !== ethers.constants.AddressZero) { + throw new Error( + `${pluginName}.${pluginDomain} is already owned by someone other than pluginSubdomainRegistrar` + ); + } const tx = await pluginRepoFactoryContract.createPluginRepo( pluginName, deployer ); + console.log( `Creating & registering repo for ${pluginName} with tx ${tx.hash}` ); @@ -244,6 +265,7 @@ export async function createVersion( pluginRepoContract: string, pluginSetupContract: string, releaseNumber: number, + buildNumber: number, releaseMetadata: string, buildMetadata: string ): Promise { @@ -252,6 +274,18 @@ export async function createVersion( const PluginRepo = new PluginRepo__factory(signers[0]); const pluginRepo = PluginRepo.attach(pluginRepoContract); + try { + // getVersion reverts if the version doesn't exist. + await pluginRepo['getVersion((uint8,uint16))']({ + release: releaseNumber, + build: buildNumber, + }); + console.log( + `Version already deployed. Skipping the 'createVersion' call on pluginRepo` + ); + return; + } catch (err) {} + const tx = await pluginRepo.createVersion( releaseNumber, pluginSetupContract, @@ -330,6 +364,7 @@ export async function populatePluginRepo( hre.aragonPluginRepos[pluginRepoName], placeholderSetup, releaseNumber, + i, emptyJsonObject, ethers.utils.hexlify( ethers.utils.toUtf8Bytes(`ipfs://${hre.placeholderBuildCIDPath}`) @@ -342,6 +377,7 @@ export async function populatePluginRepo( hre.aragonPluginRepos[pluginRepoName], latestVersion.pluginSetupContract, releaseNumber, + latestBuildNumber, latestVersion.releaseMetadata, latestVersion.buildMetadata ); @@ -605,7 +641,7 @@ export function getManagingDAOMultisigAddress( ): string { const {network} = hre; const address = - process.env[`${network.name.toUpperCase()}_MANAGINGDAO_MULTISIG`]; + process.env[`${network.name.toUpperCase()}_MANAGEMENT_DAO_MULTISIG`]; if (!address) { throw new Error( `Failed to find managing DAO multisig address in env variables for ${network.name}` @@ -614,5 +650,55 @@ export function getManagingDAOMultisigAddress( return address; } +export async function getPSPAddress(hre: HardhatRuntimeEnvironment) { + let name = 'PluginSetupProcessor' + if(ZK_SYNC_NETWORKS.includes(hre.network.name)) { + name = 'PluginSetupProcessorUpgradeable' + } + + const address = await getContractAddress( + name, + hre + ); + + return address; +} + +export async function getTokenVotingSetupAddress(hre: HardhatRuntimeEnvironment) { + let name = 'TokenVotingSetup' + if(ZK_SYNC_NETWORKS.includes(hre.network.name)) { + name = 'TokenVotingSetupZkSync' + } + + const address = await getContractAddress( + name, + hre + ); + + return address; +} + +export async function skipIfZkSync( + hre: HardhatRuntimeEnvironment, + stage: string +) { + if (ZK_SYNC_NETWORKS.includes(hre.network.name)) { + console.log(`Skipping deployment stage: ${stage} due to zkSync network`); + return true; + } + return false; +} + +export async function skipIfNotZkSync( + hre: HardhatRuntimeEnvironment, + stage: string +) { + if (!ZK_SYNC_NETWORKS.includes(hre.network.name)) { + console.log(`Skipping deployment stage: ${stage} due to zkSync network`); + return true; + } + return false; +} + // exports dummy function for hardhat-deploy. Otherwise we would have to move this file export default function () {} diff --git a/packages/contracts/deploy/management-dao-metadata.json b/packages/contracts/deploy/management-dao-metadata.json new file mode 100644 index 000000000..4faf4af57 --- /dev/null +++ b/packages/contracts/deploy/management-dao-metadata.json @@ -0,0 +1,15 @@ +{ + "name": "Aragon Management DAO", + "description": "Aragon OSx includes a group of global smart contracts that allow for a DAO ecosystem to be built on top. These contracts will require future improvements and general maintenance. The Management DAO is intended to perform such maintenance tasks and holds the permissions to deliver any new capabilities that are added in the future.", + "avatar": "https://ipfs.eth.aragon.network/ipfs/QmVyy3ci7F2zHG6JUJ1XbcwLKuxWrQ6hqNvSnjmDmdYJzP/", + "links": [ + { + "name": "Web site", + "url": "https://www.aragon.org" + }, + { + "name": "Developer Portal", + "url": "https://devs.aragon.org/" + } + ] +} diff --git a/packages/contracts/deploy/new/00_managing-dao/00_managing-dao.ts b/packages/contracts/deploy/new/00_managing-dao/00_managing-dao.ts index 20e450a81..eecc22fbc 100644 --- a/packages/contracts/deploy/new/00_managing-dao/00_managing-dao.ts +++ b/packages/contracts/deploy/new/00_managing-dao/00_managing-dao.ts @@ -1,7 +1,7 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import daoArtifactJson from '../../../artifacts/src/core/dao/DAO.sol/DAO.json'; import {ArtifactData, DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import daoArtifactJson from '../../../artifacts/src/core/dao/DAO.sol/DAO.json'; /** NOTE: * Create a (Managing DAO) with no Plugin, to be the owner DAO for the framework, temporarily. */ diff --git a/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts b/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts index 7099a40ff..3760460e3 100644 --- a/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts +++ b/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts @@ -1,13 +1,45 @@ -import {DeployFunction} from 'hardhat-deploy/types'; -import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {ENSRegistry, ENSRegistry__factory} from '../../../typechain'; import { + ENS_ADDRESSES, getContractAddress, getENSAddress, getPublicResolverAddress, registerSubnodeRecord, transferSubnodeChain, } from '../../helpers'; -import {ENSRegistry__factory} from '../../../typechain'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +async function registerAndTransferDomain( + ensRegistryContract: ENSRegistry, + managingDAOAddress: string, + domain: string, + node: string, + deployer: SignerWithAddress, + hre: HardhatRuntimeEnvironment, + ethers: any +) { + let owner = await ensRegistryContract.owner(node); + + if (owner !== managingDAOAddress && owner !== deployer.address) { + throw new Error( + `${domain} is not owned either by deployer: ${deployer.address} or management dao: ${managingDAOAddress}. + Check if the domain is owned by ENS wrapper and if so, unwrap it from the ENS app.` + ); + } + + // It could be the case that domain is already owned by the management DAO which could happen + // if the script succeeded and is re-run again. So avoid transfer which would fail otherwise. + if (owner === deployer.address) { + await transferSubnodeChain( + domain, + managingDAOAddress, + deployer.address, + await getENSAddress(hre) + ); + } +} const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {ethers, network} = hre; @@ -19,66 +51,35 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const pluginDomain = process.env[`${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`] || ''; - if (!daoDomain || !pluginDomain) { - throw new Error('DAO or Plugin ENS domains have not been set in .env'); - } - const ensRegistryAddress = await getENSAddress(hre); const ensRegistryContract = ENSRegistry__factory.connect( ensRegistryAddress, deployer ); - // Check if domains are owned by the managingDAO + const managingDAOAddress = await getContractAddress('DAO', hre); + const daoNode = ethers.utils.namehash(daoDomain); const pluginNode = ethers.utils.namehash(pluginDomain); - let daoDomainOwnerAddress = await ensRegistryContract.owner(daoNode); - - // node hasn't been registered yet - if (daoDomainOwnerAddress === ethers.constants.AddressZero) { - daoDomainOwnerAddress = await registerSubnodeRecord( - daoDomain, - deployer, - await getENSAddress(hre), - await getPublicResolverAddress(hre) - ); - } - if (daoDomainOwnerAddress != deployer.address) { - throw new Error( - `${daoDomain} is not owned by deployer: ${deployer.address}.` - ); - } - - let pluginDomainOwnerAddress = await ensRegistryContract.owner(pluginNode); - // node hasn't been registered yet - if (pluginDomainOwnerAddress === ethers.constants.AddressZero) { - pluginDomainOwnerAddress = await registerSubnodeRecord( - pluginDomain, - deployer, - await getENSAddress(hre), - await getPublicResolverAddress(hre) - ); - } - if (pluginDomainOwnerAddress != deployer.address) { - throw new Error( - `${pluginDomain} is not owned by deployer: ${deployer.address}.` - ); - } - - // Registration is now complete. Lets move the ownership of all domains to the managing DAO - const managingDAOAddress = await getContractAddress('DAO', hre); - await transferSubnodeChain( - daoDomain, + await registerAndTransferDomain( + ensRegistryContract, managingDAOAddress, - deployer.address, - await getENSAddress(hre) + daoDomain, + daoNode, + deployer, + hre, + ethers ); - await transferSubnodeChain( - pluginDomain, + + await registerAndTransferDomain( + ensRegistryContract, managingDAOAddress, - deployer.address, - await getENSAddress(hre) + pluginDomain, + pluginNode, + deployer, + hre, + ethers ); }; export default func; diff --git a/packages/contracts/deploy/new/10_framework/40_plugin_setup_processor.ts b/packages/contracts/deploy/new/10_framework/40_plugin_setup_processor.ts index 3fda88935..d594b1fae 100644 --- a/packages/contracts/deploy/new/10_framework/40_plugin_setup_processor.ts +++ b/packages/contracts/deploy/new/10_framework/40_plugin_setup_processor.ts @@ -1,6 +1,6 @@ import {HardhatRuntimeEnvironment} from 'hardhat/types'; import {DeployFunction} from 'hardhat-deploy/types'; -import {getContractAddress} from '../../helpers'; +import {getContractAddress, skipIfZkSync} from '../../helpers'; import pluginSetupProcessorFactoryArtifact from '../../../artifacts/src/framework/plugin/setup/PluginSetupProcessor.sol/PluginSetupProcessor.json'; @@ -24,3 +24,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }; export default func; func.tags = ['New', 'PluginSetupProcessor']; +func.skip = async hre => await skipIfZkSync(hre, 'PluginSetupProcessor'); diff --git a/packages/contracts/deploy/new/10_framework/41_plugin-setup-processor_conclude.ts b/packages/contracts/deploy/new/10_framework/41_plugin-setup-processor_conclude.ts index e4839811d..327a4a90c 100644 --- a/packages/contracts/deploy/new/10_framework/41_plugin-setup-processor_conclude.ts +++ b/packages/contracts/deploy/new/10_framework/41_plugin-setup-processor_conclude.ts @@ -1,5 +1,6 @@ import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {skipIfZkSync} from '../../helpers'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`Concluding Plugin Setup Processor deployment.\n`); @@ -12,3 +13,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func; func.tags = ['New', 'PluginSetupProcessor', 'Verify']; +func.skip = async hre => await skipIfZkSync(hre, 'PluginSetupProcessor'); diff --git a/packages/contracts/deploy/new/10_framework/42_plugin_setup_processor_upgradeable.ts b/packages/contracts/deploy/new/10_framework/42_plugin_setup_processor_upgradeable.ts new file mode 100644 index 000000000..b197f7d07 --- /dev/null +++ b/packages/contracts/deploy/new/10_framework/42_plugin_setup_processor_upgradeable.ts @@ -0,0 +1,43 @@ +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {getContractAddress, skipIfNotZkSync} from '../../helpers'; + +import pluginSetupProcessorUpgradeableFactoryArtifact from '../../../artifacts/src/zksync/PluginSetupProcessorUpgradeable.sol/PluginSetupProcessorUpgradeable.json'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {deployments, ethers} = hre; + const {deploy} = deployments; + const [deployer] = await ethers.getSigners(); + + // Get `PluginRepoRegistry` address. + const pluginRepoRegistryAddress = await getContractAddress( + 'PluginRepoRegistry', + hre + ); + + // Get `Management DAO` address. + const managementDAOAddress = await getContractAddress('DAO', hre); + + await deploy('PluginSetupProcessorUpgradeable', { + contract: pluginSetupProcessorUpgradeableFactoryArtifact, + from: deployer.address, + args: [], + log: true, + proxy: { + owner: deployer.address, + proxyContract: 'ERC1967Proxy', + proxyArgs: ['{implementation}', '{data}'], + execute: { + init: { + methodName: 'initialize', + args: [managementDAOAddress, pluginRepoRegistryAddress], + }, + }, + }, + }); +}; + +export default func; +func.tags = ['New', 'PluginSetupProcessorUpgradeable']; +func.skip = async hre => + await skipIfNotZkSync(hre, 'PluginSetupProcessorUpgradeable'); diff --git a/packages/contracts/deploy/new/10_framework/43_plugin_setup_processor_upgradeable_conclude.ts b/packages/contracts/deploy/new/10_framework/43_plugin_setup_processor_upgradeable_conclude.ts new file mode 100644 index 000000000..b04ea505c --- /dev/null +++ b/packages/contracts/deploy/new/10_framework/43_plugin_setup_processor_upgradeable_conclude.ts @@ -0,0 +1,17 @@ +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {skipIfNotZkSync} from '../../helpers'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + console.log(`Concluding Plugin Setup Processor deployment.\n`); + + const {deployments} = hre; + hre.aragonToVerifyContracts.push( + await deployments.get('PluginSetupProcessorUpgradeable') + ); +}; + +export default func; +func.tags = ['New', 'PluginSetupProcessorUpgradeable', 'Verify']; +func.skip = async hre => + await skipIfNotZkSync(hre, 'PluginSetupProcessorUpgradeable-Verify'); diff --git a/packages/contracts/deploy/new/10_framework/50_dao-factory.ts b/packages/contracts/deploy/new/10_framework/50_dao-factory.ts index 43bbec0be..ed2fb2bd0 100644 --- a/packages/contracts/deploy/new/10_framework/50_dao-factory.ts +++ b/packages/contracts/deploy/new/10_framework/50_dao-factory.ts @@ -1,8 +1,9 @@ import {HardhatRuntimeEnvironment} from 'hardhat/types'; import {DeployFunction} from 'hardhat-deploy/types'; -import {getContractAddress} from '../../helpers'; +import {getContractAddress, getPSPAddress} from '../../helpers'; import daoFactoryArtifact from '../../../artifacts/src/framework/dao/DAOFactory.sol/DAOFactory.json'; +import { ZK_SYNC_NETWORKS } from '../../../utils/zkSync'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployments, ethers} = hre; @@ -12,11 +13,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Get `DAORegistry` address. const daoRegistryAddress = await getContractAddress('DAORegistry', hre); - // Get `PluginSetupProcessor` address. - const pluginSetupProcessorAddress = await getContractAddress( - 'PluginSetupProcessor', - hre - ); + let pluginSetupProcessorAddress = await getPSPAddress(hre) await deploy('DAOFactory', { contract: daoFactoryArtifact, diff --git a/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts b/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts index 73faa823a..5bac9c432 100644 --- a/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts +++ b/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts @@ -1,7 +1,7 @@ import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {checkSetManagingDao, getContractAddress} from '../../helpers'; +import {checkSetManagingDao, getContractAddress, getPSPAddress} from '../../helpers'; import { DAOFactory__factory, DAORegistry__factory, @@ -11,6 +11,7 @@ import { PluginRepoRegistry__factory, PluginSetupProcessor__factory, } from '../../../typechain'; +import { ZK_SYNC_NETWORKS } from '../../../utils/zkSync'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log('\nVerifying framework deployment.'); @@ -147,13 +148,11 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { `${PluginRepoFactoryAddress} has wrong PluginRepoRegistry set. Expected ${SetPluginRepoRegistryAddress} to be ${PluginRepoRegistryAddress}` ); } - } + } + + + let PluginSetupProcessorAddress = await getPSPAddress(hre) - // VERIFYING PSP - const PluginSetupProcessorAddress = await getContractAddress( - 'PluginSetupProcessor', - hre - ); const PluginSetupProcessor = PluginSetupProcessor__factory.connect( PluginSetupProcessorAddress, deployer diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/00_addresslist_voting_setup.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/00_addresslist_voting_setup.ts index 50e373994..288961d15 100644 --- a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/00_addresslist_voting_setup.ts +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/00_addresslist_voting_setup.ts @@ -1,7 +1,6 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - import addresslistVotingSetupArtifact from '../../../../artifacts/src/plugins/governance/majority-voting/addresslist/AddresslistVotingSetup.sol/AddresslistVotingSetup.json'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`\nDeploying plugins.`); diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/01_addresslist_voting_setup_conclude.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/01_addresslist_voting_setup_conclude.ts index f7f89b509..0fad6581c 100644 --- a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/01_addresslist_voting_setup_conclude.ts +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/01_addresslist_voting_setup_conclude.ts @@ -1,7 +1,7 @@ +import {AddresslistVotingSetup__factory} from '../../../../typechain'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import {setTimeout} from 'timers/promises'; -import {AddresslistVotingSetup__factory} from '../../../../typechain'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`Concluding addresslist voting setup deployment.\n`); diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/10_token_voting_setup.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/10_token_voting_setup.ts index 400f53fde..56e8ef156 100644 --- a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/10_token_voting_setup.ts +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/10_token_voting_setup.ts @@ -1,10 +1,10 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - +import tokenVotingSetupArtifact from '../../../../artifacts/src/plugins/governance/majority-voting/token/TokenVotingSetup.sol/TokenVotingSetup.json'; import governanceERC20Artifact from '../../../../artifacts/src/token/ERC20/governance/GovernanceERC20.sol/GovernanceERC20.json'; import governanceWrappedERC20Artifact from '../../../../artifacts/src/token/ERC20/governance/GovernanceWrappedERC20.sol/GovernanceWrappedERC20.json'; -import tokenVotingSetupArtifact from '../../../../artifacts/src/plugins/governance/majority-voting/token/TokenVotingSetup.sol/TokenVotingSetup.json'; import {MintSettings} from '../../../../test/token/erc20/governance-erc20'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {skipIfZkSync} from '../../../helpers'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployments, ethers} = hre; @@ -51,3 +51,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }; export default func; func.tags = ['New', 'TokenVotingSetup']; +func.skip = async hre => await skipIfZkSync(hre, 'TokenVotingSetup'); diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/11_token_voting_setup_conclude.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/11_token_voting_setup_conclude.ts index 4c9264b92..eab3fcd72 100644 --- a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/11_token_voting_setup_conclude.ts +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/11_token_voting_setup_conclude.ts @@ -1,7 +1,8 @@ -import {DeployFunction} from 'hardhat-deploy/types'; import {TokenVotingSetup__factory} from '../../../../typechain'; -import {setTimeout} from 'timers/promises'; +import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {setTimeout} from 'timers/promises'; +import {skipIfZkSync} from '../../../helpers'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`Concluding token voting setup deployment.\n`); @@ -44,3 +45,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func; func.tags = ['New', 'TokenVotingSetup', 'Verify']; +func.skip = async hre => await skipIfZkSync(hre, 'TokenVotingSetup-Verify'); diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/12_token_voting_setup_zksync.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/12_token_voting_setup_zksync.ts new file mode 100644 index 000000000..a82a36a05 --- /dev/null +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/12_token_voting_setup_zksync.ts @@ -0,0 +1,21 @@ +import tokenVotingSetupArtifact from '../../../../artifacts/src/zksync/TokenVotingSetupZkSync.sol/TokenVotingSetupZkSync.json'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {skipIfNotZkSync} from '../../../helpers'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {deployments, ethers} = hre; + const {deploy} = deployments; + const [deployer] = await ethers.getSigners(); + + // Deploy the TokenVotingSetup and provide the bases in the constructor + await deploy('TokenVotingSetupZkSync', { + contract: tokenVotingSetupArtifact, + from: deployer.address, + args: [], + log: true, + }); +}; +export default func; +func.tags = ['New', 'TokenVotingSetupZkSync']; +func.skip = async hre => await skipIfNotZkSync(hre, 'TokenVotingSetupZkSync'); diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/13_token_voting_setup_zksync_conclude.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/13_token_voting_setup_zksync_conclude.ts new file mode 100644 index 000000000..e00799de9 --- /dev/null +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/13_token_voting_setup_zksync_conclude.ts @@ -0,0 +1,34 @@ +import {TokenVotingSetup__factory} from '../../../../typechain'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {setTimeout} from 'timers/promises'; +import {skipIfNotZkSync} from '../../../helpers'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + console.log(`Concluding token voting setup deployment.\n`); + const [deployer] = await hre.ethers.getSigners(); + + const {deployments, network} = hre; + + const TokenVotingSetupDeployment = await deployments.get('TokenVotingSetupZkSync'); + const tokenVotingSetup = TokenVotingSetup__factory.connect( + TokenVotingSetupDeployment.address, + deployer + ); + + hre.aragonToVerifyContracts.push({ + contract: 'src/zksync/TokenVotingSetupZkSync.sol:TokenVotingSetupZkSync', + ...TokenVotingSetupDeployment, + }); + hre.aragonToVerifyContracts.push({ + contract: + 'src/plugins/governance/majority-voting/token/TokenVoting.sol:TokenVoting', + address: await tokenVotingSetup.implementation(), + args: [], + }); +}; + +export default func; +func.tags = ['New', 'TokenVotingSetupZkSync', 'Verify']; +func.skip = async hre => + await skipIfNotZkSync(hre, 'TokenVotingSetupZkSync-Verify'); diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/20_admin_setup.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/20_admin_setup.ts index 2ea04c3f4..df74e32d0 100644 --- a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/20_admin_setup.ts +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/20_admin_setup.ts @@ -1,7 +1,7 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - import adminSetupArtifact from '../../../../artifacts/src/plugins/governance/admin/AdminSetup.sol/AdminSetup.json'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {skipIfZkSync} from '../../../helpers'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployments, ethers} = hre; @@ -17,3 +17,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }; export default func; func.tags = ['New', 'AdminSetup']; +func.skip = async hre => await skipIfZkSync(hre, 'AdminSetup'); diff --git a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/21_admin_setup_conclude.ts b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/21_admin_setup_conclude.ts index e6daf6973..3724f947e 100644 --- a/packages/contracts/deploy/new/30_plugins/00_plugin-setups/21_admin_setup_conclude.ts +++ b/packages/contracts/deploy/new/30_plugins/00_plugin-setups/21_admin_setup_conclude.ts @@ -1,7 +1,8 @@ -import {DeployFunction} from 'hardhat-deploy/types'; import {AdminSetup__factory} from '../../../../typechain'; -import {setTimeout} from 'timers/promises'; +import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {setTimeout} from 'timers/promises'; +import {skipIfZkSync} from '../../../helpers'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`Concluding admin setup deployment.\n`); @@ -34,3 +35,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func; func.tags = ['New', 'AdminSetup', 'Verify']; +func.skip = async hre => await skipIfZkSync(hre, 'AdminSetupConclude'); diff --git a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/00_create_address_list_voting_repo.ts b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/00_create_address_list_voting_repo.ts index 982a79a1f..493efa47a 100644 --- a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/00_create_address_list_voting_repo.ts +++ b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/00_create_address_list_voting_repo.ts @@ -1,9 +1,5 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - -import addresslistReleaseMetadata from '../../../../src/plugins/governance/majority-voting/addresslist/release-metadata.json'; import addresslistBuildMetadata from '../../../../src/plugins/governance/majority-voting/addresslist/build-metadata.json'; - +import addresslistReleaseMetadata from '../../../../src/plugins/governance/majority-voting/addresslist/release-metadata.json'; import { createPluginRepo, populatePluginRepo, @@ -11,6 +7,8 @@ import { uploadToIPFS, } from '../../../helpers'; import {ethers} from 'ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`\nCreating address-list-voting repo.`); diff --git a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/01_create_address_list_voting_repo_conclude.ts b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/01_create_address_list_voting_repo_conclude.ts index ba6f0d2ff..df2064add 100644 --- a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/01_create_address_list_voting_repo_conclude.ts +++ b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/01_create_address_list_voting_repo_conclude.ts @@ -1,9 +1,9 @@ -import {DeployFunction} from 'hardhat-deploy/types'; -import {HardhatRuntimeEnvironment} from 'hardhat/types'; import { PluginRepoFactory__factory, PluginRepo__factory, } from '../../../../typechain'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`Concluding AddresslistVotingSetup deployment.\n`); diff --git a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/10_create_token_voting_repo.ts b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/10_create_token_voting_repo.ts index 9b7dd2c59..3a695a0d3 100644 --- a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/10_create_token_voting_repo.ts +++ b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/10_create_token_voting_repo.ts @@ -1,16 +1,15 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - -import tokenVotingReleaseMetadata from '../../../../src/plugins/governance/majority-voting/token/release-metadata.json'; import tokenVotingBuildMetadata from '../../../../src/plugins/governance/majority-voting/token/build-metadata.json'; - +import tokenVotingReleaseMetadata from '../../../../src/plugins/governance/majority-voting/token/release-metadata.json'; import { createPluginRepo, populatePluginRepo, getContractAddress, uploadToIPFS, + getTokenVotingSetupAddress, } from '../../../helpers'; import {ethers} from 'ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`\nCreating token-voting repo.`); @@ -30,10 +29,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { network.name ); - const tokenVotingSetupContract = await getContractAddress( - 'TokenVotingSetup', - hre - ); + const tokenVotingSetupContract = await getTokenVotingSetupAddress(hre); await createPluginRepo(hre, 'token-voting'); await populatePluginRepo(hre, 'token-voting', [ diff --git a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/11_create_token_voting_repo_conclude.ts b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/11_create_token_voting_repo_conclude.ts index 5b3c3dfe1..3305674c0 100644 --- a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/11_create_token_voting_repo_conclude.ts +++ b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/11_create_token_voting_repo_conclude.ts @@ -1,9 +1,9 @@ -import {DeployFunction} from 'hardhat-deploy/types'; -import {HardhatRuntimeEnvironment} from 'hardhat/types'; import { PluginRepoFactory__factory, PluginRepo__factory, } from '../../../../typechain'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`Concluding TokenVotingSetup deployment.\n`); diff --git a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/20_create_admin_repo.ts b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/20_create_admin_repo.ts index ea78d0419..c8ac1f36f 100644 --- a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/20_create_admin_repo.ts +++ b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/20_create_admin_repo.ts @@ -1,16 +1,15 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - -import adminReleaseMetadata from '../../../../src/plugins/governance/admin/release-metadata.json'; import adminBuildMetadata from '../../../../src/plugins/governance/admin/build-metadata.json'; - +import adminReleaseMetadata from '../../../../src/plugins/governance/admin/release-metadata.json'; import { createPluginRepo, populatePluginRepo, getContractAddress, uploadToIPFS, + skipIfZkSync, } from '../../../helpers'; import {ethers} from 'ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`\nCreating admin repo.`); @@ -49,3 +48,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func; func.tags = ['New', 'CreateAdminRepo']; +func.skip = async hre => await skipIfZkSync(hre, 'CreateAdminRepo'); diff --git a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/21_create_admin_repo_conclude.ts b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/21_create_admin_repo_conclude.ts index ad40b6c66..59fe16fec 100644 --- a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/21_create_admin_repo_conclude.ts +++ b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/21_create_admin_repo_conclude.ts @@ -1,9 +1,10 @@ -import {DeployFunction} from 'hardhat-deploy/types'; -import {HardhatRuntimeEnvironment} from 'hardhat/types'; import { PluginRepoFactory__factory, PluginRepo__factory, } from '../../../../typechain'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {skipIfZkSync} from '../../../helpers'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`Concluding TokenVotingSetup deployment.\n`); @@ -34,3 +35,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func; func.tags = ['New', 'CreateAdminRepo', 'Verify']; +func.skip = async hre => await skipIfZkSync(hre, 'CreateAdminRepoConclude'); diff --git a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/31_create_multisig_repo_conclude.ts b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/31_create_multisig_repo_conclude.ts index 3d3355ee8..f5e97d659 100644 --- a/packages/contracts/deploy/new/30_plugins/10_plugin-repos/31_create_multisig_repo_conclude.ts +++ b/packages/contracts/deploy/new/30_plugins/10_plugin-repos/31_create_multisig_repo_conclude.ts @@ -1,12 +1,12 @@ -import {DeployFunction} from 'hardhat-deploy/types'; -import {HardhatRuntimeEnvironment} from 'hardhat/types'; import { PluginRepoFactory__factory, PluginRepo__factory, } from '../../../../typechain'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - console.log(`Concluding AddresslistVotingSetup deployment.\n`); + console.log(`Concluding Multisig deployment.\n`); const [deployer] = await hre.ethers.getSigners(); const {deployments} = hre; diff --git a/packages/contracts/deploy/new/40_finalize-managing-dao/00_grant-permissions.ts b/packages/contracts/deploy/new/40_finalize-managing-dao/00_grant-permissions.ts index 020ba3c8d..3c5fe4f05 100644 --- a/packages/contracts/deploy/new/40_finalize-managing-dao/00_grant-permissions.ts +++ b/packages/contracts/deploy/new/40_finalize-managing-dao/00_grant-permissions.ts @@ -1,9 +1,8 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - -import {Operation} from '../../../utils/types'; -import {getContractAddress, managePermissions, Permission} from '../../helpers'; import {DAO__factory, PluginRepo__factory} from '../../../typechain'; +import {Operation} from '../../../utils/types'; +import {getContractAddress, getPSPAddress, managePermissions, Permission} from '../../helpers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log(`\nFinalizing ManagingDao.`); @@ -15,7 +14,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const daoRegistryAddress = await getContractAddress('DAORegistry', hre); // Get `PluginSetupProcessor` address. - const pspAddress = await getContractAddress('PluginSetupProcessor', hre); + const pspAddress = await getPSPAddress(hre); // Get `managingDAO` address. const managingDAOAddress = await getContractAddress('DAO', hre); @@ -62,6 +61,10 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Grant `ROOT_PERMISSION`, `MAINTAINER_PERMISSION` and `UPGRADE_REPO_PERMISSION` to `managingDao` on the permission manager of each PluginRepo. for (const repoName in hre.aragonPluginRepos) { const repoAddress = hre.aragonPluginRepos[repoName]; + + // if repoAddress empty, the deployment must have been marked as skipped. + if (repoAddress === '') continue; + const grantPluginRepoPermissions: Permission[] = []; grantPluginRepoPermissions.push({ operation: Operation.Grant, diff --git a/packages/contracts/deploy/new/40_finalize-managing-dao/20_register-managing-dao-on-dao-registry.ts b/packages/contracts/deploy/new/40_finalize-managing-dao/20_register-managing-dao-on-dao-registry.ts index a21583806..e2b40bc1d 100644 --- a/packages/contracts/deploy/new/40_finalize-managing-dao/20_register-managing-dao-on-dao-registry.ts +++ b/packages/contracts/deploy/new/40_finalize-managing-dao/20_register-managing-dao-on-dao-registry.ts @@ -1,13 +1,12 @@ -import {DAO__factory, DAORegistry__factory} from '../../../typechain'; import { - getContractAddress, - getENSAddress, - isENSDomainRegistered, - MANAGING_DAO_METADATA, - uploadToIPFS, -} from '../../helpers'; + DAORegistry__factory, + DAO__factory, + ENSRegistry__factory, +} from '../../../typechain'; +import {getContractAddress, getENSAddress, uploadToIPFS} from '../../helpers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import MANAGING_DAO_METADATA from '../../management-dao-metadata.json'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {ethers, network} = hre; @@ -21,6 +20,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { if (!daoSubdomain) throw new Error('ManagingDAO subdomain has not been set in .env'); + const node = ethers.utils.namehash(`${daoSubdomain}.${daoDomain}`); + // Get `managingDAO` address. const managingDAOAddress = await getContractAddress('DAO', hre); @@ -33,29 +34,38 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { deployer ); + const ensRegistryContract = ENSRegistry__factory.connect( + await getENSAddress(hre), + deployer + ); + let owner = await ensRegistryContract.owner(node); + let daoENSSubdomainRegistrar = await getContractAddress( + 'DAO_ENSSubdomainRegistrar', + hre + ); + if ( - await isENSDomainRegistered( - `${daoSubdomain}.${daoDomain}`, - await getENSAddress(hre), - deployer - ) + owner != daoENSSubdomainRegistrar && + owner != ethers.constants.AddressZero ) { - // not beeing able to register the managing DAO means that something is not right with the framework deployment used. - // Either a fruntrun happened or something else. Thus we abort here throw new Error( - `A DAO with ${daoSubdomain}.${daoDomain} is already registered! Aborting...` + `A DAO with ${daoSubdomain}.${daoDomain} is registered and owned by + someone other than ENSSubdomainRegistrar ${daoENSSubdomainRegistrar}.` + ); + } + + if (owner === ethers.constants.AddressZero) { + // Register `managingDAO` on `DAORegistry`. + const registerTx = await daoRegistryContract.register( + managingDAOAddress, + deployer.address, + daoSubdomain + ); + await registerTx.wait(); + console.log( + `Registered the (managingDAO: ${managingDAOAddress}) on (DAORegistry: ${daoRegistryAddress}), see (tx: ${registerTx.hash})` ); } - // Register `managingDAO` on `DAORegistry`. - const registerTx = await daoRegistryContract.register( - managingDAOAddress, - deployer.address, - daoSubdomain - ); - await registerTx.wait(); - console.log( - `Registered the (managingDAO: ${managingDAOAddress}) on (DAORegistry: ${daoRegistryAddress}), see (tx: ${registerTx.hash})` - ); // Set Metadata for the Managing DAO const managingDaoContract = DAO__factory.connect( @@ -67,10 +77,21 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { network.name ); - const setMetadataTX = await managingDaoContract.setMetadata( - ethers.utils.hexlify(ethers.utils.toUtf8Bytes(`ipfs://${metadataCIDPath}`)) + const hasMetadataPermission = await managingDaoContract.hasPermission( + managingDaoContract.address, + deployer.address, + ethers.utils.id('SET_METADATA_PERMISSION'), + '0x' ); - await setMetadataTX.wait(); + + if (hasMetadataPermission) { + const setMetadataTX = await managingDaoContract.setMetadata( + ethers.utils.hexlify( + ethers.utils.toUtf8Bytes(`ipfs://${metadataCIDPath}`) + ) + ); + await setMetadataTX.wait(); + } }; export default func; func.tags = ['New', 'RegisterManagingDAO']; diff --git a/packages/contracts/deploy/new/40_finalize-managing-dao/30_install-multisig-on-managing-dao.ts b/packages/contracts/deploy/new/40_finalize-managing-dao/30_install-multisig-on-managing-dao.ts index a54a7e4cb..9e9a490ae 100644 --- a/packages/contracts/deploy/new/40_finalize-managing-dao/30_install-multisig-on-managing-dao.ts +++ b/packages/contracts/deploy/new/40_finalize-managing-dao/30_install-multisig-on-managing-dao.ts @@ -1,46 +1,48 @@ -import {DeployFunction} from 'hardhat-deploy/types'; - import buildMetadataJson from '../../../src/plugins/governance/multisig/build-metadata.json'; -import {findEvent} from '../../../utils/event'; - -import {checkPermission, getContractAddress} from '../../helpers'; -import {Operation} from '../../../utils/types'; -import {hashHelpers} from '../../../utils/psp'; import { DAO__factory, MultisigSetup__factory, Multisig__factory, PluginSetupProcessor__factory, } from '../../../typechain'; -import {HardhatRuntimeEnvironment} from 'hardhat/types'; import {InstallationPreparedEvent} from '../../../typechain/PluginSetupProcessor'; +import {findEvent} from '../../../utils/event'; import {getNamedTypesFromMetadata} from '../../../utils/metadata'; +import {hashHelpers} from '../../../utils/psp'; +import {Operation} from '../../../utils/types'; +import {checkPermission, getContractAddress, getPSPAddress} from '../../helpers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {ethers, network} = hre; const [deployer] = await ethers.getSigners(); - if (network.name !== 'localhost' && network.name !== 'hardhat') { + if ( + network.name !== 'localhost' && + network.name !== 'hardhat' && + network.name !== 'zkLocalTestnet' + ) { if ( - !('MANAGINGDAO_MULTISIG_LISTEDONLY' in process.env) || - !('MANAGINGDAO_MULTISIG_MINAPPROVALS' in process.env) || - !('MANAGINGDAO_MULTISIG_APPROVERS' in process.env) + !('MANAGEMENT_DAO_MULTISIG_LISTEDONLY' in process.env) || + !('MANAGEMENT_DAO_MULTISIG_MINAPPROVALS' in process.env) || + !('MANAGEMENT_DAO_MULTISIG_APPROVERS' in process.env) ) { throw new Error('Managing DAO Multisig settings not set in .env'); } } - const approvers = process.env.MANAGINGDAO_MULTISIG_APPROVERS?.split(',') || [ - deployer.address, - ]; + const approvers = process.env.MANAGEMENT_DAO_MULTISIG_APPROVERS?.split( + ',' + ) || [deployer.address]; const minApprovals = parseInt( - process.env.MANAGINGDAO_MULTISIG_MINAPPROVALS || '1' + process.env.MANAGEMENT_DAO_MULTISIG_MINAPPROVALS || '1' ); - // In case `MANAGINGDAO_MULTISIG_LISTEDONLY` not present in .env + // In case `MANAGEMENT_DAO_MULTISIG_LISTEDONLY` not present in .env // which applies only hardhat/localhost, use `true` setting for extra safety for tests. const listedOnly = - 'MANAGINGDAO_MULTISIG_LISTEDONLY' in process.env - ? process.env.MANAGINGDAO_MULTISIG_LISTEDONLY === 'true' + 'MANAGEMENT_DAO_MULTISIG_LISTEDONLY' in process.env + ? process.env.MANAGEMENT_DAO_MULTISIG_LISTEDONLY === 'true' : true; // Get `managingDAO` address. @@ -53,7 +55,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ); // Get `PluginSetupProcessor` address. - const pspAddress = await getContractAddress('PluginSetupProcessor', hre); + const pspAddress = await getPSPAddress(hre) // Get `PluginSetupProcessor` contract. const pspContract = PluginSetupProcessor__factory.connect( diff --git a/packages/contracts/deploy/new/40_finalize-managing-dao/40_revoke-permissions-on-plugin-repos.ts b/packages/contracts/deploy/new/40_finalize-managing-dao/40_revoke-permissions-on-plugin-repos.ts new file mode 100644 index 000000000..97735ff41 --- /dev/null +++ b/packages/contracts/deploy/new/40_finalize-managing-dao/40_revoke-permissions-on-plugin-repos.ts @@ -0,0 +1,60 @@ +import {DAO__factory, PluginRepo__factory} from '../../../typechain'; +import {Operation} from '../../../utils/types'; +import {getContractAddress, managePermissions, Permission} from '../../helpers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {ethers} = hre; + const [deployer] = await ethers.getSigners(); + + // Revoke `ROOT_PERMISSION`, `MAINTAINER_PERMISSION` and `UPGRADE_REPO_PERMISSION` from `Deployer` on the permission manager of each PluginRepo. + for (const repoName in hre.aragonPluginRepos) { + const repoAddress = hre.aragonPluginRepos[repoName]; + + // if repoAddress empty, the deployment must have been marked as skipped. + if (repoAddress === '') continue; + + const revokePluginRepoPermissions: Permission[] = []; + revokePluginRepoPermissions.push({ + operation: Operation.Revoke, + where: { + name: repoName + ' PluginRepo', + address: repoAddress, + }, + who: {name: 'Deployer', address: deployer.address}, + permission: 'ROOT_PERMISSION', + }); + + revokePluginRepoPermissions.push({ + operation: Operation.Revoke, + where: { + name: repoName + ' PluginRepo', + address: repoAddress, + }, + who: {name: 'Deployer', address: deployer.address}, + permission: 'MAINTAINER_PERMISSION', + }); + + revokePluginRepoPermissions.push({ + operation: Operation.Revoke, + where: { + name: repoName + ' PluginRepo', + address: repoAddress, + }, + who: {name: 'Deployer', address: deployer.address}, + permission: 'UPGRADE_REPO_PERMISSION', + }); + + await managePermissions( + PluginRepo__factory.connect(repoAddress, deployer), + revokePluginRepoPermissions + ); + } + + console.log( + `\nPluginRepos are no longer owned by the (Deployer: ${deployer.address}),` + ); +}; +export default func; +func.tags = ['New', 'RevokePermissionsPluginRepos']; diff --git a/packages/contracts/deploy/new/40_finalize-managing-dao/40_revoke-permissions.ts b/packages/contracts/deploy/new/40_finalize-managing-dao/50_revoke-permissions-on-managing-dao.ts similarity index 66% rename from packages/contracts/deploy/new/40_finalize-managing-dao/40_revoke-permissions.ts rename to packages/contracts/deploy/new/40_finalize-managing-dao/50_revoke-permissions-on-managing-dao.ts index 20efcad71..8412c2bf1 100644 --- a/packages/contracts/deploy/new/40_finalize-managing-dao/40_revoke-permissions.ts +++ b/packages/contracts/deploy/new/40_finalize-managing-dao/50_revoke-permissions-on-managing-dao.ts @@ -1,6 +1,6 @@ import {DAO__factory, PluginRepo__factory} from '../../../typechain'; import {Operation} from '../../../utils/types'; -import {getContractAddress, managePermissions, Permission} from '../../helpers'; +import {getContractAddress, getPSPAddress, managePermissions, Permission} from '../../helpers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -18,7 +18,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const daoRegistryAddress = await getContractAddress('DAORegistry', hre); // Get `PluginSetupProcessor` address. - const pspAddress = await getContractAddress('PluginSetupProcessor', hre); + const pspAddress = await getPSPAddress(hre); // Get `managingDAO` address. const managingDAOAddress = await getContractAddress('DAO', hre); @@ -33,6 +33,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Revoke `ROOT_PERMISSION` from `PluginSetupProcessor`. // Revoke `APPLY_INSTALLATION_PERMISSION` from `Deployer`. // Revoke `ROOT_PERMISSION` from `Deployer`. + // Revoke `EXECUTE_PERMISSION` from `Deployer`. const revokePermissions = [ { operation: Operation.Revoke, @@ -73,46 +74,6 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ]; await managePermissions(managingDaoContract, revokePermissions); - // Revoke `ROOT_PERMISSION`, `MAINTAINER_PERMISSION` and `UPGRADE_REPO_PERMISSION` from `Deployer` on the permission manager of each PluginRepo. - for (const repoName in hre.aragonPluginRepos) { - const repoAddress = hre.aragonPluginRepos[repoName]; - const revokePluginRepoPermissions: Permission[] = []; - revokePluginRepoPermissions.push({ - operation: Operation.Revoke, - where: { - name: repoName + ' PluginRepo', - address: repoAddress, - }, - who: {name: 'Deployer', address: deployer.address}, - permission: 'ROOT_PERMISSION', - }); - - revokePluginRepoPermissions.push({ - operation: Operation.Revoke, - where: { - name: repoName + ' PluginRepo', - address: repoAddress, - }, - who: {name: 'Deployer', address: deployer.address}, - permission: 'MAINTAINER_PERMISSION', - }); - - revokePluginRepoPermissions.push({ - operation: Operation.Revoke, - where: { - name: repoName + ' PluginRepo', - address: repoAddress, - }, - who: {name: 'Deployer', address: deployer.address}, - permission: 'UPGRADE_REPO_PERMISSION', - }); - - await managePermissions( - PluginRepo__factory.connect(repoAddress, deployer), - revokePluginRepoPermissions - ); - } - console.log( `\nManagingDao is no longer owned by the (Deployer: ${deployer.address}),` + ` and all future actions of the (managingDAO: ${managingDAOAddress}) will be handled by the newly installed (Multisig plugin).` diff --git a/packages/contracts/deploy/new/40_finalize-managing-dao/99_verify_step.ts b/packages/contracts/deploy/new/40_finalize-managing-dao/99_verify_step.ts index 2ae045947..44f66db5c 100644 --- a/packages/contracts/deploy/new/40_finalize-managing-dao/99_verify_step.ts +++ b/packages/contracts/deploy/new/40_finalize-managing-dao/99_verify_step.ts @@ -1,10 +1,9 @@ +import {DAO__factory} from '../../../typechain'; +import {Operation} from '../../../utils/types'; +import {checkPermission, getContractAddress, getPSPAddress} from '../../helpers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {Operation} from '../../../utils/types'; -import {checkPermission, getContractAddress} from '../../helpers'; -import {DAO__factory} from '../../../typechain'; - const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log('\nVerifying managing DAO deployment.'); @@ -22,7 +21,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Get `DAORegistry` address. const daoRegistryAddress = await getContractAddress('DAORegistry', hre); // Get `PluginSetupProcessor` address. - const pspAddress = await getContractAddress('PluginSetupProcessor', hre); + const pspAddress = await getPSPAddress(hre) // Check revoked permission. await checkPermission(managingDaoContract, { diff --git a/packages/contracts/deploy/verification/99_conclude/00_save-contract-addresses.ts b/packages/contracts/deploy/new/50_save-contract-addresses.ts similarity index 94% rename from packages/contracts/deploy/verification/99_conclude/00_save-contract-addresses.ts rename to packages/contracts/deploy/new/50_save-contract-addresses.ts index d0e8a72e0..ff0db4148 100644 --- a/packages/contracts/deploy/verification/99_conclude/00_save-contract-addresses.ts +++ b/packages/contracts/deploy/new/50_save-contract-addresses.ts @@ -1,6 +1,7 @@ import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import {promises as fs} from 'fs'; +import { DAORegistry } from '../../typechain'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log('\nPrinting deployed contracts.'); @@ -43,8 +44,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await fs.writeFile( 'deployed_contracts.json', - JSON.stringify(deployedContractAddresses) + JSON.stringify(deployedContractAddresses, null, 2) ); + }; export default func; func.tags = ['New', 'Conclude']; diff --git a/packages/contracts/deploy/update/to_v1.3.0/60_Admin_PluginRepo.ts b/packages/contracts/deploy/update/to_v1.3.0/60_Admin_PluginRepo.ts index b3ea92ee4..9b163675b 100644 --- a/packages/contracts/deploy/update/to_v1.3.0/60_Admin_PluginRepo.ts +++ b/packages/contracts/deploy/update/to_v1.3.0/60_Admin_PluginRepo.ts @@ -4,7 +4,7 @@ import { PluginRepo__factory, PluginRepoFactory__factory, } from '../../../typechain'; -import {getContractAddress} from '../../helpers'; +import {getContractAddress, skipIfZkSync} from '../../helpers'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log( @@ -42,3 +42,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }; export default func; func.tags = ['AdminPluginRepo', 'v1.3.0']; +func.skip = async hre => await skipIfZkSync(hre, 'UpdateAdminPluginRepo'); diff --git a/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts b/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts index f58aa42f9..b5147e6ea 100644 --- a/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts +++ b/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts @@ -1,7 +1,6 @@ -import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {DeployFunction} from 'hardhat-deploy/types'; - import {verifyContract} from '../../../utils/etherscan'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; function delay(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); @@ -33,5 +32,6 @@ func.skip = (hre: HardhatRuntimeEnvironment) => Promise.resolve( hre.network.name === 'localhost' || hre.network.name === 'hardhat' || - hre.network.name === 'coverage' + hre.network.name === 'coverage' || + hre.network.name === 'zkLocalTestnet' ); diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index bacbdec76..7f3017f9a 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -3,15 +3,26 @@ import fs from 'fs'; import path from 'path'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; -import {extendEnvironment, HardhatUserConfig} from 'hardhat/config'; +import {extendEnvironment, HardhatUserConfig, task} from 'hardhat/config'; + import '@nomicfoundation/hardhat-chai-matchers'; -import '@nomicfoundation/hardhat-verify'; +import '@matterlabs/hardhat-zksync-deploy'; +import '@matterlabs/hardhat-zksync-solc'; +import '@matterlabs/hardhat-zksync-node'; import 'hardhat-deploy'; import 'hardhat-gas-reporter'; -import '@openzeppelin/hardhat-upgrades'; import 'solidity-coverage'; import 'solidity-docgen'; +// If you're running on zksync, import the below +import '@matterlabs/hardhat-zksync-upgradable'; +import '@matterlabs/hardhat-zksync-ethers'; +import '@matterlabs/hardhat-zksync-verify'; + +// If you're running on hardhat, import the following +// import '@nomicfoundation/hardhat-verify' +// import '@openzeppelin/hardhat-upgrades' + import {AragonPluginRepos, TestingFork} from './utils/types'; dotenv.config(); @@ -28,6 +39,46 @@ for (const network of Object.keys(networks)) { networks[network].accounts = accounts; } +task('build-contracts').setAction(async (args, hre) => { + await hre.run('compile'); + if ( + hre.network.name === 'zkTestnet' || + hre.network.name === 'zkLocalTestnet' || + hre.network.name === 'zkMainnet' + ) { + // Copy zkSync specific build artifacts and cache to the default directories. + // This ensures that we don't need to change import paths for artifacts in the project. + fs.cpSync('./build/artifacts-zk', './artifacts', { + recursive: true, + force: true, + }); + fs.cpSync('./build/cache-zk', './cache', {recursive: true, force: true}); + + return; + } + + fs.cpSync('./build/artifacts', './artifacts', {recursive: true, force: true}); + fs.cpSync('./build/cache', './cache', {recursive: true, force: true}); +}); + +task('deploy-contracts').setAction(async (args, hre) => { + await hre.run('build-contracts'); + await hre.run('deploy'); +}); + +task('test-contracts').setAction(async (args, hre) => { + await hre.run('build-contracts'); + const imp = await import('./test/test-utils/wrapper'); + + const wrapper = await imp.Wrapper.create( + hre.network.name, + hre.ethers.provider + ); + hre.wrapper = wrapper; + + await hre.run('test'); +}); + // Extend HardhatRuntimeEnvironment extendEnvironment((hre: HardhatRuntimeEnvironment) => { const aragonPluginRepos: AragonPluginRepos = { @@ -55,6 +106,11 @@ console.log('Is deploy test is enabled: ', ENABLE_DEPLOY_TEST); // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more const config: HardhatUserConfig = { + zksolc: { + version: '1.5.0', + compilerSource: 'binary', + settings: {}, + }, solidity: { version: '0.8.17', settings: { @@ -80,6 +136,63 @@ const config: HardhatUserConfig = { ? ['./deploy'] : ['./deploy/new', './deploy/verification'], }, + zkLocalTestnet: { + url: 'http://127.0.0.1:8011', + ethNetwork: 'http://127.0.0.1:8545', + zksync: true, + deploy: ['./deploy/new'], + gas: 15000000, + blockGasLimit: 30000000, + accounts: [ + // Rich accounts with pre-funded balances for the chain on port 8545. + // These accounts are used for testing purposes and have sufficient funds. + '0x3d3cbc973389cb26f657686445bcc75662b415b656078503592ac8c1abb8810e', + '0x509ca2e9e6acf0ba086477910950125e698d4ea70fa6f63e000c5a22bda9361c', + '0x71781d3a358e7a65150e894264ccc594993fbc0ea12d69508a340bc1d4f5bfbc', + '0x379d31d4a7031ead87397f332aab69ef5cd843ba3898249ca1046633c0c7eefe', + '0x105de4e75fe465d075e1daae5647a02e3aad54b8d23cf1f70ba382b9f9bee839', + '0x7becc4a46e0c3b512d380ca73a4c868f790d1055a7698f38fb3ca2b2ac97efbb', + '0xe0415469c10f3b1142ce0262497fe5c7a0795f0cbfd466a6bfa31968d0f70841', + '0x4d91647d0a8429ac4433c83254fb9625332693c848e578062fe96362f32bfe91', + '0x41c9f9518aa07b50cb1c0cc160d45547f57638dd824a8d85b5eb3bf99ed2bdeb', + '0xb0680d66303a0163a19294f1ef8c95cd69a9d7902a4aca99c05f3e134e68a11a', + // Additional accounts to ensure ethers.getSigners() returns 20 addresses. + // zkSync only returns 10 accounts by default, which may break tests + // that expect more. Adding these extra accounts prevents the need + // to rewrite tests by maintaining a consistent 20 accounts. + // ethers.getSigners() still return 20 addresses instead of 10. + '0xec4822aa037f555ba18304bfcb6e30f3c981e730f57e7bad174312868952af90', + '0x00058bfe32cbfe46e81a7c60178fae13078068b5a3a8e1441d47f7cb96665286', + '0x4e0e42d531f61e25f12d64504ec5f021ead984c406fb5df97d27d813d11222a3', + '0x9534dcb0f1e8c94c8c936b39d8a5667169df34d80966d13fe7ab9ef0c78c704a', + '0xe6c08ed153863f48ccb843b6ba82e4880cd30a0874309a291d214a3a7d794499', + '0x247411619389bbc301816f8928c568115c7e340daf950e241f447bcb68644f92', + '0xaff4231bc7ef2141fe25aea9d957114064a778a1aeb54276ea8b6576b958d30f', + '0x7c81899f9d699ce7eeea50ce47fbcf2bd84ae5d7d1b6eb01cd9eedd73eac13ee', + '0xab5f8bf24c10790972c3a25c78e7ae070619d07c93dd189f86ccac67e82da837', + '0x23d60e6c95faf5d242edeaed780868fb55f85556764dcc11082dd40d9a2ffd3f', + ], + }, + zkTestnet: { + url: 'https://sepolia.era.zksync.dev', + ethNetwork: 'sepolia', + zksync: true, + verifyURL: + 'https://explorer.sepolia.era.zksync.dev/contract_verification', + deploy: ['./deploy/new', './deploy/verification'], + accounts: accounts, + forceDeploy: true, + }, + zkMainnet: { + url: 'https://mainnet.era.zksync.io', + ethNetwork: 'mainnet', + zksync: true, + verifyURL: + 'https://zksync2-mainnet-explorer.zksync.io/contract_verification', + deploy: ['./deploy/new', './deploy/verification'], + accounts: accounts, + forceDeploy: true, + }, ...networks, }, gasReporter: { @@ -143,8 +256,8 @@ const config: HardhatUserConfig = { paths: { sources: './src', tests: './test', - cache: './cache', - artifacts: './artifacts', + cache: './build/cache', + artifacts: './build/artifacts', deploy: './deploy', }, docgen: { @@ -156,7 +269,7 @@ const config: HardhatUserConfig = { exclude: ['test'], }, mocha: { - timeout: 60000, // 60 seconds // increase the timeout for subdomain validation tests + timeout: 6000000, // 60 seconds // increase the timeout for subdomain validation tests }, }; diff --git a/packages/contracts/package.json b/packages/contracts/package.json index b304dce54..1bfdceb28 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -15,13 +15,15 @@ "typechain:osx": "ts-node scripts/generate-typechain-osx.ts", "typechain:osx-versions": "ts-node scripts/generate-typechain-osx-versions.ts", "typechain": "yarn typechain:osx && yarn typechain:osx-versions", - "test": "hardhat test", - "build": "hardhat compile && yarn typechain", + "test": "hardhat test-contracts", + "build": "hardhat build-contracts", + "verify": "hardhat verify", + "postbuild": "yarn typechain", "build:npm": "rollup --config rollup.config.ts", "coverage": "hardhat coverage --solcoverjs ./.solcover.js", "flatten": "hardhat flatten", "analyze": "mythx analyze", - "deploy": "hardhat deploy", + "deploy": "hardhat deploy-contracts", "dev": "yarn hardhat node --hostname 0.0.0.0", "prepublishOnly": "yarn build && yarn build:npm", "docgen": "hardhat docgen", @@ -44,13 +46,19 @@ "@aragon/osx-v1.0.1": "npm:@aragon/osx@1.0.1", "@defi-wonderland/smock": "^2.3.4", "@ensdomains/ens-contracts": "0.0.11", + "@matterlabs/hardhat-zksync-deploy": "0.8", + "@matterlabs/hardhat-zksync-node": "^0.1.0", + "@matterlabs/hardhat-zksync-solc": "^1.1.4", + "@matterlabs/hardhat-zksync-upgradable": "^0.4.0", + "@matterlabs/hardhat-zksync-verify": "^0.6.0", "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", "@nomicfoundation/hardhat-verify": "^1.0.4", "@nomiclabs/hardhat-ethers": "^2.2.1", "@opengsn/contracts": "2.2.6", - "@openzeppelin/contracts": "4.8.1", - "@openzeppelin/contracts-upgradeable": "4.8.1", + "@openzeppelin/contracts": "4.9.5", + "@openzeppelin/contracts-upgradeable": "4.9.5", "@openzeppelin/hardhat-upgrades": "^1.23.1", + "@openzeppelin/upgrades-core": "^1.33.1", "@rollup/plugin-json": "^4.1.0", "@typechain/ethers-v5": "^7.2.0", "@typechain/hardhat": "^2.3.1", @@ -69,9 +77,9 @@ "eslint-plugin-prettier": "^3.4.1", "eslint-plugin-promise": "^5.1.1", "ethereumjs-util": "^7.1.4", - "ethers": "^5.5.1", - "hardhat": "^2.12.7", - "hardhat-deploy": "^0.9.26", + "ethers": "^5.7.2", + "hardhat": "^2.12.4", + "hardhat-deploy": "0.12.4", "hardhat-gas-reporter": "^1.0.4", "ipfs-http-client": "51.0.0", "prettier": "^2.4.1", @@ -84,6 +92,8 @@ "tmp-promise": "^3.0.3", "ts-node": "^8.1.0", "typechain": "^5.2.0", - "typescript": "^4.4.4" + "typescript": "^4.4.4", + "zksync-ethers": "^5.0", + "@matterlabs/hardhat-zksync-ethers": "0.0.1-beta.2" } } diff --git a/packages/contracts/src/test/dao/ERC1271Mock.sol b/packages/contracts/src/test/dao/ERC1271Mock.sol index cc39cfd91..3c2514dc4 100644 --- a/packages/contracts/src/test/dao/ERC1271Mock.sol +++ b/packages/contracts/src/test/dao/ERC1271Mock.sol @@ -3,7 +3,15 @@ pragma solidity 0.8.17; contract ERC1271Mock { - function isValidSignature(bytes32, bytes memory) public pure returns (bytes4) { - return 0x41424344; + event Called(uint); + + bytes4 private magicValue; + + function setMagicValue(bytes4 _magicValue) public { + magicValue = _magicValue; + } + + function isValidSignature(bytes32, bytes memory) public view returns (bytes4) { + return magicValue; } } diff --git a/packages/contracts/src/test/plugin/UUPSUpgradeable/PluginUUPSUpgradeableSetupMock.sol b/packages/contracts/src/test/plugin/UUPSUpgradeable/PluginUUPSUpgradeableSetupMock.sol index 482d5b94a..b0b04ce11 100644 --- a/packages/contracts/src/test/plugin/UUPSUpgradeable/PluginUUPSUpgradeableSetupMock.sol +++ b/packages/contracts/src/test/plugin/UUPSUpgradeable/PluginUUPSUpgradeableSetupMock.sol @@ -9,7 +9,66 @@ import {IPluginSetup} from "../../../framework/plugin/setup/IPluginSetup.sol"; import {mockPermissions, mockHelpers, mockPluginProxy} from "../PluginMockData.sol"; import {PluginUUPSUpgradeableV1Mock, PluginUUPSUpgradeableV2Mock, PluginUUPSUpgradeableV3Mock} from "./PluginUUPSUpgradeableMock.sol"; -contract PluginUUPSUpgradeableSetupV1Mock is PluginSetup { +abstract contract MockedHelper is PluginSetup { + // Used for mocking in tests + uint160 private helpersCount; + uint160 private permissionMockLowerIndex; + uint160 private permissionMockUpperIndex; + + // Helper emits to help with testing + event InstallationPrepared(address dao, bytes data); + event UninstallationPrepared(address dao, SetupPayload payload); + event UpdatePrepared(address dao, uint16 build, SetupPayload payload); + + // helper functions to help with testing + function emitInstallationPrepared(address dao, bytes memory data) internal { + emit InstallationPrepared(dao, data); + } + + function emitUpdatePrepared(address dao, uint16 build, SetupPayload memory payload) internal { + emit UpdatePrepared(dao, build, payload); + } + + function emitUninstallationPrepared(address dao, SetupPayload memory payload) internal { + emit UninstallationPrepared(dao, payload); + } + + // called externally to allow mock behaviour + function mockPermissionIndexes(uint160 _lowerIndex, uint160 _upperIndex) public { + permissionMockLowerIndex = _lowerIndex; + permissionMockUpperIndex = _upperIndex; + } + + function mockHelperCount(uint160 _helpersCount) public { + helpersCount = _helpersCount; + } + + // called internally from the setup contracts + function _mockHelpers(uint160 _helpersCount) internal view returns (address[] memory) { + return mockHelpers(helpersCount != 0 ? helpersCount : _helpersCount); + } + + function _mockPermissions( + uint160 lower, + uint160 upper, + PermissionLib.Operation _op + ) internal view returns (PermissionLib.MultiTargetPermission[] memory permissions) { + return + mockPermissions( + permissionMockLowerIndex != 0 ? permissionMockLowerIndex : lower, + permissionMockUpperIndex != 0 ? permissionMockUpperIndex : upper, + _op + ); + } + + function reset() public { + permissionMockLowerIndex = 0; + permissionMockUpperIndex = 0; + helpersCount = 0; + } +} + +contract PluginUUPSUpgradeableSetupV1Mock is PluginSetup, MockedHelper { address internal pluginBase; constructor() { @@ -19,11 +78,13 @@ contract PluginUUPSUpgradeableSetupV1Mock is PluginSetup { /// @inheritdoc IPluginSetup function prepareInstallation( address _dao, - bytes memory + bytes memory _data ) public virtual override returns (address plugin, PreparedSetupData memory preparedSetupData) { plugin = mockPluginProxy(pluginBase, _dao); - preparedSetupData.helpers = mockHelpers(2); - preparedSetupData.permissions = mockPermissions(0, 2, PermissionLib.Operation.Grant); + preparedSetupData.helpers = _mockHelpers(2); + preparedSetupData.permissions = _mockPermissions(0, 2, PermissionLib.Operation.Grant); + + emitInstallationPrepared(_dao, _data); } /// @inheritdoc IPluginSetup @@ -32,7 +93,9 @@ contract PluginUUPSUpgradeableSetupV1Mock is PluginSetup { SetupPayload calldata _payload ) external virtual override returns (PermissionLib.MultiTargetPermission[] memory permissions) { (_dao, _payload); - permissions = mockPermissions(0, 1, PermissionLib.Operation.Revoke); + permissions = _mockPermissions(0, 1, PermissionLib.Operation.Revoke); + + emitUninstallationPrepared(_dao, _payload); } /// @inheritdoc IPluginSetup @@ -44,12 +107,14 @@ contract PluginUUPSUpgradeableSetupV1Mock is PluginSetup { contract PluginUUPSUpgradeableSetupV1MockBad is PluginUUPSUpgradeableSetupV1Mock { function prepareInstallation( address _dao, - bytes memory - ) public pure override returns (address plugin, PreparedSetupData memory preparedSetupData) { + bytes memory _data + ) public override returns (address plugin, PreparedSetupData memory preparedSetupData) { (_dao); plugin = address(0); // The bad behaviour is returning the same address over and over again preparedSetupData.helpers = mockHelpers(1); - preparedSetupData.permissions = mockPermissions(0, 1, PermissionLib.Operation.Grant); + preparedSetupData.permissions = super._mockPermissions(0, 1, PermissionLib.Operation.Grant); + + emitInstallationPrepared(_dao, _data); } } @@ -61,11 +126,13 @@ contract PluginUUPSUpgradeableSetupV2Mock is PluginUUPSUpgradeableSetupV1Mock { /// @inheritdoc IPluginSetup function prepareInstallation( address _dao, - bytes memory + bytes memory _data ) public virtual override returns (address plugin, PreparedSetupData memory preparedSetupData) { plugin = mockPluginProxy(pluginBase, _dao); - preparedSetupData.helpers = mockHelpers(2); - preparedSetupData.permissions = mockPermissions(0, 2, PermissionLib.Operation.Grant); + preparedSetupData.helpers = super._mockHelpers(2); + preparedSetupData.permissions = super._mockPermissions(0, 2, PermissionLib.Operation.Grant); + + emitInstallationPrepared(_dao, _data); } function prepareUpdate( @@ -82,12 +149,18 @@ contract PluginUUPSUpgradeableSetupV2Mock is PluginUUPSUpgradeableSetupV1Mock { // Update from V1 if (_currentBuild == 1) { - preparedSetupData.helpers = mockHelpers(2); + preparedSetupData.helpers = super._mockHelpers(2); initData = abi.encodeWithSelector( PluginUUPSUpgradeableV2Mock.initializeV1toV2.selector ); - preparedSetupData.permissions = mockPermissions(1, 2, PermissionLib.Operation.Grant); + preparedSetupData.permissions = super._mockPermissions( + 1, + 2, + PermissionLib.Operation.Grant + ); } + + emitUpdatePrepared(_dao, _currentBuild, _payload); } } @@ -99,11 +172,13 @@ contract PluginUUPSUpgradeableSetupV3Mock is PluginUUPSUpgradeableSetupV2Mock { /// @inheritdoc IPluginSetup function prepareInstallation( address _dao, - bytes memory + bytes memory _data ) public virtual override returns (address plugin, PreparedSetupData memory preparedSetupData) { plugin = mockPluginProxy(pluginBase, _dao); - preparedSetupData.helpers = mockHelpers(3); - preparedSetupData.permissions = mockPermissions(0, 3, PermissionLib.Operation.Grant); + preparedSetupData.helpers = super._mockHelpers(3); + preparedSetupData.permissions = super._mockPermissions(0, 3, PermissionLib.Operation.Grant); + + emitInstallationPrepared(_dao, _data); } function prepareUpdate( @@ -120,21 +195,31 @@ contract PluginUUPSUpgradeableSetupV3Mock is PluginUUPSUpgradeableSetupV2Mock { // Update from V1 if (_currentBuild == 1) { - preparedSetupData.helpers = mockHelpers(3); + preparedSetupData.helpers = super._mockHelpers(3); initData = abi.encodeWithSelector( PluginUUPSUpgradeableV3Mock.initializeV1toV3.selector ); - preparedSetupData.permissions = mockPermissions(1, 3, PermissionLib.Operation.Grant); + preparedSetupData.permissions = super._mockPermissions( + 1, + 3, + PermissionLib.Operation.Grant + ); } // Update from V2 if (_currentBuild == 2) { - preparedSetupData.helpers = mockHelpers(3); + preparedSetupData.helpers = super._mockHelpers(3); initData = abi.encodeWithSelector( PluginUUPSUpgradeableV3Mock.initializeV2toV3.selector ); - preparedSetupData.permissions = mockPermissions(2, 3, PermissionLib.Operation.Grant); + preparedSetupData.permissions = super._mockPermissions( + 2, + 3, + PermissionLib.Operation.Grant + ); } + + emitUpdatePrepared(_dao, _currentBuild, _payload); } } @@ -151,11 +236,13 @@ contract PluginUUPSUpgradeableSetupV4Mock is PluginUUPSUpgradeableSetupV3Mock { /// @inheritdoc IPluginSetup function prepareInstallation( address _dao, - bytes memory + bytes memory _data ) public virtual override returns (address plugin, PreparedSetupData memory preparedSetupData) { plugin = mockPluginProxy(pluginBase, _dao); - preparedSetupData.helpers = mockHelpers(3); - preparedSetupData.permissions = mockPermissions(0, 3, PermissionLib.Operation.Grant); + preparedSetupData.helpers = super._mockHelpers(3); + preparedSetupData.permissions = super._mockPermissions(0, 3, PermissionLib.Operation.Grant); + + emitInstallationPrepared(_dao, _data); } function prepareUpdate( @@ -174,7 +261,11 @@ contract PluginUUPSUpgradeableSetupV4Mock is PluginUUPSUpgradeableSetupV3Mock { // the desired updated permissions. PluginSetupProcessor will take care of // not calling `upgradeTo` on the plugin in such cases. if (_currentBuild == 3) { - preparedSetupData.permissions = mockPermissions(3, 4, PermissionLib.Operation.Grant); + preparedSetupData.permissions = super._mockPermissions( + 3, + 4, + PermissionLib.Operation.Grant + ); } // If the update happens from those that have different implementation addresses(v1,v2) // proxy(plugin) contract should be upgraded to the new base implementation which requires(not always though) @@ -183,7 +274,13 @@ contract PluginUUPSUpgradeableSetupV4Mock is PluginUUPSUpgradeableSetupV3Mock { else if (_currentBuild == 1 || _currentBuild == 2) { (initData, preparedSetupData) = super.prepareUpdate(_dao, _currentBuild, _payload); // Even for this case, dev might decide to modify the permissions.. - preparedSetupData.permissions = mockPermissions(4, 5, PermissionLib.Operation.Grant); + preparedSetupData.permissions = super._mockPermissions( + 4, + 5, + PermissionLib.Operation.Grant + ); } + + emitUpdatePrepared(_dao, _currentBuild, _payload); } } diff --git a/packages/contracts/src/test/token/GovernanceERC20Mock.sol b/packages/contracts/src/test/token/GovernanceERC20Mock.sol index cdc6aab5c..3bcf6e824 100644 --- a/packages/contracts/src/test/token/GovernanceERC20Mock.sol +++ b/packages/contracts/src/test/token/GovernanceERC20Mock.sol @@ -22,12 +22,16 @@ contract GovernanceERC20Mock is GovernanceERC20 { // sets the balance of the address // this mints/burns the amount depending on the current balance - function setBalance(address to, uint256 amount) public { - uint256 old = balanceOf(to); - if (old < amount) { - _mint(to, amount - old); - } else if (old > amount) { - _burn(to, old - amount); + function setBalances(address[] calldata receivers, uint256[] calldata amounts) public { + for (uint i = 0; i < receivers.length; i++) { + address to = receivers[i]; + uint256 amount = amounts[i]; + uint256 old = balanceOf(to); + if (old < amount) { + _mint(to, amount - old); + } else if (old > amount) { + _burn(to, old - amount); + } } } } diff --git a/packages/contracts/src/utils/deployment/ProxyFactory.sol b/packages/contracts/src/utils/deployment/ProxyFactory.sol new file mode 100644 index 000000000..c340ed68f --- /dev/null +++ b/packages/contracts/src/utils/deployment/ProxyFactory.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ProxyLib} from "./ProxyLib.sol"; + +/// @title ProxyFactory +/// @author Aragon X - 2024 +/// @notice A factory to deploy proxies via the UUPS pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) and minimal proxy pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)). +/// @custom:security-contact sirt@aragon.org +contract ProxyFactory { + using ProxyLib for address; + /// @notice The immutable logic contract address. + address internal immutable IMPLEMENTATION; + + /// @notice Emitted when an proxy contract is created. + /// @param proxy The proxy address. + event ProxyCreated(address proxy); + + /// @notice Initializes the contract with a logic contract address. + /// @param _implementation The logic contract address. + constructor(address _implementation) { + IMPLEMENTATION = _implementation; + } + + /// @notice Creates an [ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) proxy contract pointing to the pre-set logic contract. + /// @param _data The initialization data for this contract. + /// @return proxy The address of the proxy contract created. + /// @dev If `_data` is non-empty, it is used in a delegate call to the `_implementation` contract. This will typically be an encoded function call initializing the proxy (see [OpenZeppelin ERC1967Proxy-constructor](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy-constructor-address-bytes-)). + function deployUUPSProxy(bytes memory _data) external returns (address proxy) { + proxy = IMPLEMENTATION.deployUUPSProxy(_data); + emit ProxyCreated({proxy: proxy}); + } + + /// @notice Creates an [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy contract pointing to the pre-set logic contract. + /// @param _data The initialization data for this contract. + /// @return proxy The address of the proxy contract created. + /// @dev If `_data` is non-empty, it is used in a call to the clone contract. This will typically be an encoded function call initializing the storage of the contract. + function deployMinimalProxy(bytes memory _data) external returns (address proxy) { + proxy = IMPLEMENTATION.deployMinimalProxy(_data); + emit ProxyCreated({proxy: proxy}); + } + + /// @notice Returns the implementation contract address. + /// @return The address of the implementation contract. + /// @dev The implementation can be cloned via the minimal proxy pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)), or proxied via the UUPS proxy pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). + function implementation() public view returns (address) { + return IMPLEMENTATION; + } +} diff --git a/packages/contracts/src/utils/deployment/ProxyLib.sol b/packages/contracts/src/utils/deployment/ProxyLib.sol new file mode 100644 index 000000000..389b69a78 --- /dev/null +++ b/packages/contracts/src/utils/deployment/ProxyLib.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +/// @title ProxyLib +/// @author Aragon X - 2024 +/// @notice A library containing methods for the deployment of proxies via the UUPS pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) and minimal proxy pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)). +/// @custom:security-contact sirt@aragon.org +library ProxyLib { + using Address for address; + using Clones for address; + + /// @notice Creates an [ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) UUPS proxy contract pointing to a logic contract and allows to immediately initialize it. + /// @param _logic The logic contract the proxy is pointing to. + /// @param _initCalldata The initialization data for this contract. + /// @return uupsProxy The address of the UUPS proxy contract created. + /// @dev If `_initCalldata` is non-empty, it is used in a delegate call to the `_logic` contract. This will typically be an encoded function call initializing the storage of the proxy (see [OpenZeppelin ERC1967Proxy-constructor](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy-constructor-address-bytes-)). + function deployUUPSProxy( + address _logic, + bytes memory _initCalldata + ) internal returns (address uupsProxy) { + uupsProxy = address(new ERC1967Proxy({_logic: _logic, _data: _initCalldata})); + } + + /// @notice Creates an [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167) minimal proxy contract, also known as clones, pointing to a logic contract and allows to immediately initialize it. + /// @param _logic The logic contract the proxy is pointing to. + /// @param _initCalldata The initialization data for this contract. + /// @return minimalProxy The address of the minimal proxy contract created. + /// @dev If `_initCalldata` is non-empty, it is used in a call to the clone contract. This will typically be an encoded function call initializing the storage of the contract. + function deployMinimalProxy( + address _logic, + bytes memory _initCalldata + ) internal returns (address minimalProxy) { + minimalProxy = _logic.clone(); + if (_initCalldata.length > 0) { + minimalProxy.functionCall({data: _initCalldata}); + } + } +} diff --git a/packages/contracts/src/zksync/PluginSetupProcessorUpgradeable.sol b/packages/contracts/src/zksync/PluginSetupProcessorUpgradeable.sol new file mode 100644 index 000000000..e5bb90709 --- /dev/null +++ b/packages/contracts/src/zksync/PluginSetupProcessorUpgradeable.sol @@ -0,0 +1,759 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity 0.8.17; + +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import {DAO, IDAO} from "../core/dao/DAO.sol"; +import {PermissionLib} from "../core/permission/PermissionLib.sol"; +import {PluginUUPSUpgradeable} from "../core/plugin/PluginUUPSUpgradeable.sol"; +import {IPlugin} from "../core/plugin/IPlugin.sol"; + +import {PluginRepoRegistry} from "../framework/plugin/repo/PluginRepoRegistry.sol"; +import {PluginRepo} from "../framework/plugin/repo/PluginRepo.sol"; + +import {IPluginSetup} from "../framework/plugin/setup/IPluginSetup.sol"; +import {PluginSetup} from "../framework/plugin/setup/PluginSetup.sol"; +import {PluginSetupRef, hashHelpers, hashPermissions, _getPreparedSetupId, _getAppliedSetupId, _getPluginInstallationId, PreparationType} from "../framework/plugin/setup/PluginSetupProcessorHelpers.sol"; + +import {DaoAuthorizableUpgradeable} from "../core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; + +/// @title PluginSetupProcessor +/// @author Aragon Association - 2022-2023 +/// @notice This contract processes the preparation and application of plugin setups (installation, update, uninstallation) on behalf of a requesting DAO. +/// @dev This contract is temporarily granted the `ROOT_PERMISSION_ID` permission on the applying DAO and therefore is highly security critical. +contract PluginSetupProcessorUpgradeable is UUPSUpgradeable, DaoAuthorizableUpgradeable { + using ERC165Checker for address; + + /// @notice The ID of the permission required to call the `_authorizeUpgrade` function. + bytes32 public constant UPGRADE_PSP_PERMISSION_ID = keccak256("UPGRADE_PSP_PERMISSION"); + + /// @notice The ID of the permission required to call the `applyInstallation` function. + bytes32 public constant APPLY_INSTALLATION_PERMISSION_ID = + keccak256("APPLY_INSTALLATION_PERMISSION"); + + /// @notice The ID of the permission required to call the `applyUpdate` function. + bytes32 public constant APPLY_UPDATE_PERMISSION_ID = keccak256("APPLY_UPDATE_PERMISSION"); + + /// @notice The ID of the permission required to call the `applyUninstallation` function. + bytes32 public constant APPLY_UNINSTALLATION_PERMISSION_ID = + keccak256("APPLY_UNINSTALLATION_PERMISSION"); + + /// @notice The hash obtained from the bytes-encoded empty array to be used for UI updates being required to submit an empty permission array. + /// @dev The hash is computed via `keccak256(abi.encode([]))`. + bytes32 private constant EMPTY_ARRAY_HASH = + 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; + + /// @notice The hash obtained from the bytes-encoded zero value. + /// @dev The hash is computed via `keccak256(abi.encode(0))`. + bytes32 private constant ZERO_BYTES_HASH = + 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; + + /// @notice A struct containing information related to plugin setups that have been applied. + /// @param blockNumber The block number at which the `applyInstallation`, `applyUpdate` or `applyUninstallation` was executed. + /// @param currentAppliedSetupId The current setup id that plugin holds. Needed to confirm that `prepareUpdate` or `prepareUninstallation` happens for the plugin's current/valid dependencies. + /// @param preparedSetupIdToBlockNumber The mapping between prepared setup IDs and block numbers at which `prepareInstallation`, `prepareUpdate` or `prepareUninstallation` was executed. + struct PluginState { + uint256 blockNumber; + bytes32 currentAppliedSetupId; + mapping(bytes32 => uint256) preparedSetupIdToBlockNumber; + } + + /// @notice A mapping between the plugin installation ID (obtained from the DAO and plugin address) and the plugin state information. + /// @dev This variable is public on purpose to allow future versions to access and migrate the storage. + mapping(bytes32 => PluginState) public states; + + /// @notice The struct containing the parameters for the `prepareInstallation` function. + /// @param pluginSetupRef The reference to the plugin setup to be used for the installation. + /// @param data The bytes-encoded data containing the input parameters for the installation preparation as specified in the corresponding ABI on the version's metadata. + struct PrepareInstallationParams { + PluginSetupRef pluginSetupRef; + bytes data; + } + + /// @notice The struct containing the parameters for the `applyInstallation` function. + /// @param pluginSetupRef The reference to the plugin setup used for the installation. + /// @param plugin The address of the plugin contract to be installed. + /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. + /// @param helpersHash The hash of helpers that were deployed in `prepareInstallation`. This helps to derive the setup ID. + struct ApplyInstallationParams { + PluginSetupRef pluginSetupRef; + address plugin; + PermissionLib.MultiTargetPermission[] permissions; + bytes32 helpersHash; + } + + /// @notice The struct containing the parameters for the `prepareUpdate` function. + /// @param currentVersionTag The tag of the current plugin version to update from. + /// @param newVersionTag The tag of the new plugin version to update to. + /// @param pluginSetupRepo The plugin setup repository address on which the plugin exists. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// This includes the bytes-encoded data containing the input parameters for the update preparation as specified in the corresponding ABI on the version's metadata. + struct PrepareUpdateParams { + PluginRepo.Tag currentVersionTag; + PluginRepo.Tag newVersionTag; + PluginRepo pluginSetupRepo; + IPluginSetup.SetupPayload setupPayload; + } + + /// @notice The struct containing the parameters for the `applyUpdate` function. + /// @param plugin The address of the plugin contract to be updated. + /// @param pluginSetupRef The reference to the plugin setup used for the update. + /// @param initData The encoded data (function selector and arguments) to be provided to `upgradeToAndCall`. + /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the DAO. + /// @param helpersHash The hash of helpers that were deployed in `prepareUpdate`. This helps to derive the setup ID. + struct ApplyUpdateParams { + address plugin; + PluginSetupRef pluginSetupRef; + bytes initData; + PermissionLib.MultiTargetPermission[] permissions; + bytes32 helpersHash; + } + + /// @notice The struct containing the parameters for the `prepareUninstallation` function. + /// @param pluginSetupRef The reference to the plugin setup to be used for the uninstallation. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// This includes the bytes-encoded data containing the input parameters for the uninstallation preparation as specified in the corresponding ABI on the version's metadata. + struct PrepareUninstallationParams { + PluginSetupRef pluginSetupRef; + IPluginSetup.SetupPayload setupPayload; + } + + /// @notice The struct containing the parameters for the `applyInstallation` function. + /// @param plugin The address of the plugin contract to be uninstalled. + /// @param pluginSetupRef The reference to the plugin setup used for the uninstallation. + /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcess. + struct ApplyUninstallationParams { + address plugin; + PluginSetupRef pluginSetupRef; + PermissionLib.MultiTargetPermission[] permissions; + } + + /// @notice The plugin repo registry listing the `PluginRepo` contracts versioning the `PluginSetup` contracts. + PluginRepoRegistry public repoRegistry; + + /// @notice Thrown if a setup is unauthorized and cannot be applied because of a missing permission of the associated DAO. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param caller The address (EOA or contract) that requested the application of a setup on the associated DAO. + /// @param permissionId The permission identifier. + /// @dev This is thrown if the `APPLY_INSTALLATION_PERMISSION_ID`, `APPLY_UPDATE_PERMISSION_ID`, or APPLY_UNINSTALLATION_PERMISSION_ID is missing. + error SetupApplicationUnauthorized(address dao, address caller, bytes32 permissionId); + + /// @notice Thrown if a plugin is not upgradeable. + /// @param plugin The address of the plugin contract. + error PluginNonupgradeable(address plugin); + + error PSPNonupgradeable(); + + /// @notice Thrown if the upgrade of an `UUPSUpgradeable` proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)) failed. + /// @param proxy The address of the proxy. + /// @param implementation The address of the implementation contract. + /// @param initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. + error PluginProxyUpgradeFailed(address proxy, address implementation, bytes initData); + + /// @notice Thrown if a contract does not support the `IPlugin` interface. + /// @param plugin The address of the contract. + error IPluginNotSupported(address plugin); + + /// @notice Thrown if a plugin repository does not exist on the plugin repo registry. + error PluginRepoNonexistent(); + + /// @notice Thrown if a plugin setup was already prepared indicated by the prepared setup ID. + /// @param preparedSetupId The prepared setup ID. + error SetupAlreadyPrepared(bytes32 preparedSetupId); + + /// @notice Thrown if a prepared setup ID is not eligible to be applied. This can happen if another setup has been already applied or if the setup wasn't prepared in the first place. + /// @param preparedSetupId The prepared setup ID. + error SetupNotApplicable(bytes32 preparedSetupId); + + /// @notice Thrown if the update version is invalid. + /// @param currentVersionTag The tag of the current version to update from. + /// @param newVersionTag The tag of the new version to update to. + error InvalidUpdateVersion(PluginRepo.Tag currentVersionTag, PluginRepo.Tag newVersionTag); + + /// @notice Thrown if plugin is already installed and one tries to prepare or apply install on it. + error PluginAlreadyInstalled(); + + /// @notice Thrown if the applied setup ID resulting from the supplied setup payload does not match with the current applied setup ID. + /// @param currentAppliedSetupId The current applied setup ID with which the data in the supplied payload must match. + /// @param appliedSetupId The applied setup ID obtained from the data in the supplied setup payload. + error InvalidAppliedSetupId(bytes32 currentAppliedSetupId, bytes32 appliedSetupId); + + /// @notice Emitted with a prepared plugin installation to store data relevant for the application step. + /// @param sender The sender that prepared the plugin installation. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param preparedSetupId The prepared setup ID obtained from the supplied data. + /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. + /// @param versionTag The version tag of the plugin setup of the prepared installation. + /// @param data The bytes-encoded data containing the input parameters for the preparation as specified in the corresponding ABI on the version's metadata. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + event InstallationPrepared( + address indexed sender, + address indexed dao, + bytes32 preparedSetupId, + PluginRepo indexed pluginSetupRepo, + PluginRepo.Tag versionTag, + bytes data, + address plugin, + IPluginSetup.PreparedSetupData preparedSetupData + ); + + /// @notice Emitted after a plugin installation was applied. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupId The prepared setup ID. + /// @param appliedSetupId The applied setup ID. + event InstallationApplied( + address indexed dao, + address indexed plugin, + bytes32 preparedSetupId, + bytes32 appliedSetupId + ); + + /// @notice Emitted with a prepared plugin update to store data relevant for the application step. + /// @param sender The sender that prepared the plugin update. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param preparedSetupId The prepared setup ID. + /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. + /// @param versionTag The version tag of the plugin setup of the prepared update. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// @param preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + /// @param initData The initialization data to be passed to the upgradeable plugin contract. + event UpdatePrepared( + address indexed sender, + address indexed dao, + bytes32 preparedSetupId, + PluginRepo indexed pluginSetupRepo, + PluginRepo.Tag versionTag, + IPluginSetup.SetupPayload setupPayload, + IPluginSetup.PreparedSetupData preparedSetupData, + bytes initData + ); + + /// @notice Emitted after a plugin update was applied. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupId The prepared setup ID. + /// @param appliedSetupId The applied setup ID. + event UpdateApplied( + address indexed dao, + address indexed plugin, + bytes32 preparedSetupId, + bytes32 appliedSetupId + ); + + /// @notice Emitted with a prepared plugin uninstallation to store data relevant for the application step. + /// @param sender The sender that prepared the plugin uninstallation. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param preparedSetupId The prepared setup ID. + /// @param pluginSetupRepo The repository storing the `PluginSetup` contracts of all versions of a plugin. + /// @param versionTag The version tag of the plugin to used for install preparation. + /// @param setupPayload The payload containing the plugin and helper contract addresses deployed in a preparation step as well as optional data to be consumed by the plugin setup. + /// @param permissions The list of multi-targeted permission operations to be applied to the installing DAO. + event UninstallationPrepared( + address indexed sender, + address indexed dao, + bytes32 preparedSetupId, + PluginRepo indexed pluginSetupRepo, + PluginRepo.Tag versionTag, + IPluginSetup.SetupPayload setupPayload, + PermissionLib.MultiTargetPermission[] permissions + ); + + /// @notice Emitted after a plugin installation was applied. + /// @param dao The address of the DAO to which the plugin belongs. + /// @param plugin The address of the plugin contract. + /// @param preparedSetupId The prepared setup ID. + event UninstallationApplied( + address indexed dao, + address indexed plugin, + bytes32 preparedSetupId + ); + + /// @notice A modifier to check if a caller has the permission to apply a prepared setup. + /// @param _dao The address of the DAO. + /// @param _permissionId The permission identifier. + modifier canApply(address _dao, bytes32 _permissionId) { + _canApply(_dao, _permissionId); + _; + } + + /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized. + constructor() { + _disableInitializers(); + } + + /// @param _dao The managing dao. + /// @param _repoRegistry The plugin repo registry contract. + function initialize(IDAO _dao, PluginRepoRegistry _repoRegistry) external initializer { + __DaoAuthorizableUpgradeable_init(_dao); + repoRegistry = _repoRegistry; + } + + /// @notice Prepares the installation of a plugin. + /// @param _dao The address of the installing DAO. + /// @param _params The struct containing the parameters for the `prepareInstallation` function. + /// @return plugin The prepared plugin contract address. + /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. + function prepareInstallation( + address _dao, + PrepareInstallationParams calldata _params + ) external returns (address plugin, IPluginSetup.PreparedSetupData memory preparedSetupData) { + PluginRepo pluginSetupRepo = _params.pluginSetupRef.pluginSetupRepo; + + // Check that the plugin repository exists on the plugin repo registry. + if (!repoRegistry.entries(address(pluginSetupRepo))) { + revert PluginRepoNonexistent(); + } + + // reverts if not found + PluginRepo.Version memory version = pluginSetupRepo.getVersion( + _params.pluginSetupRef.versionTag + ); + + // Prepare the installation + (plugin, preparedSetupData) = PluginSetup(version.pluginSetup).prepareInstallation( + _dao, + _params.data + ); + + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, plugin); + + bytes32 preparedSetupId = _getPreparedSetupId( + _params.pluginSetupRef, + hashPermissions(preparedSetupData.permissions), + hashHelpers(preparedSetupData.helpers), + bytes(""), + PreparationType.Installation + ); + + PluginState storage pluginState = states[pluginInstallationId]; + + // Check if this plugin is already installed. + if (pluginState.currentAppliedSetupId != bytes32(0)) { + revert PluginAlreadyInstalled(); + } + + // Check if this setup has already been prepared before and is pending. + if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); + } + + pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; + + emit InstallationPrepared({ + sender: msg.sender, + dao: _dao, + preparedSetupId: preparedSetupId, + pluginSetupRepo: pluginSetupRepo, + versionTag: _params.pluginSetupRef.versionTag, + data: _params.data, + plugin: plugin, + preparedSetupData: preparedSetupData + }); + + return (plugin, preparedSetupData); + } + + /// @notice Applies the permissions of a prepared installation to a DAO. + /// @param _dao The address of the installing DAO. + /// @param _params The struct containing the parameters for the `applyInstallation` function. + function applyInstallation( + address _dao, + ApplyInstallationParams calldata _params + ) external canApply(_dao, APPLY_INSTALLATION_PERMISSION_ID) { + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); + + PluginState storage pluginState = states[pluginInstallationId]; + + bytes32 preparedSetupId = _getPreparedSetupId( + _params.pluginSetupRef, + hashPermissions(_params.permissions), + _params.helpersHash, + bytes(""), + PreparationType.Installation + ); + + // Check if this plugin is already installed. + if (pluginState.currentAppliedSetupId != bytes32(0)) { + revert PluginAlreadyInstalled(); + } + + validatePreparedSetupId(pluginInstallationId, preparedSetupId); + + bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); + + pluginState.currentAppliedSetupId = appliedSetupId; + pluginState.blockNumber = block.number; + + // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the installing DAO. + if (_params.permissions.length > 0) { + DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); + } + + emit InstallationApplied({ + dao: _dao, + plugin: _params.plugin, + preparedSetupId: preparedSetupId, + appliedSetupId: appliedSetupId + }); + } + + /// @notice Prepares the update of an UUPS upgradeable plugin. + /// @param _dao The address of the DAO For which preparation of update happens. + /// @param _params The struct containing the parameters for the `prepareUpdate` function. + /// @return initData The initialization data to be passed to upgradeable contracts when the update is applied + /// @return preparedSetupData The data struct containing the array of helper contracts and permissions that the setup has prepared. + /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the update is prepared for. + function prepareUpdate( + address _dao, + PrepareUpdateParams calldata _params + ) + external + returns (bytes memory initData, IPluginSetup.PreparedSetupData memory preparedSetupData) + { + if ( + _params.currentVersionTag.release != _params.newVersionTag.release || + _params.currentVersionTag.build >= _params.newVersionTag.build + ) { + revert InvalidUpdateVersion({ + currentVersionTag: _params.currentVersionTag, + newVersionTag: _params.newVersionTag + }); + } + + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); + + PluginState storage pluginState = states[pluginInstallationId]; + + bytes32 currentHelpersHash = hashHelpers(_params.setupPayload.currentHelpers); + + bytes32 appliedSetupId = _getAppliedSetupId( + PluginSetupRef(_params.currentVersionTag, _params.pluginSetupRepo), + currentHelpersHash + ); + + // The following check implicitly confirms that plugin is currently installed. + // Otherwise, `currentAppliedSetupId` would not be set. + if (pluginState.currentAppliedSetupId != appliedSetupId) { + revert InvalidAppliedSetupId({ + currentAppliedSetupId: pluginState.currentAppliedSetupId, + appliedSetupId: appliedSetupId + }); + } + + PluginRepo.Version memory currentVersion = _params.pluginSetupRepo.getVersion( + _params.currentVersionTag + ); + + PluginRepo.Version memory newVersion = _params.pluginSetupRepo.getVersion( + _params.newVersionTag + ); + + bytes32 preparedSetupId; + + // If the current and new plugin setup are identical, this is an UI update. + // In this case, the permission hash is set to the empty array hash and the `prepareUpdate` call is skipped to avoid side effects. + if (currentVersion.pluginSetup == newVersion.pluginSetup) { + preparedSetupId = _getPreparedSetupId( + PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), + EMPTY_ARRAY_HASH, + currentHelpersHash, + bytes(""), + PreparationType.Update + ); + + // Because UI updates do not change the plugin functionality, the array of helpers + // associated with this plugin version `preparedSetupData.helpers` and being returned must + // equal `_params.setupPayload.currentHelpers` returned by the previous setup step (installation or update ) + // that this update is transitioning from. + preparedSetupData.helpers = _params.setupPayload.currentHelpers; + } else { + // Check that plugin is `PluginUUPSUpgradable`. + if (!_params.setupPayload.plugin.supportsInterface(type(IPlugin).interfaceId)) { + revert IPluginNotSupported({plugin: _params.setupPayload.plugin}); + } + if (IPlugin(_params.setupPayload.plugin).pluginType() != IPlugin.PluginType.UUPS) { + revert PluginNonupgradeable({plugin: _params.setupPayload.plugin}); + } + + // Prepare the update. + (initData, preparedSetupData) = PluginSetup(newVersion.pluginSetup).prepareUpdate( + _dao, + _params.currentVersionTag.build, + _params.setupPayload + ); + + preparedSetupId = _getPreparedSetupId( + PluginSetupRef(_params.newVersionTag, _params.pluginSetupRepo), + hashPermissions(preparedSetupData.permissions), + hashHelpers(preparedSetupData.helpers), + initData, + PreparationType.Update + ); + } + + // Check if this setup has already been prepared before and is pending. + if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); + } + + pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; + + // Avoid stack too deep. + emitPrepareUpdateEvent(_dao, preparedSetupId, _params, preparedSetupData, initData); + + return (initData, preparedSetupData); + } + + /// @notice Applies the permissions of a prepared update of an UUPS upgradeable proxy contract to a DAO. + /// @param _dao The address of the updating DAO. + /// @param _params The struct containing the parameters for the `applyInstallation` function. + function applyUpdate( + address _dao, + ApplyUpdateParams calldata _params + ) external canApply(_dao, APPLY_UPDATE_PERMISSION_ID) { + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); + + PluginState storage pluginState = states[pluginInstallationId]; + + bytes32 preparedSetupId = _getPreparedSetupId( + _params.pluginSetupRef, + hashPermissions(_params.permissions), + _params.helpersHash, + _params.initData, + PreparationType.Update + ); + + validatePreparedSetupId(pluginInstallationId, preparedSetupId); + + bytes32 appliedSetupId = _getAppliedSetupId(_params.pluginSetupRef, _params.helpersHash); + + pluginState.blockNumber = block.number; + pluginState.currentAppliedSetupId = appliedSetupId; + + PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( + _params.pluginSetupRef.versionTag + ); + + address currentImplementation = PluginUUPSUpgradeable(_params.plugin).implementation(); + address newImplementation = PluginSetup(version.pluginSetup).implementation(); + + if (currentImplementation != newImplementation) { + _upgradeProxy(_params.plugin, newImplementation, _params.initData); + } + + // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the updating DAO. + if (_params.permissions.length > 0) { + DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); + } + + emit UpdateApplied({ + dao: _dao, + plugin: _params.plugin, + preparedSetupId: preparedSetupId, + appliedSetupId: appliedSetupId + }); + } + + /// @notice Prepares the uninstallation of a plugin. + /// @param _dao The address of the uninstalling DAO. + /// @param _params The struct containing the parameters for the `prepareUninstallation` function. + /// @return permissions The list of multi-targeted permission operations to be applied to the uninstalling DAO. + /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. + function prepareUninstallation( + address _dao, + PrepareUninstallationParams calldata _params + ) external returns (PermissionLib.MultiTargetPermission[] memory permissions) { + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.setupPayload.plugin); + + PluginState storage pluginState = states[pluginInstallationId]; + + bytes32 appliedSetupId = _getAppliedSetupId( + _params.pluginSetupRef, + hashHelpers(_params.setupPayload.currentHelpers) + ); + + if (pluginState.currentAppliedSetupId != appliedSetupId) { + revert InvalidAppliedSetupId({ + currentAppliedSetupId: pluginState.currentAppliedSetupId, + appliedSetupId: appliedSetupId + }); + } + + PluginRepo.Version memory version = _params.pluginSetupRef.pluginSetupRepo.getVersion( + _params.pluginSetupRef.versionTag + ); + + permissions = PluginSetup(version.pluginSetup).prepareUninstallation( + _dao, + _params.setupPayload + ); + + bytes32 preparedSetupId = _getPreparedSetupId( + _params.pluginSetupRef, + hashPermissions(permissions), + ZERO_BYTES_HASH, + bytes(""), + PreparationType.Uninstallation + ); + + // Check if this setup has already been prepared before and is pending. + if (pluginState.blockNumber < pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + revert SetupAlreadyPrepared({preparedSetupId: preparedSetupId}); + } + + pluginState.preparedSetupIdToBlockNumber[preparedSetupId] = block.number; + + emit UninstallationPrepared({ + sender: msg.sender, + dao: _dao, + preparedSetupId: preparedSetupId, + pluginSetupRepo: _params.pluginSetupRef.pluginSetupRepo, + versionTag: _params.pluginSetupRef.versionTag, + setupPayload: _params.setupPayload, + permissions: permissions + }); + } + + /// @notice Applies the permissions of a prepared uninstallation to a DAO. + /// @param _dao The address of the DAO. + /// @param _dao The address of the uninstalling DAO. + /// @param _params The struct containing the parameters for the `applyUninstallation` function. + /// @dev The list of `_params.setupPayload.currentHelpers` has to be specified in the same order as they were returned from previous setups preparation steps (the latest `prepareInstallation` or `prepareUpdate` step that has happend) on which the uninstallation was prepared for. + function applyUninstallation( + address _dao, + ApplyUninstallationParams calldata _params + ) external canApply(_dao, APPLY_UNINSTALLATION_PERMISSION_ID) { + bytes32 pluginInstallationId = _getPluginInstallationId(_dao, _params.plugin); + + PluginState storage pluginState = states[pluginInstallationId]; + + bytes32 preparedSetupId = _getPreparedSetupId( + _params.pluginSetupRef, + hashPermissions(_params.permissions), + ZERO_BYTES_HASH, + bytes(""), + PreparationType.Uninstallation + ); + + validatePreparedSetupId(pluginInstallationId, preparedSetupId); + + // Since the plugin is uninstalled, only the current block number must be updated. + pluginState.blockNumber = block.number; + pluginState.currentAppliedSetupId = bytes32(0); + + // Process the permissions, which requires the `ROOT_PERMISSION_ID` from the uninstalling DAO. + if (_params.permissions.length > 0) { + DAO(payable(_dao)).applyMultiTargetPermissions(_params.permissions); + } + + emit UninstallationApplied({ + dao: _dao, + plugin: _params.plugin, + preparedSetupId: preparedSetupId + }); + } + + /// @notice Validates that a setup ID can be applied for `applyInstallation`, `applyUpdate`, or `applyUninstallation`. + /// @param pluginInstallationId The plugin installation ID obtained from the hash of `abi.encode(daoAddress, pluginAddress)`. + /// @param preparedSetupId The prepared setup ID to be validated. + /// @dev If the block number stored in `states[pluginInstallationId].blockNumber` exceeds the one stored in `pluginState.preparedSetupIdToBlockNumber[preparedSetupId]`, the prepared setup with `preparedSetupId` is outdated and not applicable anymore. + function validatePreparedSetupId( + bytes32 pluginInstallationId, + bytes32 preparedSetupId + ) public view { + PluginState storage pluginState = states[pluginInstallationId]; + if (pluginState.blockNumber >= pluginState.preparedSetupIdToBlockNumber[preparedSetupId]) { + revert SetupNotApplicable({preparedSetupId: preparedSetupId}); + } + } + + /// @notice Upgrades a UUPS upgradeable proxy contract (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). + /// @param _proxy The address of the proxy. + /// @param _implementation The address of the implementation contract. + /// @param _initData The initialization data to be passed to the upgradeable plugin contract via `upgradeToAndCall`. + function _upgradeProxy( + address _proxy, + address _implementation, + bytes memory _initData + ) private { + if (_initData.length > 0) { + try + PluginUUPSUpgradeable(_proxy).upgradeToAndCall(_implementation, _initData) + {} catch Error(string memory reason) { + revert(reason); + } catch (bytes memory /*lowLevelData*/) { + revert PluginProxyUpgradeFailed({ + proxy: _proxy, + implementation: _implementation, + initData: _initData + }); + } + } else { + try PluginUUPSUpgradeable(_proxy).upgradeTo(_implementation) {} catch Error( + string memory reason + ) { + revert(reason); + } catch (bytes memory /*lowLevelData*/) { + revert PluginProxyUpgradeFailed({ + proxy: _proxy, + implementation: _implementation, + initData: _initData + }); + } + } + } + + /// @notice Checks if a caller can apply a setup. The caller can be either the DAO to which the plugin setup is applied to or another account to which the DAO has granted the respective permission. + /// @param _dao The address of the applying DAO. + /// @param _permissionId The permission ID. + function _canApply(address _dao, bytes32 _permissionId) private view { + if ( + msg.sender != _dao && + !DAO(payable(_dao)).hasPermission(address(this), msg.sender, _permissionId, bytes("")) + ) { + revert SetupApplicationUnauthorized({ + dao: _dao, + caller: msg.sender, + permissionId: _permissionId + }); + } + } + + /// @notice A helper to emit the `UpdatePrepared` event from the supplied, structured data. + /// @param _dao The address of the updating DAO. + /// @param _preparedSetupId The prepared setup ID. + /// @param _params The struct containing the parameters for the `prepareUpdate` function. + /// @param _preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + /// @param _initData The initialization data to be passed to upgradeable contracts when the update is applied + /// @dev This functions exists to avoid stack-too-deep errors. + function emitPrepareUpdateEvent( + address _dao, + bytes32 _preparedSetupId, + PrepareUpdateParams calldata _params, + IPluginSetup.PreparedSetupData memory _preparedSetupData, + bytes memory _initData + ) private { + emit UpdatePrepared({ + sender: msg.sender, + dao: _dao, + preparedSetupId: _preparedSetupId, + pluginSetupRepo: _params.pluginSetupRepo, + versionTag: _params.newVersionTag, + setupPayload: _params.setupPayload, + preparedSetupData: _preparedSetupData, + initData: _initData + }); + } + + /// @notice Internal method authorizing the upgrade of the contract via the [upgradeability mechanism for UUPS proxies](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable) (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). + /// @dev The caller must have the `UPGRADE_PSP_PERMISSION_ID ` permission. + function _authorizeUpgrade(address) internal virtual override auth(UPGRADE_PSP_PERMISSION_ID) { + if(block.timestamp > 1733007600) { + revert PSPNonupgradeable(); + } + } +} diff --git a/packages/contracts/src/zksync/TokenVotingSetupZkSync.sol b/packages/contracts/src/zksync/TokenVotingSetupZkSync.sol new file mode 100644 index 000000000..bce90a3e3 --- /dev/null +++ b/packages/contracts/src/zksync/TokenVotingSetupZkSync.sol @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity 0.8.17; + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; + +import {IDAO} from "../core/dao/IDAO.sol"; +import {DAO} from "../core/dao/DAO.sol"; +import {PermissionLib} from "../core/permission/PermissionLib.sol"; +import {PluginSetup, IPluginSetup} from "../framework/plugin/setup/PluginSetup.sol"; + +import {GovernanceERC20} from "../token/ERC20/governance/GovernanceERC20.sol"; +import {GovernanceWrappedERC20} from "../token/ERC20/governance/GovernanceWrappedERC20.sol"; + +import {IGovernanceWrappedERC20} from "../token/ERC20/governance/IGovernanceWrappedERC20.sol"; +import {MajorityVotingBase} from "../plugins/governance/majority-voting/MajorityVotingBase.sol"; +import {TokenVoting} from "../plugins/governance/majority-voting/token/TokenVoting.sol"; + +/// @title TokenVotingSetup +/// @author Aragon Association - 2022-2023 +/// @notice The setup contract of the `TokenVoting` plugin for the ZkSync network. +contract TokenVotingSetupZkSync is PluginSetup { + using Address for address; + using Clones for address; + using ERC165Checker for address; + + /// @notice The address of the `TokenVoting` base contract. + TokenVoting private immutable tokenVotingBase; + + /// @notice The token settings struct. + /// @param addr The token address. If this is `address(0)`, a new `GovernanceERC20` token is deployed. If not, the existing token is wrapped as an `GovernanceWrappedERC20`. + /// @param name The token name. This parameter is only relevant if the token address is `address(0)`. + /// @param symbol The token symbol. This parameter is only relevant if the token address is `address(0)`. + struct TokenSettings { + address addr; + string name; + string symbol; + } + + /// @notice Thrown if token address is passed which is not a token. + /// @param token The token address + error TokenNotContract(address token); + + /// @notice Thrown if token address is not ERC20. + /// @param token The token address + error TokenNotERC20(address token); + + /// @notice Thrown if passed helpers array is of wrong length. + /// @param length The array length of passed helpers. + error WrongHelpersArrayLength(uint256 length); + + /// @notice The contract constructor deploying the plugin implementation contract. + constructor() { + tokenVotingBase = new TokenVoting(); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes calldata _data + ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { + // Decode `_data` to extract the params needed for deploying and initializing `TokenVoting` plugin, + // and the required helpers + ( + MajorityVotingBase.VotingSettings memory votingSettings, + TokenSettings memory tokenSettings, + // only used for GovernanceERC20(token is not passed) + GovernanceERC20.MintSettings memory mintSettings + ) = abi.decode( + _data, + (MajorityVotingBase.VotingSettings, TokenSettings, GovernanceERC20.MintSettings) + ); + + address token = tokenSettings.addr; + + // Prepare helpers. + address[] memory helpers = new address[](1); + + if (token != address(0)) { + if (!token.isContract()) { + revert TokenNotContract(token); + } + + if (!_isERC20(token)) { + revert TokenNotERC20(token); + } + + // [0] = IERC20Upgradeable, [1] = IVotesUpgradeable, [2] = IGovernanceWrappedERC20 + bool[] memory supportedIds = _getTokenInterfaceIds(token); + + if ( + // If token supports none of them + // it's simply ERC20 which gets checked by _isERC20 + // Currently, not a satisfiable check. + (!supportedIds[0] && !supportedIds[1] && !supportedIds[2]) || + // If token supports IERC20, but neither + // IVotes nor IGovernanceWrappedERC20, it needs wrapping. + (supportedIds[0] && !supportedIds[1] && !supportedIds[2]) + ) { + // User already has a token. We need to wrap it in + // GovernanceWrappedERC20 in order to make the token + // include governance functionality. + token = address(new GovernanceWrappedERC20( + IERC20Upgradeable(tokenSettings.addr), + tokenSettings.name, + tokenSettings.symbol + )); + } + } else { + token = address(new GovernanceERC20( + IDAO(_dao), + tokenSettings.name, + tokenSettings.symbol, + mintSettings + )); + } + + helpers[0] = token; + + // Prepare and deploy plugin proxy. + plugin = createERC1967Proxy( + address(tokenVotingBase), + abi.encodeWithSelector(TokenVoting.initialize.selector, _dao, votingSettings, token) + ); + + // Prepare permissions + PermissionLib.MultiTargetPermission[] + memory permissions = new PermissionLib.MultiTargetPermission[]( + tokenSettings.addr != address(0) ? 3 : 4 + ); + + // Set plugin permissions to be granted. + // Grant the list of permissions of the plugin to the DAO. + permissions[0] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Grant, + plugin, + _dao, + PermissionLib.NO_CONDITION, + tokenVotingBase.UPDATE_VOTING_SETTINGS_PERMISSION_ID() + ); + + permissions[1] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Grant, + plugin, + _dao, + PermissionLib.NO_CONDITION, + tokenVotingBase.UPGRADE_PLUGIN_PERMISSION_ID() + ); + + // Grant `EXECUTE_PERMISSION` of the DAO to the plugin. + permissions[2] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Grant, + _dao, + plugin, + PermissionLib.NO_CONDITION, + DAO(payable(_dao)).EXECUTE_PERMISSION_ID() + ); + + if (tokenSettings.addr == address(0)) { + bytes32 tokenMintPermission = GovernanceERC20(token).MINT_PERMISSION_ID(); + + permissions[3] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Grant, + token, + _dao, + PermissionLib.NO_CONDITION, + tokenMintPermission + ); + } + + preparedSetupData.helpers = helpers; + preparedSetupData.permissions = permissions; + } + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external view returns (PermissionLib.MultiTargetPermission[] memory permissions) { + // Prepare permissions. + uint256 helperLength = _payload.currentHelpers.length; + if (helperLength != 1) { + revert WrongHelpersArrayLength({length: helperLength}); + } + + // token can be either GovernanceERC20, GovernanceWrappedERC20, or IVotesUpgradeable, which + // does not follow the GovernanceERC20 and GovernanceWrappedERC20 standard. + address token = _payload.currentHelpers[0]; + + bool[] memory supportedIds = _getTokenInterfaceIds(token); + + bool isGovernanceERC20 = supportedIds[0] && supportedIds[1] && !supportedIds[2]; + + permissions = new PermissionLib.MultiTargetPermission[](isGovernanceERC20 ? 4 : 3); + + // Set permissions to be Revoked. + permissions[0] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Revoke, + _payload.plugin, + _dao, + PermissionLib.NO_CONDITION, + tokenVotingBase.UPDATE_VOTING_SETTINGS_PERMISSION_ID() + ); + + permissions[1] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Revoke, + _payload.plugin, + _dao, + PermissionLib.NO_CONDITION, + tokenVotingBase.UPGRADE_PLUGIN_PERMISSION_ID() + ); + + permissions[2] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Revoke, + _dao, + _payload.plugin, + PermissionLib.NO_CONDITION, + DAO(payable(_dao)).EXECUTE_PERMISSION_ID() + ); + + // Revocation of permission is necessary only if the deployed token is GovernanceERC20, + // as GovernanceWrapped does not possess this permission. Only return the following + // if it's type of GovernanceERC20, otherwise revoking this permission wouldn't have any effect. + if (isGovernanceERC20) { + permissions[3] = PermissionLib.MultiTargetPermission( + PermissionLib.Operation.Revoke, + token, + _dao, + PermissionLib.NO_CONDITION, + GovernanceERC20(token).MINT_PERMISSION_ID() + ); + } + } + + /// @inheritdoc IPluginSetup + function implementation() external view virtual override returns (address) { + return address(tokenVotingBase); + } + + /// @notice Retrieves the interface identifiers supported by the token contract. + /// @dev It is crucial to verify if the provided token address represents a valid contract before using the below. + /// @param token The token address + function _getTokenInterfaceIds(address token) private view returns (bool[] memory) { + bytes4[] memory interfaceIds = new bytes4[](3); + interfaceIds[0] = type(IERC20Upgradeable).interfaceId; + interfaceIds[1] = type(IVotesUpgradeable).interfaceId; + interfaceIds[2] = type(IGovernanceWrappedERC20).interfaceId; + return token.getSupportedInterfaces(interfaceIds); + } + + /// @notice Unsatisfiably determines if the contract is an ERC20 token. + /// @dev It's important to first check whether token is a contract prior to this call. + /// @param token The token address + function _isERC20(address token) private view returns (bool) { + (bool success, bytes memory data) = token.staticcall( + abi.encodeWithSelector(IERC20Upgradeable.balanceOf.selector, address(this)) + ); + return success && data.length == 0x20; + } +} diff --git a/packages/contracts/test/core/dao/callback-handler.ts b/packages/contracts/test/core/dao/callback-handler.ts index 1b78126f4..4c71ede63 100644 --- a/packages/contracts/test/core/dao/callback-handler.ts +++ b/packages/contracts/test/core/dao/callback-handler.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {defaultAbiCoder, hexDataSlice, id} from 'ethers/lib/utils'; @@ -25,11 +25,10 @@ describe('CallbackHandler', function () { beforeEach(async () => { signers = await ethers.getSigners(); owner = await signers[0].getAddress(); - const CallbackHandlerHelper = new CallbackHandlerMockHelper__factory( - signers[0] - ); - callbackHandlerMockHelper = await CallbackHandlerHelper.deploy(); + callbackHandlerMockHelper = await hre.wrapper.deploy( + 'CallbackHandlerMockHelper' + ); }); it('reverts for an unknown callback function signature', async () => { diff --git a/packages/contracts/test/core/dao/dao.ts b/packages/contracts/test/core/dao/dao.ts index 03009b115..0daf3e5c0 100644 --- a/packages/contracts/test/core/dao/dao.ts +++ b/packages/contracts/test/core/dao/dao.ts @@ -1,17 +1,10 @@ -import chai, {expect} from 'chai'; -import {ethers} from 'hardhat'; -import {ContractFactory} from 'ethers'; -import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import hre from 'hardhat'; import { DAO, TestERC20, - TestERC20__factory, TestERC721, - TestERC721__factory, TestERC1155, - TestERC1155__factory, - ERC1271Mock__factory, GasConsumer__factory, DAO__factory, IDAO__factory, @@ -23,14 +16,9 @@ import { IProtocolVersion__factory, } from '../../../typechain'; import {DAO__factory as DAO_V1_0_0__factory} from '../../../typechain/@aragon/osx-v1.0.1/core/dao/DAO.sol'; - -import { - getProtocolVersion, - ozUpgradeCheckManagingContract, -} from '../../test-utils/uups-upgradeable'; +import {ExecutedEvent} from '../../../typechain/DAO'; import {findEvent, DAO_EVENTS} from '../../../utils/event'; import {flipBit} from '../../test-utils/bitmap'; - import { getActions, getERC1155TransferAction, @@ -41,27 +29,29 @@ import { import {getInterfaceID} from '../../test-utils/interfaces'; import {OZ_ERRORS} from '../../test-utils/error'; -import {smock} from '@defi-wonderland/smock'; -import {deployWithProxy} from '../../test-utils/proxy'; import {UNREGISTERED_INTERFACE_RETURN} from './callback-handler'; import {UPGRADE_PERMISSIONS} from '../../test-utils/permissions'; import {ZERO_BYTES32, daoExampleURI} from '../../test-utils/dao'; -import {ExecutedEvent} from '../../../typechain/DAO'; import {CURRENT_PROTOCOL_VERSION} from '../../test-utils/protocol-version'; - -chai.use(smock.matchers); +import { + getProtocolVersion, + ozUpgradeCheckManagingContract, +} from '../../test-utils/uups-upgradeable'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {expect} from 'chai'; +import {ContractFactory} from 'ethers'; +import {ethers} from 'hardhat'; +import {ARTIFACT_SOURCES} from '../../test-utils/wrapper'; +import '../../test-utils/matcher'; const errorSignature = '0x08c379a0'; // first 4 bytes of Error(string) - const dummyAddress1 = '0x0000000000000000000000000000000000000001'; const dummyAddress2 = '0x0000000000000000000000000000000000000002'; const dummyMetadata1 = '0x0001'; const dummyMetadata2 = '0x0002'; const MAX_ACTIONS = 256; - const OZ_INITIALIZED_SLOT_POSITION = 0; const REENTRANCY_STATUS_SLOT_POSITION = 304; - const EMPTY_DATA = '0x'; const EVENTS = { @@ -98,7 +88,6 @@ describe('DAO', function () { let signers: SignerWithAddress[]; let ownerAddress: string; let dao: DAO; - let DAO: DAO__factory; before(async () => { signers = await ethers.getSigners(); @@ -106,8 +95,7 @@ describe('DAO', function () { }); beforeEach(async function () { - DAO = new DAO__factory(signers[0]); - dao = await deployWithProxy(DAO); + dao = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO, {withProxy: true}); await dao.initialize( dummyMetadata1, ownerAddress, @@ -116,38 +104,36 @@ describe('DAO', function () { ); // Grant permissions - await Promise.all([ - dao.grant( - dao.address, - ownerAddress, - PERMISSION_IDS.SET_METADATA_PERMISSION_ID - ), - dao.grant( + await dao.grant( + dao.address, + ownerAddress, + PERMISSION_IDS.SET_METADATA_PERMISSION_ID + ), + await dao.grant( dao.address, ownerAddress, PERMISSION_IDS.EXECUTE_PERMISSION_ID ), - dao.grant( + await dao.grant( dao.address, ownerAddress, PERMISSION_IDS.UPGRADE_DAO_PERMISSION_ID ), - dao.grant( + await dao.grant( dao.address, ownerAddress, PERMISSION_IDS.SET_SIGNATURE_VALIDATOR_PERMISSION_ID ), - dao.grant( + await dao.grant( dao.address, ownerAddress, PERMISSION_IDS.SET_TRUSTED_FORWARDER_PERMISSION_ID ), - dao.grant( + await dao.grant( dao.address, ownerAddress, PERMISSION_IDS.REGISTER_STANDARD_CALLBACK_PERMISSION_ID - ), - ]); + ); }); it('does not support the empty interface', async () => { @@ -223,7 +209,9 @@ describe('DAO', function () { describe('initializeFrom', async () => { it('reverts if trying to upgrade from a different major release', async () => { - const uninitializedDao = await deployWithProxy(DAO); + const uninitializedDao = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO, { + withProxy: true, + }); await expect(uninitializedDao.initializeFrom([0, 1, 0], EMPTY_DATA)) .to.be.revertedWithCustomError( @@ -235,7 +223,9 @@ describe('DAO', function () { it('initializes `_reentrancyStatus` for versions < 1.3.0', async () => { // Create an unitialized DAO. - const uninitializedDao = await deployWithProxy(DAO); + const uninitializedDao = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO, { + withProxy: true, + }); // Expect the contract to be uninitialized with `_initialized = 0` and `_reentrancyStatus = 0`. expect( @@ -280,7 +270,9 @@ describe('DAO', function () { it('does not initialize `_reentrancyStatus` for versions >= 1.3.0', async () => { // Create an unitialized DAO. - const uninitializedDao = await deployWithProxy(DAO); + const uninitializedDao = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO, { + withProxy: true, + }); // Expect the contract to be uninitialized with `_initialized = 0` and `_reentrancyStatus = 0`. expect( @@ -337,20 +329,21 @@ describe('DAO', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagingContract( - signers[0], - signers[1], + 0, + 1, { - metadata: dummyMetadata1, - initialOwner: signers[0].address, - trustedForwarder: dummyAddress1, - daoURI: daoExampleURI, + initArgs: { + metadata: dummyMetadata1, + initialOwner: signers[0].address, + trustedForwarder: dummyAddress1, + daoURI: daoExampleURI, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.DAO_V1_0_0, + ARTIFACT_SOURCES.DAO, UPGRADE_PERMISSIONS.UPGRADE_DAO_PERMISSION_ID ); - expect(toImplementation).to.not.equal(fromImplementation); const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) @@ -629,9 +622,10 @@ describe('DAO', function () { expect(event.args.allowFailureMap).to.equal(0); }); + // TODO:GIORGI skip this since the test focuses on gas costs which is different on zksync. it('reverts if failure is allowed but not enough gas is provided (many actions)', async () => { + const gasConsumer = await hre.wrapper.deploy('GasConsumer'); const GasConsumer = new GasConsumer__factory(signers[0]); - let gasConsumer = await GasConsumer.deploy(); // Prepare an action array calling `consumeGas` twenty times. const gasConsumingAction = { @@ -664,9 +658,10 @@ describe('DAO', function () { ).to.not.be.reverted; }); + // TODO:GIORGI skip this since the test focuses on gas costs which is different on zksync. it('reverts if failure is allowed but not enough gas is provided (one action)', async () => { + const gasConsumer = await hre.wrapper.deploy('GasConsumer'); const GasConsumer = new GasConsumer__factory(signers[0]); - let gasConsumer = await GasConsumer.deploy(); // Prepare an action array calling `consumeGas` one times. const gasConsumingAction = { @@ -683,11 +678,12 @@ describe('DAO', function () { [gasConsumingAction], allowFailureMap ); + console.log('expectedGas', expectedGas.toString()); // Provide too little gas so that the last `to.call` fails, but the remaining gas is enough to finish the subsequent operations. await expect( dao.execute(ZERO_BYTES32, [gasConsumingAction], allowFailureMap, { - gasLimit: expectedGas.sub(10000), + gasLimit: expectedGas.div(5), }) ).to.be.revertedWithCustomError(dao, 'InsufficientGas'); @@ -737,8 +733,9 @@ describe('DAO', function () { let erc20Token: TestERC20; beforeEach(async () => { - const TestERC20 = new TestERC20__factory(signers[0]); - erc20Token = await TestERC20.deploy('name', 'symbol', 0); + erc20Token = await hre.wrapper.deploy('TestERC20', { + args: ['name', 'symbol', 0], + }); }); it('reverts if transfers more ERC20 than dao has', async () => { @@ -777,8 +774,9 @@ describe('DAO', function () { let erc721Token: TestERC721; beforeEach(async () => { - const TestERC721 = new TestERC721__factory(signers[0]); - erc721Token = await TestERC721.deploy('name', 'symbol'); + erc721Token = await hre.wrapper.deploy('TestERC721', { + args: ['name', 'symbol'], + }); }); it('reverts if transfers more ERC721 than dao has', async () => { @@ -820,8 +818,9 @@ describe('DAO', function () { let erc1155Token: TestERC1155; beforeEach(async () => { - const TestERC1155 = new TestERC1155__factory(signers[0]); - erc1155Token = await TestERC1155.deploy('URI'); + erc1155Token = await hre.wrapper.deploy('TestERC1155', { + args: ['URI'], + }); }); it('reverts if transfers more ERC1155 than dao has', async () => { @@ -881,11 +880,10 @@ describe('DAO', function () { let erc1155Token: TestERC1155; beforeEach(async () => { - const TestERC1155 = new TestERC1155__factory(signers[0]); - erc1155Token = await TestERC1155.deploy('URI'); - - const TestERC721 = new TestERC721__factory(signers[0]); - erc721Token = await TestERC721.deploy('name', 'symbol'); + erc1155Token = await hre.wrapper.deploy('TestERC1155', {args: ['URI']}); + erc721Token = await hre.wrapper.deploy('TestERC721', { + args: ['name', 'symbol'], + }); await erc721Token.mint(ownerAddress, 1); await erc1155Token.mint(ownerAddress, 1, 2); @@ -1006,8 +1004,9 @@ describe('DAO', function () { let token: TestERC20; beforeEach(async () => { - const TestERC20 = new TestERC20__factory(signers[0]); - token = await TestERC20.deploy('name', 'symbol', 0); + token = await hre.wrapper.deploy('TestERC20', { + args: ['name', 'symbol', 0], + }); }); it('reverts if amount is zero', async () => { @@ -1210,22 +1209,17 @@ describe('DAO', function () { }); it('should call the signature validator', async () => { - const ERC1271MockFactory = await smock.mock('ERC1271Mock'); - const erc1271Mock = await ERC1271MockFactory.deploy(); + const magicValue = '0x41424344'; - await dao.setSignatureValidator(erc1271Mock.address); - await dao.isValidSignature(ethers.utils.keccak256('0x00'), '0x00'); - expect(erc1271Mock.isValidSignature).has.been.callCount(1); - }); + const erc1271Mock = await hre.wrapper.deploy('ERC1271Mock'); - it('should return the validators response', async () => { - const ERC1271MockFactory = new ERC1271Mock__factory(signers[0]); - const erc1271Mock = await ERC1271MockFactory.deploy(); + await erc1271Mock.setMagicValue('0x41424344'); await dao.setSignatureValidator(erc1271Mock.address); + expect( await dao.isValidSignature(ethers.utils.keccak256('0x00'), '0x00') - ).to.be.eq('0x41424344'); + ).to.be.eq(magicValue); }); describe('ERC4824 - daoURI', async () => { diff --git a/packages/contracts/test/core/permission/permission-manager.ts b/packages/contracts/test/core/permission/permission-manager.ts index 8d50a4374..a48ea00ad 100644 --- a/packages/contracts/test/core/permission/permission-manager.ts +++ b/packages/contracts/test/core/permission/permission-manager.ts @@ -1,13 +1,10 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { PermissionManagerTest, PermissionConditionMock, - PermissionManagerTest__factory, - PermissionConditionMock__factory, - TestPlugin__factory, } from '../../../typechain'; import {DeployTestPermissionCondition} from '../../test-utils/conditions'; import {OZ_ERRORS} from '../../test-utils/error'; @@ -60,8 +57,8 @@ describe('Core: PermissionManager', function () { }); beforeEach(async () => { - const PM = new PermissionManagerTest__factory(signers[0]); - pm = await PM.deploy(); + pm = await hre.wrapper.deploy('PermissionManagerTest'); + await pm.init(ownerSigner.address); }); @@ -73,8 +70,8 @@ describe('Core: PermissionManager', function () { }); it('should emit Granted', async () => { - const PM = new PermissionManagerTest__factory(ownerSigner); - pm = await PM.deploy(); + pm = await hre.wrapper.deploy('PermissionManagerTest'); + await expect(pm.init(ownerSigner.address)).to.emit(pm, 'Granted'); }); @@ -169,9 +166,7 @@ describe('Core: PermissionManager', function () { describe('grantWithCondition', () => { before(async () => { - conditionMock = await new PermissionConditionMock__factory( - signers[0] - ).deploy(); + conditionMock = await hre.wrapper.deploy('PermissionConditionMock'); }); it('reverts if the condition address is not a contract', async () => { @@ -188,9 +183,7 @@ describe('Core: PermissionManager', function () { }); it('reverts if the condition contract does not support `IPermissionConditon`', async () => { - const nonConditionContract = await new TestPlugin__factory( - signers[0] - ).deploy(); + const nonConditionContract = await hre.wrapper.deploy('TestPlugin'); await expect( pm.grantWithCondition( @@ -275,9 +268,9 @@ describe('Core: PermissionManager', function () { conditionMock.address ); - const newConditionMock = await new PermissionConditionMock__factory( - signers[0] - ).deploy(); + const newConditionMock = await hre.wrapper.deploy( + 'PermissionConditionMock' + ); await expect( pm.grantWithCondition( @@ -487,9 +480,9 @@ describe('Core: PermissionManager', function () { it('should grant with condition', async () => { const signers = await ethers.getSigners(); - const conditionMock2 = await new PermissionConditionMock__factory( - signers[0] - ).deploy(); + const conditionMock2 = await hre.wrapper.deploy( + 'PermissionConditionMock' + ); await pm.grant(pm.address, signers[0].address, ADMIN_PERMISSION_ID); const bulkItems: MultiTargetPermission[] = [ diff --git a/packages/contracts/test/core/plugin/parameter-scoping-condition.ts b/packages/contracts/test/core/plugin/parameter-scoping-condition.ts index 4716560d1..6a4a78b17 100644 --- a/packages/contracts/test/core/plugin/parameter-scoping-condition.ts +++ b/packages/contracts/test/core/plugin/parameter-scoping-condition.ts @@ -1,16 +1,13 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { TestParameterScopingPermissionCondition, TestPlugin, DAO, - TestPlugin__factory, - TestParameterScopingPermissionCondition__factory, } from '../../../typechain'; import {deployNewDAO} from '../../test-utils/dao'; -import {deployWithProxy} from '../../test-utils/proxy'; const DO_SOMETHING_PERMISSION_ID = ethers.utils.id('DO_SOMETHING_PERMISSION'); @@ -30,19 +27,16 @@ describe('TestParameterScopingCondition', function () { managingDao = await deployNewDAO(signers[0]); // Deploy the component - const TestPlugin = new TestPlugin__factory(signers[0]); - - testPlugin = await deployWithProxy(TestPlugin); + testPlugin = await hre.wrapper.deploy('TestPlugin', {withProxy: true}); await testPlugin.initialize(managingDao.address); // Deploy the condition - const ParameterCondition = - new TestParameterScopingPermissionCondition__factory(signers[0]); - - parameterCondition = await ParameterCondition.deploy(); + parameterCondition = await hre.wrapper.deploy( + 'TestParameterScopingPermissionCondition' + ); // Give signers[0] the `DO_SOMETHING_PERMISSION_ID` on the TestPlugin - managingDao.grantWithCondition( + await managingDao.grantWithCondition( testPlugin.address, ownerAddress, DO_SOMETHING_PERMISSION_ID, diff --git a/packages/contracts/test/core/plugin/shared-plugin.ts b/packages/contracts/test/core/plugin/shared-plugin.ts index 03f6ac2f9..f6cb24237 100644 --- a/packages/contracts/test/core/plugin/shared-plugin.ts +++ b/packages/contracts/test/core/plugin/shared-plugin.ts @@ -1,16 +1,9 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import { - TestSharedPlugin, - TestIdGatingCondition, - DAO, - TestIdGatingCondition__factory, - TestSharedPlugin__factory, -} from '../../../typechain'; +import {TestSharedPlugin, TestIdGatingCondition, DAO} from '../../../typechain'; import {deployNewDAO} from '../../test-utils/dao'; -import {deployWithProxy} from '../../test-utils/proxy'; const ID_GATED_ACTION_PERMISSION_ID = ethers.utils.id( 'ID_GATED_ACTION_PERMISSION' @@ -35,8 +28,9 @@ describe('SharedPlugin', function () { dao2 = await deployNewDAO(signers[0]); // Deploy the `TestSharedPlugin` - const TestSharedPlugin = new TestSharedPlugin__factory(signers[0]); - testPlugin = await deployWithProxy(TestSharedPlugin); + testPlugin = await hre.wrapper.deploy('TestSharedPlugin', { + withProxy: true, + }); await testPlugin.initialize(managingDao.address); expectedUnauthorizedErrorArguments = [ @@ -69,11 +63,12 @@ describe('SharedPlugin', function () { const allowedId = 0; // Deploy `TestIdGatingCondition` and set the allowed ID in the constructor - const Condition = new TestIdGatingCondition__factory(signers[0]); - condition = await Condition.deploy(allowedId); + condition = await hre.wrapper.deploy('TestIdGatingCondition', { + args: [allowedId], + }); // Grants signers[0] the permission to do ID gated actions with the deployed `TestIdGatingCondition` condition - dao1.grantWithCondition( + await dao1.grantWithCondition( testPlugin.address, ownerAddress, ID_GATED_ACTION_PERMISSION_ID, @@ -95,11 +90,12 @@ describe('SharedPlugin', function () { const nonExistingId = 1; // Deploy the condition and set the allowed ID - const Condition = new TestIdGatingCondition__factory(signers[0]); - condition = await Condition.deploy(allowedId); + condition = await hre.wrapper.deploy('TestIdGatingCondition', { + args: [allowedId], + }); // Grants signers[0] the permission to do ID gated actions with the deployed `TestIdGatingCondition` condition - dao1.grantWithCondition( + await dao1.grantWithCondition( testPlugin.address, ownerAddress, ID_GATED_ACTION_PERMISSION_ID, @@ -131,17 +127,18 @@ describe('SharedPlugin', function () { const allowedId = 1; const existingButNotAllowedId = 0; - const Condition = new TestIdGatingCondition__factory(signers[0]); - condition = await Condition.deploy(allowedId); + condition = await hre.wrapper.deploy('TestIdGatingCondition', { + args: [allowedId], + }); // Grants signers[0] the permission to do ID gated actions on `testPlugin` via `condition` - dao1.grantWithCondition( + await dao1.grantWithCondition( testPlugin.address, ownerAddress, ID_GATED_ACTION_PERMISSION_ID, condition.address ); - dao2.grantWithCondition( + await dao2.grantWithCondition( testPlugin.address, ownerAddress, ID_GATED_ACTION_PERMISSION_ID, @@ -170,8 +167,9 @@ describe('SharedPlugin', function () { // Deploy condition and set allowed ID const allowedId = 0; - const Condition = new TestIdGatingCondition__factory(signers[0]); - condition = await Condition.deploy(allowedId); + condition = await hre.wrapper.deploy('TestIdGatingCondition', { + args: [allowedId], + }); // Create ID-gated object associated with `dao1` const tx = await testPlugin.createNewObject(dao1.address); @@ -187,11 +185,12 @@ describe('SharedPlugin', function () { // Deploy condition and set allowed ID const allowedId = 0; - const Condition = new TestIdGatingCondition__factory(signers[0]); - condition = await Condition.deploy(allowedId); + condition = await hre.wrapper.deploy('TestIdGatingCondition', { + args: [allowedId], + }); // Grants signers[0] the permission to do ID gated actions with the deployed `TestIdGatingCondition` condition - dao2.grantWithCondition( + await dao2.grantWithCondition( testPlugin.address, ownerAddress, ID_GATED_ACTION_PERMISSION_ID, @@ -212,11 +211,12 @@ describe('SharedPlugin', function () { // Deploy condition and set allowed ID const allowedId = 0; - const Condition = new TestIdGatingCondition__factory(signers[0]); - condition = await Condition.deploy(allowedId); + condition = await hre.wrapper.deploy('TestIdGatingCondition', { + args: [allowedId], + }); // Grants signers[0] the permission to do ID gated actions with the deployed `TestIdGatingCondition` condition - dao1.grantWithCondition( + await dao1.grantWithCondition( testPlugin.address, ownerAddress, ID_GATED_ACTION_PERMISSION_ID, diff --git a/packages/contracts/test/deploy/managing-dao.ts b/packages/contracts/test/deploy/managing-dao.ts index 30cc17c94..dddd6223a 100644 --- a/packages/contracts/test/deploy/managing-dao.ts +++ b/packages/contracts/test/deploy/managing-dao.ts @@ -1,8 +1,5 @@ -import daoRegistryArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/dao/DAORegistry.sol/DAORegistry.json'; -import pluginRepoArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/plugin/repo/PluginRepo.sol/PluginRepo.json'; -import pluginRepoRegistryArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/plugin/repo/PluginRepoRegistry.sol/PluginRepoRegistry.json'; -import ensSubdomainRegistrarArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/utils/ens/ENSSubdomainRegistrar.sol/ENSSubdomainRegistrar.json'; -import daoArtifactData from '../../artifacts/src/core/dao/DAO.sol/DAO.json'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; + import { DAO, DAORegistry, @@ -14,10 +11,19 @@ import { Multisig__factory, PluginRepoRegistry, PluginRepoRegistry__factory, + PluginSetupProcessorUpgradeable, + PluginSetupProcessorUpgradeable__factory, } from '../../typechain'; + +import daoArtifactData from '../../artifacts/src/core/dao/DAO.sol/DAO.json'; +import daoRegistryArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/dao/DAORegistry.sol/DAORegistry.json'; +import pluginRepoRegistryArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/plugin/repo/PluginRepoRegistry.sol/PluginRepoRegistry.json'; +import pluginRepoArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/plugin/repo/PluginRepo.sol/PluginRepo.json'; +import ensSubdomainRegistrarArtifactData from '../../artifacts/@aragon/osx-v1.0.1/framework/utils/ens/ENSSubdomainRegistrar.sol/ENSSubdomainRegistrar.json'; +import pspUpgradeableArtifactData from '../../artifacts/src/zksync/PluginSetupProcessorUpgradeable.sol/PluginSetupProcessorUpgradeable.json'; + import {readImplementationValuesFromSlot} from '../../utils/storage'; import {initializeDeploymentFixture} from '../test-utils/fixture'; -import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import hre, {ethers, deployments, getNamedAccounts} from 'hardhat'; import {Deployment} from 'hardhat-deploy/dist/types'; @@ -38,6 +44,7 @@ describe('Managing DAO', function () { let pluginRepoRegistry: PluginRepoRegistry; let ensSubdomainRegistrarDeployments: Deployment[]; let ensSubdomainRegistrars: ENSSubdomainRegistrar[]; + let psp: PluginSetupProcessorUpgradeable; async function createUpgradeProposal( contractAddress: string[], @@ -48,9 +55,12 @@ describe('Managing DAO', function () { 'upgradeTo', [newImplementationAddress] ); - const actions = contractAddress.map(contract => { - return {to: contract, value: 0, data: data}; - }); + const actions = contractAddress + .filter(address => address != '') + .map(contract => { + return {to: contract, value: 0, data: data}; + }); + await multisig.createProposal( '0x', // metadata actions, @@ -92,6 +102,13 @@ describe('Managing DAO', function () { signers[0] ); + // PSP + let pspDeployment = await deployments.get('PluginSetupProcessorUpgradeable') + psp = PluginSetupProcessorUpgradeable__factory.connect( + pspDeployment.address, + signers[0] + ); + // ENSSubdomainRegistrar ensSubdomainRegistrarDeployments = [ await deployments.get('DAO_ENSSubdomainRegistrar'), @@ -198,6 +215,67 @@ describe('Managing DAO', function () { ); }); + it('Should be able to upgrade `PSP`', async function () { + // Grant managing dao first the permission to upgrade psp. + const action = { + to: managingDao.address, + value: 0, + data: DAO__factory.createInterface().encodeFunctionData('grant', [ + psp.address, + managingDao.address, + ethers.utils.id('UPGRADE_PSP_PERMISSION') + ]) + } + + await multisig.createProposal( + '0x', // metadata + [action], + 0, // allowFailureMap + true, // approve proposal + true, // execute proposal + 0, // start date: now + Math.floor(Date.now() / 1000) + 86400 // end date: now + 1 day + ) + + // deploy a new implementation. + const pspDeployment = await deployments.deploy( + 'newPSP', + { + contract: pspUpgradeableArtifactData, + from: ownerAddress, + args: [], + log: true, + } + ); + + expect(pspDeployment.implementation).to.be.equal(undefined); + + // check new implementation is different from the one on the `PSP`. + // read from slot + let implementationAddress = ( + await readImplementationValuesFromSlot([psp.address]) + )[0]; + + expect(pspDeployment.address).not.equal( + implementationAddress + ); + + // create proposal to upgrade to new implementation + await createUpgradeProposal( + [psp.address], + pspDeployment.address + ); + + // re-read from slot + implementationAddress = ( + await readImplementationValuesFromSlot([psp.address]) + )[0]; + + expect(pspDeployment.address).to.be.equal( + implementationAddress + ); + }); + it('Should be able to upgrade `PluginRepoRegistry`', async function () { // deploy a new implementation. const pluginRepoRegistry_v1_0_0_Deployment = await deployments.deploy( @@ -304,17 +382,24 @@ describe('Managing DAO', function () { } ); + // For some networks, not every repo is deployed, in which case + // hre.aragonPluginRepos contains empty string for that repo and + // causing the below code to fail. + const deployedRepoAddresses = []; + + for (const [key, value] of Object.entries(hre.aragonPluginRepos)) { + if (value == '') continue; + deployedRepoAddresses.push(value); + } + // make sure new `PluginRepoV2` deployment is just an implementation and not a proxy expect(PluginRepo_v1_0_0_Deployment.implementation).to.be.equal(undefined); // check new implementation is deferent from the one on the `DaoRegistry`. // read from slot - let implementationValues = await readImplementationValuesFromSlot([ - hre.aragonPluginRepos['token-voting'], - hre.aragonPluginRepos['address-list-voting'], - hre.aragonPluginRepos['admin'], - hre.aragonPluginRepos['multisig'], - ]); + let implementationValues = await readImplementationValuesFromSlot( + deployedRepoAddresses + ); for (let index = 0; index < implementationValues.length; index++) { const implementationAddress = implementationValues[index]; @@ -330,12 +415,9 @@ describe('Managing DAO', function () { ); // re-read from slot - implementationValues = await readImplementationValuesFromSlot([ - hre.aragonPluginRepos['token-voting'], - hre.aragonPluginRepos['address-list-voting'], - hre.aragonPluginRepos['admin'], - hre.aragonPluginRepos['multisig'], - ]); + implementationValues = await readImplementationValuesFromSlot( + deployedRepoAddresses + ); for (let index = 0; index < implementationValues.length; index++) { const implementationAddress = implementationValues[index]; diff --git a/packages/contracts/test/framework/dao/dao-factory.ts b/packages/contracts/test/framework/dao/dao-factory.ts index cb5a9cef0..7fdfbef71 100644 --- a/packages/contracts/test/framework/dao/dao-factory.ts +++ b/packages/contracts/test/framework/dao/dao-factory.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {anyValue} from '@nomicfoundation/hardhat-chai-matchers/withArgs'; @@ -9,7 +9,6 @@ import { PluginUUPSUpgradeableSetupV1Mock, PluginRepoRegistry, DAOFactory, - DAOFactory__factory, PluginRepoFactory, PluginUUPSUpgradeableSetupV2Mock, AdminSetup, @@ -19,9 +18,6 @@ import { Admin, DAO, Admin__factory, - AdminSetup__factory, - PluginUUPSUpgradeableSetupV2Mock__factory, - PluginUUPSUpgradeableSetupV1Mock__factory, DAORegistry__factory, PluginRepo__factory, IProtocolVersion__factory, @@ -42,7 +38,6 @@ import adminMetadata from '../../../src/plugins/governance/admin/build-metadata. import {findEventTopicLog} from '../../../utils/event'; import {daoExampleURI, deployNewDAO} from '../../test-utils/dao'; -import {deployWithProxy} from '../../test-utils/proxy'; import {getAppliedSetupId} from '../../test-utils/psp/hash-helpers'; import {PluginRepoPointer} from '../../test-utils/psp/types'; import { @@ -58,6 +53,8 @@ import { } from '../../test-utils/psp/wrappers'; import {getInterfaceID} from '../../test-utils/interfaces'; import {CURRENT_PROTOCOL_VERSION} from '../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../test-utils/wrapper'; +import {skipTestIfNetworkIsZkSync} from '../../test-utils/skip-functions'; const EVENTS = { PluginRepoRegistered: 'PluginRepoRegistered', @@ -137,11 +134,9 @@ async function extractInfoFromCreateDaoTx(tx: any): Promise<{ } async function getAnticipatedAddress(from: string) { - let nonce = await ethers.provider.getTransactionCount(from); - const anticipatedAddress = ethers.utils.getContractAddress({ - from: from, - nonce, - }); + const nonce = await hre.wrapper.getNonce(from); + const anticipatedAddress = hre.wrapper.getCreateAddress(from, nonce); + return anticipatedAddress; } @@ -181,8 +176,10 @@ describe('DAOFactory: ', function () { ); // DAO Registry - const DAORegistry = new DAORegistry__factory(signers[0]); - daoRegistry = await deployWithProxy(DAORegistry); + daoRegistry = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO_REGISTRY, { + withProxy: true, + }); + await daoRegistry.initialize( managingDao.address, ensSubdomainRegistrar.address @@ -205,8 +202,9 @@ describe('DAOFactory: ', function () { ); // Deploy DAO Factory - const DAOFactory = new DAOFactory__factory(signers[0]); - daoFactory = await DAOFactory.deploy(daoRegistry.address, psp.address); + daoFactory = await hre.wrapper.deploy('DAOFactory', { + args: [daoRegistry.address, psp.address], + }); // Grant the `REGISTER_DAO_PERMISSION` permission to the `daoFactory` await managingDao.grant( @@ -237,10 +235,9 @@ describe('DAOFactory: ', function () { ); // Create and register a plugin on the `PluginRepoRegistry`. - // PluginSetupV1 - const PluginUUPSUpgradeableSetupV1Mock = - new PluginUUPSUpgradeableSetupV1Mock__factory(signers[0]); - pluginSetupV1Mock = await PluginUUPSUpgradeableSetupV1Mock.deploy(); + pluginSetupV1Mock = await hre.wrapper.deploy( + 'PluginUUPSUpgradeableSetupV1Mock' + ); const tx = await pluginRepoFactory.createPluginRepoWithFirstVersion( 'plugin-uupsupgradeable-setup-v1-mock', @@ -551,207 +548,216 @@ describe('DAOFactory: ', function () { expect(installationAppliedEventCount).to.equal(2); }); - describe('E2E: Install,Update,Uninstall Plugin through Admin Plugin', async () => { - let pluginSetupV2Mock: PluginUUPSUpgradeableSetupV2Mock; - let adminPluginSetup: AdminSetup; - let adminPluginRepoAddress: string; - let adminPlugin: Admin; - let dao: DAO; - - beforeEach(async () => { - // create 2nd version of PluginUUPSUpgradeableSetupV1. - const PluginUUPSUpgradeableSetupV2Mock = - new PluginUUPSUpgradeableSetupV2Mock__factory(signers[0]); - pluginSetupV2Mock = await PluginUUPSUpgradeableSetupV2Mock.deploy(); - { - await pluginRepoMock.createVersion( - 1, - pluginSetupV2Mock.address, - '0x11', - '0x11' - ); - } - - // Create admin plugin repo so we can install it with dao - // This will help us execute installation/update calldatas through dao's execute. - const AdminPluginSetupFactory = new AdminSetup__factory(signers[0]); - adminPluginSetup = await AdminPluginSetupFactory.deploy(); - - let tx = await pluginRepoFactory.createPluginRepoWithFirstVersion( - 'admin', - adminPluginSetup.address, - ownerAddress, - '0x11', - '0x11' - ); - const pluginRepoRegisteredEvent = - await findEventTopicLog( - tx, - PluginRepoRegistry__factory.createInterface(), - EVENTS.PluginRepoRegistered - ); - adminPluginRepoAddress = pluginRepoRegisteredEvent.args.pluginRepo; - - // create dao with admin plugin. - const adminPluginRepoPointer: PluginRepoPointer = [ - adminPluginRepoAddress, - 1, - 1, - ]; - - let data = ethers.utils.defaultAbiCoder.encode( - adminMetadata.pluginSetup.prepareInstallation.inputs.map( - arg => `${arg.type} ${arg.name}` - ), - [ownerAddress] - ); - - let adminPluginInstallation = createPrepareInstallationParams( - adminPluginRepoPointer, - data - ); - tx = await daoFactory.createDao(daoSettings, [adminPluginInstallation]); - { - const installationPreparedEvent = - await findEventTopicLog( - tx, - PluginSetupProcessor__factory.createInterface(), - EVENTS.InstallationPrepared + // the below uses AdminSetup that has .clones inside the contract. + // This doesn't work on zksync https://github.com/zkSync-Community-Hub/zksync-developers/discussions/91 + skipTestIfNetworkIsZkSync( + 'E2E: Install,Update,Uninstall Plugin through Admin Plugin', + async () => { + describe('E2E: Install,Update,Uninstall Plugin through Admin Plugin', async () => { + let pluginSetupV2Mock: PluginUUPSUpgradeableSetupV2Mock; + let adminPluginSetup: AdminSetup; + let adminPluginRepoAddress: string; + let adminPlugin: Admin; + let dao: DAO; + + beforeEach(async () => { + // create 2nd version of PluginUUPSUpgradeableSetupV1. + pluginSetupV2Mock = await hre.wrapper.deploy( + 'PluginUUPSUpgradeableSetupV2Mock' ); - - const adminFactory = new Admin__factory(signers[0]); - adminPlugin = adminFactory.attach( - installationPreparedEvent.args.plugin - ); - - const daoFactory = new DAO__factory(signers[0]); - dao = daoFactory.attach(installationPreparedEvent.args.dao); - } - }); - - it('installs,updates and uninstalls plugin through dao', async () => { - // Prepare Installation - let { - plugin, - preparedSetupData: {permissions, helpers}, - } = await prepareInstallation( - psp, - dao.address, - [pluginSetupMockRepoAddress, 1, 1], - EMPTY_DATA - ); - - const daoInterface = DAO__factory.createInterface(); - const pspInterface = PluginSetupProcessor__factory.createInterface(); - - // Prepare actions for apply Installation. - let applyInstallationActions = [ - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ - dao.address, - psp.address, - ethers.utils.id('ROOT_PERMISSION'), - ]), - }, - { - to: psp.address, - value: 0, - data: pspInterface.encodeFunctionData('applyInstallation', [ - dao.address, - createApplyInstallationParams( - plugin, - [pluginSetupMockRepoAddress, 1, 1], - permissions, - helpers + { + await pluginRepoMock.createVersion( + 1, + pluginSetupV2Mock.address, + '0x11', + '0x11' + ); + } + + // Create admin plugin repo so we can install it with dao + // This will help us execute installation/update calldatas through dao's execute. + + adminPluginSetup = await hre.wrapper.deploy('AdminSetup'); + + let tx = await pluginRepoFactory.createPluginRepoWithFirstVersion( + 'admin', + adminPluginSetup.address, + ownerAddress, + '0x11', + '0x11' + ); + const pluginRepoRegisteredEvent = + await findEventTopicLog( + tx, + PluginRepoRegistry__factory.createInterface(), + EVENTS.PluginRepoRegistered + ); + adminPluginRepoAddress = pluginRepoRegisteredEvent.args.pluginRepo; + + // create dao with admin plugin. + const adminPluginRepoPointer: PluginRepoPointer = [ + adminPluginRepoAddress, + 1, + 1, + ]; + + let data = ethers.utils.defaultAbiCoder.encode( + adminMetadata.pluginSetup.prepareInstallation.inputs.map( + arg => `${arg.type} ${arg.name}` ), - ]), - }, - ]; - - await expect( - adminPlugin.executeProposal('0x', applyInstallationActions, 0) - ).to.emit(psp, EVENTS.InstallationApplied); - - // Prepare Update - const { - initData, - preparedSetupData: { - permissions: updatePermissions, - helpers: updateHelpers, - }, - } = await prepareUpdate( - psp, - dao.address, - plugin, - [1, 1], - [1, 2], - pluginSetupMockRepoAddress, - helpers, - EMPTY_DATA - ); + [ownerAddress] + ); - // Prepare actions for applyUpdate to succeed. - let applyUpdateActions = [ - { - to: dao.address, - value: 0, - data: daoInterface.encodeFunctionData('grant', [ + let adminPluginInstallation = createPrepareInstallationParams( + adminPluginRepoPointer, + data + ); + tx = await daoFactory.createDao(daoSettings, [ + adminPluginInstallation, + ]); + { + const installationPreparedEvent = + await findEventTopicLog( + tx, + PluginSetupProcessor__factory.createInterface(), + EVENTS.InstallationPrepared + ); + + const adminFactory = new Admin__factory(signers[0]); + adminPlugin = adminFactory.attach( + installationPreparedEvent.args.plugin + ); + + const daoFactory = new DAO__factory(signers[0]); + dao = daoFactory.attach(installationPreparedEvent.args.dao); + } + }); + + it('installs,updates and uninstalls plugin through dao', async () => { + // Prepare Installation + let { plugin, - psp.address, - ethers.utils.id('UPGRADE_PLUGIN_PERMISSION'), - ]), - }, - { - to: psp.address, - value: 0, - data: pspInterface.encodeFunctionData('applyUpdate', [ + preparedSetupData: {permissions, helpers}, + } = await prepareInstallation( + psp, dao.address, - createApplyUpdateParams( - plugin, - [pluginSetupMockRepoAddress, 1, 2], - initData, - updatePermissions, - updateHelpers - ), - ]), - }, - ]; - - await expect( - adminPlugin.executeProposal('0x', applyUpdateActions, 0) - ).to.emit(psp, EVENTS.UpdateApplied); - - // Uninstall the plugin - let {permissions: uninstallPermissions} = await prepareUninstallation( - psp, - dao.address, - plugin, - [pluginSetupMockRepoAddress, 1, 2], - updateHelpers, - EMPTY_DATA - ); + [pluginSetupMockRepoAddress, 1, 1], + EMPTY_DATA + ); - // Prepare actions for apply Uninstallation. - let applyUninstallationActions = [ - { - to: psp.address, - value: 0, - data: pspInterface.encodeFunctionData('applyUninstallation', [ + const daoInterface = DAO__factory.createInterface(); + const pspInterface = PluginSetupProcessor__factory.createInterface(); + + // Prepare actions for apply Installation. + let applyInstallationActions = [ + { + to: dao.address, + value: 0, + data: daoInterface.encodeFunctionData('grant', [ + dao.address, + psp.address, + ethers.utils.id('ROOT_PERMISSION'), + ]), + }, + { + to: psp.address, + value: 0, + data: pspInterface.encodeFunctionData('applyInstallation', [ + dao.address, + createApplyInstallationParams( + plugin, + [pluginSetupMockRepoAddress, 1, 1], + permissions, + helpers + ), + ]), + }, + ]; + + await expect( + adminPlugin.executeProposal('0x', applyInstallationActions, 0) + ).to.emit(psp, EVENTS.InstallationApplied); + + // Prepare Update + const { + initData, + preparedSetupData: { + permissions: updatePermissions, + helpers: updateHelpers, + }, + } = await prepareUpdate( + psp, dao.address, - createApplyUninstallationParams( - plugin, - [pluginSetupMockRepoAddress, 1, 2], - uninstallPermissions - ), - ]), - }, - ]; + plugin, + [1, 1], + [1, 2], + pluginSetupMockRepoAddress, + helpers, + EMPTY_DATA + ); - await expect( - adminPlugin.executeProposal('0x', applyUninstallationActions, 0) - ).to.emit(psp, EVENTS.UninstallationApplied); - }); - }); + // Prepare actions for applyUpdate to succeed. + let applyUpdateActions = [ + { + to: dao.address, + value: 0, + data: daoInterface.encodeFunctionData('grant', [ + plugin, + psp.address, + ethers.utils.id('UPGRADE_PLUGIN_PERMISSION'), + ]), + }, + { + to: psp.address, + value: 0, + data: pspInterface.encodeFunctionData('applyUpdate', [ + dao.address, + createApplyUpdateParams( + plugin, + [pluginSetupMockRepoAddress, 1, 2], + initData, + updatePermissions, + updateHelpers + ), + ]), + }, + ]; + + await expect( + adminPlugin.executeProposal('0x', applyUpdateActions, 0) + ).to.emit(psp, EVENTS.UpdateApplied); + + // Uninstall the plugin + let {permissions: uninstallPermissions} = await prepareUninstallation( + psp, + dao.address, + plugin, + [pluginSetupMockRepoAddress, 1, 2], + updateHelpers, + EMPTY_DATA + ); + + // Prepare actions for apply Uninstallation. + let applyUninstallationActions = [ + { + to: psp.address, + value: 0, + data: pspInterface.encodeFunctionData('applyUninstallation', [ + dao.address, + createApplyUninstallationParams( + plugin, + [pluginSetupMockRepoAddress, 1, 2], + uninstallPermissions + ), + ]), + }, + ]; + + await expect( + adminPlugin.executeProposal('0x', applyUninstallationActions, 0) + ).to.emit(psp, EVENTS.UninstallationApplied); + }); + }); + } + ); }); diff --git a/packages/contracts/test/framework/dao/dao-registry.ts b/packages/contracts/test/framework/dao/dao-registry.ts index a75e135b0..9dff8dda5 100644 --- a/packages/contracts/test/framework/dao/dao-registry.ts +++ b/packages/contracts/test/framework/dao/dao-registry.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {ContractFactory} from 'ethers'; import {ensDomainHash, ensLabelHash} from '../../../utils/ens'; @@ -14,13 +14,17 @@ import {DAORegistry__factory as DAORegistry_V1_0_0__factory} from '../../../type import {deployNewDAO} from '../../test-utils/dao'; import {deployENSSubdomainRegistrar} from '../../test-utils/ens'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {deployWithProxy} from '../../test-utils/proxy'; import {UPGRADE_PERMISSIONS} from '../../test-utils/permissions'; import { getProtocolVersion, ozUpgradeCheckManagedContract, } from '../../test-utils/uups-upgradeable'; import {CURRENT_PROTOCOL_VERSION} from '../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../test-utils/wrapper'; +import { + skipTestIfNetworkIsZkSync, + skipTestSuiteIfNetworkIsZkSync, +} from '../../test-utils/skip-functions'; const EVENTS = { DAORegistered: 'DAORegistered', @@ -64,9 +68,9 @@ describe('DAORegistry', function () { targetDao = await deployNewDAO(signers[0]); // DAO Registry - const Registry = new DAORegistry__factory(signers[0]); - - daoRegistry = await deployWithProxy(Registry); + daoRegistry = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO_REGISTRY, { + withProxy: true, + }); await daoRegistry.initialize( managingDao.address, @@ -168,7 +172,7 @@ describe('DAORegistry', function () { }); // without mocking we have to repeat the tests here to make sure the validation is correct - describe('subdomain validation', () => { + skipTestSuiteIfNetworkIsZkSync('subdomain validation', async () => { it('should validate the passed subdomain correctly (< 32 bytes long subdomain)', async () => { const baseSubdomain = 'this-is-my-super-valid-subdomain'; @@ -262,21 +266,21 @@ describe('DAORegistry', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, managingDao, { - dao: managingDao.address, - ensSubdomainRegistrar: ensSubdomainRegistrar.address, + initArgs: { + dao: managingDao.address, + ensSubdomainRegistrar: ensSubdomainRegistrar.address, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.DAO_REGISTRY_V1_0_0, + ARTIFACT_SOURCES.DAO_REGISTRY, UPGRADE_PERMISSIONS.UPGRADE_REGISTRY_PERMISSION_ID ); - expect(toImplementation).to.equal(fromImplementation); // The implementation was not changed from 1.0.0 to the current version - const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) ); diff --git a/packages/contracts/test/framework/plugin/plugin-repo-factory.ts b/packages/contracts/test/framework/plugin/plugin-repo-factory.ts index 27afbaefa..3a11360f9 100644 --- a/packages/contracts/test/framework/plugin/plugin-repo-factory.ts +++ b/packages/contracts/test/framework/plugin/plugin-repo-factory.ts @@ -1,6 +1,6 @@ import {expect} from 'chai'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import { deployMockPluginSetup, @@ -36,11 +36,8 @@ const REGISTER_ENS_SUBDOMAIN_PERMISSION_ID = ethers.utils.id( ); async function getExpectedRepoAddress(from: string) { - const nonce = await ethers.provider.getTransactionCount(from); - const expectedAddress = ethers.utils.getContractAddress({ - from: from, - nonce, - }); + const nonce = await hre.wrapper.getNonce(from, 'Deployment'); + const expectedAddress = hre.wrapper.getCreateAddress(from, nonce); return expectedAddress; } @@ -76,13 +73,9 @@ describe('PluginRepoFactory: ', function () { ); // deploy PluginRepoFactory - const PluginRepoFactory = new PluginRepoFactory__factory( - signers[0] - ) as PluginRepoFactory__factory; - - pluginRepoFactory = await PluginRepoFactory.deploy( - pluginRepoRegistry.address - ); + pluginRepoFactory = await hre.wrapper.deploy('PluginRepoFactory', { + args: [pluginRepoRegistry.address], + }); // grant REGISTER_PERMISSION_ID to pluginRepoFactory await managingDao.grant( diff --git a/packages/contracts/test/framework/plugin/plugin-repo-registry.ts b/packages/contracts/test/framework/plugin/plugin-repo-registry.ts index 45b9e3fb2..de0a2c311 100644 --- a/packages/contracts/test/framework/plugin/plugin-repo-registry.ts +++ b/packages/contracts/test/framework/plugin/plugin-repo-registry.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -16,13 +16,14 @@ import {deployNewDAO} from '../../test-utils/dao'; import {deployNewPluginRepo} from '../../test-utils/repo'; import {deployENSSubdomainRegistrar} from '../../test-utils/ens'; import {ensDomainHash} from '../../../utils/ens'; -import {deployWithProxy} from '../../test-utils/proxy'; import {UPGRADE_PERMISSIONS} from '../../test-utils/permissions'; import { getProtocolVersion, ozUpgradeCheckManagedContract, } from '../../test-utils/uups-upgradeable'; import {CURRENT_PROTOCOL_VERSION} from '../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../test-utils/wrapper'; +import {skipTestSuiteIfNetworkIsZkSync} from '../../test-utils/skip-functions'; const EVENTS = { PluginRepoRegistered: 'PluginRepoRegistered', @@ -64,9 +65,9 @@ describe('PluginRepoRegistry', function () { ); // deploy and initialize PluginRepoRegistry - const PluginRepoRegistry = new PluginRepoRegistry__factory(signers[0]); - pluginRepoRegistry = await deployWithProxy( - PluginRepoRegistry + pluginRepoRegistry = await hre.wrapper.deploy( + ARTIFACT_SOURCES.PLUGIN_REPO_REGISTRY, + {withProxy: true} ); await pluginRepoRegistry.initialize( @@ -177,7 +178,7 @@ describe('PluginRepoRegistry', function () { }); // without mocking we have to repeat the tests here to make sure the validation is correct - describe('subdomain validation', () => { + skipTestSuiteIfNetworkIsZkSync('subdomain validation', async () => { it('should validate the passed subdomain correctly (< 32 bytes long subdomain)', async () => { const baseSubdomain = 'this-is-my-super-valid-subdomain'; @@ -273,21 +274,21 @@ describe('PluginRepoRegistry', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, managingDAO, { - dao: managingDAO.address, - ensSubdomainRegistrar: ensSubdomainRegistrar.address, + initArgs: { + dao: managingDAO.address, + ensSubdomainRegistrar: ensSubdomainRegistrar.address, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.PLUGIN_REPO_REGISTRY_V1_0_0, + ARTIFACT_SOURCES.PLUGIN_REPO_REGISTRY, UPGRADE_PERMISSIONS.UPGRADE_REGISTRY_PERMISSION_ID ); - expect(toImplementation).to.equal(fromImplementation); // The implementation was not changed from 1.0.0 to the current version - const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) ); diff --git a/packages/contracts/test/framework/plugin/plugin-repo.ts b/packages/contracts/test/framework/plugin/plugin-repo.ts index eedbf25ec..ca42546ed 100644 --- a/packages/contracts/test/framework/plugin/plugin-repo.ts +++ b/packages/contracts/test/framework/plugin/plugin-repo.ts @@ -2,7 +2,7 @@ // https://github.com/aragon/apm/blob/next/test/contracts/apm/apm_repo.js import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -32,6 +32,7 @@ import {ZERO_BYTES32} from '../../test-utils/dao'; import {getInterfaceID} from '../../test-utils/interfaces'; import {CURRENT_PROTOCOL_VERSION} from '../../test-utils/protocol-version'; import {tagHash} from '../../test-utils/psp/hash-helpers'; +import {ARTIFACT_SOURCES} from '../../test-utils/wrapper'; const emptyBytes = '0x00'; const BUILD_METADATA = '0x11'; @@ -90,17 +91,18 @@ describe('PluginRepo', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagingContract( - signers[0], - signers[1], + 0, + 1, { - initialOwner: signers[0].address, + initArgs: { + initialOwner: signers[0].address, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.PLUGIN_REPO_V1_0_0, + ARTIFACT_SOURCES.PLUGIN_REPO, UPGRADE_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID ); - expect(toImplementation).to.not.equal(fromImplementation); const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) @@ -186,9 +188,7 @@ describe('PluginRepo', function () { ); // If a contract is passed, but doesn't have `supportsInterface` signature described in the contract. - const randomContract = await new TestPlugin__factory( - signers[0] - ).deploy(); + const randomContract = await hre.wrapper.deploy('TestPlugin'); await expect( pluginRepo.createVersion( 1, @@ -387,9 +387,8 @@ describe('PluginRepo', function () { }); it('allows to create placeholder builds for the same release', async () => { - const PlaceholderSetup = new PlaceholderSetup__factory(signers[0]); - const placeholder1 = await PlaceholderSetup.deploy(); - const placeholder2 = await PlaceholderSetup.deploy(); + const placeholder1 = await hre.wrapper.deploy('PlaceholderSetup'); + const placeholder2 = await hre.wrapper.deploy('PlaceholderSetup'); // Release 1 await expect( diff --git a/packages/contracts/test/framework/plugin/plugin-setup-processor.ts b/packages/contracts/test/framework/plugin/plugin-setup-processor.ts index d2b10adf6..4b6e25080 100644 --- a/packages/contracts/test/framework/plugin/plugin-setup-processor.ts +++ b/packages/contracts/test/framework/plugin/plugin-setup-processor.ts @@ -1,8 +1,7 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {anyValue} from '@nomicfoundation/hardhat-chai-matchers/withArgs'; -import pluginUUPSUpgradeableArtifact from '../../../artifacts/src/core/plugin/PluginUUPSUpgradeable.sol/PluginUUPSUpgradeable.json'; import { PluginSetupProcessor, @@ -25,18 +24,18 @@ import { PluginUUPSUpgradeableV2Mock__factory, PluginUUPSUpgradeableV3Mock__factory, PluginUUPSUpgradeableSetupV1Mock__factory, - PluginUUPSUpgradeableSetupV1MockBad__factory, - PluginUUPSUpgradeableSetupV2Mock__factory, - PluginUUPSUpgradeableSetupV4Mock__factory, - PluginCloneableSetupV1Mock__factory, - PluginCloneableSetupV2Mock__factory, - PluginCloneableSetupV1MockBad__factory, + PluginSetupProcessorUpgradeable, + PluginSetupProcessor__factory, + PluginSetupProcessorUpgradeable__factory } from '../../../typechain'; import {PluginRepoRegisteredEvent} from '../../../typechain/PluginRepoRegistry'; import {deployENSSubdomainRegistrar} from '../../test-utils/ens'; import {deployNewDAO, ZERO_BYTES32} from '../../test-utils/dao'; -import {deployPluginSetupProcessor} from '../../test-utils/plugin-setup-processor'; +import { + deployPluginSetupProcessor, + deployUpgradeablePluginSetupProcessor, +} from '../../test-utils/plugin-setup-processor'; import {findEventTopicLog} from '../../../utils/event'; import {Operation} from '../../../utils/types'; @@ -78,13 +77,14 @@ import { getPluginInstallationId, getPreparedSetupId, } from '../../test-utils/psp/hash-helpers'; -import {MockContract, smock} from '@defi-wonderland/smock'; import { installPlugin, updatePlugin, uninstallPlugin, } from '../../test-utils/psp/atomic-helpers'; import {UPGRADE_PERMISSIONS} from '../../test-utils/permissions'; +import {ozUpgradeCheckManagedContract} from '../../test-utils/uups-upgradeable'; +import { readImplementationValueFromSlot } from '../../../utils/storage'; const EVENTS = { InstallationPrepared: 'InstallationPrepared', @@ -121,770 +121,854 @@ const REGISTER_ENS_SUBDOMAIN_PERMISSION_ID = ethers.utils.id( const {UPGRADE_PLUGIN_PERMISSION_ID} = UPGRADE_PERMISSIONS; -describe('Plugin Setup Processor', function () { - let signers: SignerWithAddress[]; - let psp: PluginSetupProcessor; - let repoU: PluginRepo; - let PluginUV1: PluginUUPSUpgradeableV1Mock__factory; - let PluginUV2: PluginUUPSUpgradeableV2Mock__factory; - let PluginUV3: PluginUUPSUpgradeableV3Mock__factory; - let setupUV1: MockContract; - let setupUV2: MockContract; - let setupUV3: MockContract; - let setupUV4: MockContract; - let setupUV1Bad: MockContract; - let repoC: PluginRepo; - let setupCV1: MockContract; - let setupCV1Bad: MockContract; - let setupCV2: MockContract; - let ownerAddress: string; - let targetDao: DAO; - let managingDao: DAO; - let pluginRepoFactory: PluginRepoFactory; - let pluginRepoRegistry: PluginRepoRegistry; - - before(async () => { - signers = await ethers.getSigners(); - ownerAddress = await signers[0].getAddress(); - - PluginUV1 = new PluginUUPSUpgradeableV1Mock__factory(signers[0]); - PluginUV2 = new PluginUUPSUpgradeableV2Mock__factory(signers[0]); - PluginUV3 = new PluginUUPSUpgradeableV3Mock__factory(signers[0]); - - // Deploy PluginUUPSUpgradeableSetupMock - - const SetupV1 = await smock.mock( - 'PluginUUPSUpgradeableSetupV1Mock' - ); - setupUV1 = await SetupV1.deploy(); - - const PluginUUPSUpgradeableSetupV1MockBad = - await smock.mock( +const runs = [ + {artifact: 'PluginSetupProcessor', upgradeable: false}, + {artifact: 'PluginSetupProcessorUpgradeable', upgradeable: true}, +]; + +runs.forEach(options => { + describe(options.artifact, function () { + let signers: SignerWithAddress[]; + let psp: PluginSetupProcessor; + let repoU: PluginRepo; + let PluginUV1: PluginUUPSUpgradeableV1Mock__factory; + let PluginUV2: PluginUUPSUpgradeableV2Mock__factory; + let PluginUV3: PluginUUPSUpgradeableV3Mock__factory; + let setupUV1: PluginUUPSUpgradeableSetupV1Mock; + let setupUV2: PluginUUPSUpgradeableSetupV2Mock; + let setupUV3: PluginUUPSUpgradeableSetupV3Mock; + let setupUV4: PluginUUPSUpgradeableSetupV4Mock; + let setupUV1Bad: PluginUUPSUpgradeableSetupV1MockBad; + let repoC: PluginRepo; + let setupCV1: PluginCloneableSetupV1Mock; + let setupCV1Bad: PluginCloneableSetupV1MockBad; + let setupCV2: PluginCloneableSetupV2Mock; + let ownerAddress: string; + let targetDao: DAO; + let managingDao: DAO; + let pluginRepoFactory: PluginRepoFactory; + let pluginRepoRegistry: PluginRepoRegistry; + + before(async () => { + signers = await ethers.getSigners(); + ownerAddress = await signers[0].getAddress(); + + PluginUV1 = new PluginUUPSUpgradeableV1Mock__factory(signers[0]); + PluginUV2 = new PluginUUPSUpgradeableV2Mock__factory(signers[0]); + PluginUV3 = new PluginUUPSUpgradeableV3Mock__factory(signers[0]); + + // Deploy PluginUUPSUpgradeableSetupMock + setupUV1 = await hre.wrapper.deploy('PluginUUPSUpgradeableSetupV1Mock'); + setupUV1Bad = await hre.wrapper.deploy( 'PluginUUPSUpgradeableSetupV1MockBad' ); - setupUV1Bad = await PluginUUPSUpgradeableSetupV1MockBad.deploy(); - - const SetupV2 = await smock.mock( - 'PluginUUPSUpgradeableSetupV2Mock' - ); - setupUV2 = await SetupV2.deploy(); - - const SetupV3 = await smock.mock( - 'PluginUUPSUpgradeableSetupV3Mock' - ); - setupUV3 = await SetupV3.deploy(); - - const SetupV4 = await smock.mock( - 'PluginUUPSUpgradeableSetupV4Mock' - ); - setupUV4 = await SetupV4.deploy(await setupUV3.implementation()); - - // Deploy PluginCloneableSetupMock - const SetupC1 = await smock.mock( - 'PluginCloneableSetupV1Mock' - ); - setupCV1 = await SetupC1.deploy(); - - const SetupC1Bad = await smock.mock( - 'PluginCloneableSetupV1MockBad' - ); - setupCV1Bad = await SetupC1Bad.deploy(); - - const SetupC2 = await smock.mock( - 'PluginCloneableSetupV2Mock' - ); - setupCV2 = await SetupC2.deploy(); - - // Deploy yhe managing DAO having permission to manage `PluginSetupProcessor` - managingDao = await deployNewDAO(signers[0]); + setupUV2 = await hre.wrapper.deploy('PluginUUPSUpgradeableSetupV2Mock'); + setupUV3 = await hre.wrapper.deploy('PluginUUPSUpgradeableSetupV3Mock'); + setupUV4 = await hre.wrapper.deploy('PluginUUPSUpgradeableSetupV4Mock', { + args: [await setupUV3.implementation()], + }); - // Deploy ENS subdomain Registry - const ensSubdomainRegistrar = await deployENSSubdomainRegistrar( - signers[0], - managingDao, - 'dao.eth' - ); + // Deploy PluginCloneableSetupMock + setupCV1 = await hre.wrapper.deploy('PluginCloneableSetupV1Mock'); + setupCV1Bad = await hre.wrapper.deploy('PluginCloneableSetupV1MockBad'); + setupCV2 = await hre.wrapper.deploy('PluginCloneableSetupV2Mock'); - // Deploy Plugin Repo Registry - pluginRepoRegistry = await deployPluginRepoRegistry( - managingDao, - ensSubdomainRegistrar, - signers[0] - ); + // Deploy yhe managing DAO having permission to manage `PluginSetupProcessor` + managingDao = await deployNewDAO(signers[0]); - // Deploy Plugin Repo Factory - pluginRepoFactory = await deployPluginRepoFactory( - signers, - pluginRepoRegistry - ); + // Deploy ENS subdomain Registry + const ensSubdomainRegistrar = await deployENSSubdomainRegistrar( + signers[0], + managingDao, + 'dao.eth' + ); - // Grant `PLUGIN_REGISTER_PERMISSION` to `PluginRepoFactory`. - await managingDao.grant( - pluginRepoRegistry.address, - pluginRepoFactory.address, - REGISTER_PLUGIN_REPO_PERMISSION_ID - ); + // Deploy Plugin Repo Registry + pluginRepoRegistry = await deployPluginRepoRegistry( + managingDao, + ensSubdomainRegistrar, + signers[0] + ); - // Grant `REGISTER_ENS_SUBDOMAIN_PERMISSION` to `PluginRepoFactory`. - await managingDao.grant( - ensSubdomainRegistrar.address, - pluginRepoRegistry.address, - REGISTER_ENS_SUBDOMAIN_PERMISSION_ID - ); + // Deploy Plugin Repo Factory + pluginRepoFactory = await deployPluginRepoFactory( + signers, + pluginRepoRegistry + ); - const releaseMetadata = '0x11'; - const buildMetadata = '0x11'; + // Grant `PLUGIN_REGISTER_PERMISSION` to `PluginRepoFactory`. + await managingDao.grant( + pluginRepoRegistry.address, + pluginRepoFactory.address, + REGISTER_PLUGIN_REPO_PERMISSION_ID + ); - // Plugin Setup Processor - psp = await deployPluginSetupProcessor(pluginRepoRegistry); + // Grant `REGISTER_ENS_SUBDOMAIN_PERMISSION` to `PluginRepoFactory`. + await managingDao.grant( + ensSubdomainRegistrar.address, + pluginRepoRegistry.address, + REGISTER_ENS_SUBDOMAIN_PERMISSION_ID + ); - // Create and register a plugin on the PluginRepoRegistry - let tx = await pluginRepoFactory.createPluginRepoWithFirstVersion( - `plugin-uups-upgradeable-mock`, - setupUV1.address, // build 1 - ownerAddress, - releaseMetadata, - buildMetadata - ); + const releaseMetadata = '0x11'; + const buildMetadata = '0x11'; - const PluginRepoRegisteredEvent1 = - await findEventTopicLog( - tx, - PluginRepoRegistry__factory.createInterface(), - EVENTS.PluginRepoRegistered + // Plugin Setup Processor + if (options.upgradeable) { + psp = await deployUpgradeablePluginSetupProcessor( + managingDao, + pluginRepoRegistry + ); + } else { + psp = await deployPluginSetupProcessor(pluginRepoRegistry); + } + + // Create and register a plugin on the PluginRepoRegistry + let tx = await pluginRepoFactory.createPluginRepoWithFirstVersion( + `plugin-uups-upgradeable-mock`, + setupUV1.address, // build 1 + ownerAddress, + releaseMetadata, + buildMetadata ); - const PluginRepo = new PluginRepo__factory(signers[0]); - repoU = PluginRepo.attach(PluginRepoRegisteredEvent1.args.pluginRepo); - - // Add setups - await repoU.createVersion(1, setupUV2.address, EMPTY_DATA, EMPTY_DATA); // build 2 - await repoU.createVersion(1, setupUV3.address, EMPTY_DATA, EMPTY_DATA); // build 3 - await repoU.createVersion(1, setupUV1Bad.address, EMPTY_DATA, EMPTY_DATA); // build 4 - await repoU.createVersion(1, setupUV4.address, EMPTY_DATA, EMPTY_DATA); // build 5 - await repoU.createVersion(1, setupUV4.address, EMPTY_DATA, EMPTY_DATA); // buidl 6. - - tx = await pluginRepoFactory.createPluginRepoWithFirstVersion( - `plugin-clonable-mock`, - setupCV1.address, - ownerAddress, - releaseMetadata, - buildMetadata - ); - const PluginRepoRegisteredEvent2 = - await findEventTopicLog( - tx, - PluginRepoRegistry__factory.createInterface(), - EVENTS.PluginRepoRegistered + const PluginRepoRegisteredEvent1 = + await findEventTopicLog( + tx, + PluginRepoRegistry__factory.createInterface(), + EVENTS.PluginRepoRegistered + ); + const PluginRepo = new PluginRepo__factory(signers[0]); + repoU = PluginRepo.attach(PluginRepoRegisteredEvent1.args.pluginRepo); + + // Add setups + await repoU.createVersion(1, setupUV2.address, EMPTY_DATA, EMPTY_DATA); // build 2 + await repoU.createVersion(1, setupUV3.address, EMPTY_DATA, EMPTY_DATA); // build 3 + await repoU.createVersion(1, setupUV1Bad.address, EMPTY_DATA, EMPTY_DATA); // build 4 + await repoU.createVersion(1, setupUV4.address, EMPTY_DATA, EMPTY_DATA); // build 5 + await repoU.createVersion(1, setupUV4.address, EMPTY_DATA, EMPTY_DATA); // buidl 6. + + tx = await pluginRepoFactory.createPluginRepoWithFirstVersion( + `plugin-clonable-mock`, + setupCV1.address, + ownerAddress, + releaseMetadata, + buildMetadata ); - repoC = PluginRepo.attach(PluginRepoRegisteredEvent2.args.pluginRepo); - await repoC.createVersion(1, setupCV1Bad.address, EMPTY_DATA, EMPTY_DATA); - await repoC.createVersion(1, setupCV2.address, EMPTY_DATA, EMPTY_DATA); - }); - - beforeEach(async function () { - // Target DAO to be used as an example DAO - targetDao = await deployNewDAO(signers[0]); - // Grant - await targetDao.grant(targetDao.address, psp.address, ROOT_PERMISSION_ID); - }); - - // They end up in the same pluginRepo with - // the same release - 1, but different builds - 1,2,3. - describe('PluginUUPSUpgradeableSetupMock', function () { - it('points to the V1 implementation', async () => { - await checkImplementation(setupUV1, PluginUV1, 1); + const PluginRepoRegisteredEvent2 = + await findEventTopicLog( + tx, + PluginRepoRegistry__factory.createInterface(), + EVENTS.PluginRepoRegistered + ); + repoC = PluginRepo.attach(PluginRepoRegisteredEvent2.args.pluginRepo); + await repoC.createVersion(1, setupCV1Bad.address, EMPTY_DATA, EMPTY_DATA); + await repoC.createVersion(1, setupCV2.address, EMPTY_DATA, EMPTY_DATA); }); - it('points to the V2 implementation', async () => { - await checkImplementation(setupUV2, PluginUV2, 2); - }); + beforeEach(async function () { + // Target DAO to be used as an example DAO + targetDao = await deployNewDAO(signers[0]); - it('points to the V3 implementation', async () => { - await checkImplementation(setupUV3, PluginUV3, 3); + // Grant + await targetDao.grant(targetDao.address, psp.address, ROOT_PERMISSION_ID); }); - async function checkImplementation( - setup: any, - pluginFactory: any, - build: number - ) { - const {plugin} = await prepareInstallation( - psp, - targetDao.address, - [repoU.address, 1, build], - EMPTY_DATA - ); + if (options.upgradeable) { + // PSP stops upgradability after `hardcodedTimestamp`. + let hardcodedTimestamp = 1733007600; + describe('Upgrades', async () => { + beforeEach(async () => { + await managingDao + .connect(signers[0]) + .grant(psp.address, signers[0].address, UPGRADE_PERMISSIONS.UPGRADE_PSP_PERMISSION_ID); + }) + it('upgrades the contract', async () => { + const newPSP = await hre.wrapper.deploy('PluginSetupProcessorUpgradeable') - const proxy = await pluginFactory - .attach(plugin) - .callStatic.implementation(); + // @ts-ignore + await psp.upgradeTo(newPSP.address) - expect(proxy).to.equal(await setup.callStatic.implementation()); + const toImplementation = await readImplementationValueFromSlot(psp.address); + expect(toImplementation).to.equal(newPSP.address) + }); + + it("upgrades the contract even if current block number is very close to the specified timestamp", async () => { + const newPSP = await hre.wrapper.deploy('PluginSetupProcessorUpgradeable') + + const snapshotId = await ethers.provider.send("evm_snapshot", []); + + await ethers.provider.send('evm_setNextBlockTimestamp', [hardcodedTimestamp - 40000]) + await ethers.provider.send('evm_mine', []) + + // @ts-ignore + await psp.upgradeTo(newPSP.address) + + await ethers.provider.send('evm_revert', [snapshotId]) + }) + it("doesn't allow upgrade if block number exceeds the specified number", async () => { + const newPSP = await hre.wrapper.deploy('PluginSetupProcessorUpgradeable') + + const snapshotId = await ethers.provider.send("evm_snapshot", []); + + await ethers.provider.send('evm_setNextBlockTimestamp', [hardcodedTimestamp]) + await ethers.provider.send('evm_mine', []) + + // @ts-ignore + await expect(psp.upgradeTo(newPSP.address)).to.be.revertedWithCustomError( + psp, + 'PSPNonupgradeable' + ); + + await ethers.provider.send('evm_revert', [snapshotId]) + }) + }); } - }); - - describe('Installation', function () { - beforeEach(async () => { - // Grant necessary permission to `ownerAddress` so it can install plugins on behalf of the DAO. - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_INSTALLATION_PERMISSION_ID - ); - }); - describe('prepareInstallation', function () { - it('reverts if `PluginSetupRepo` does not exist on `PluginRepoRegistry`', async () => { - await expect( - psp.prepareInstallation( - targetDao.address, - createPrepareInstallationParams([ADDRESS_TWO, 1, 1], '0x') - ) - ).to.be.revertedWithCustomError(psp, 'PluginRepoNonexistent'); + // They end up in the same pluginRepo with + // the same release - 1, but different builds - 1,2,3. + describe('PluginUUPSUpgradeableSetupMock', function () { + it('points to the V1 implementation', async () => { + await checkImplementation(setupUV1, PluginUV1, 1); }); - it('reverts if the plugin version does not exist on `PluginRepoRegistry`', async () => { - // non-existent build which should cause error. - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 15]; - - await expect( - psp.prepareInstallation( - targetDao.address, - createPrepareInstallationParams(pluginRepoPointer, '0x') - ) - ).to.be.revertedWithCustomError(repoU, 'VersionHashDoesNotExist'); + it('points to the V2 implementation', async () => { + await checkImplementation(setupUV2, PluginUV2, 2); }); - it('reverts if plugin with the same setupId is already prepared.', async () => { - // uses plugin setup that returns the same plugin address and dependencies - // each time you call it. Useful to generate the same setup id - // which should revert. - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; - - const {preparedSetupId} = await prepareInstallation( - psp, - targetDao.address, - pluginRepoPointer, - '0x' - ); - - await expect( - psp.prepareInstallation( - targetDao.address, - createPrepareInstallationParams(pluginRepoPointer, '0x') - ) - ) - .to.be.revertedWithCustomError(psp, 'SetupAlreadyPrepared') - .withArgs(preparedSetupId); + it('points to the V3 implementation', async () => { + await checkImplementation(setupUV3, PluginUV3, 3); }); - it('reverts if plugin with the same address is already installed.', async () => { - // uses plugin setup that returns the same plugin address and dependencies - // each time you call it. Useful to generate the same plugin address - // which should revert. - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; - - await installPlugin( + async function checkImplementation( + setup: any, + pluginFactory: any, + build: number + ) { + const {plugin} = await prepareInstallation( psp, targetDao.address, - pluginRepoPointer, + [repoU.address, 1, build], EMPTY_DATA ); - await expect( - psp.prepareInstallation( - targetDao.address, - createPrepareInstallationParams(pluginRepoPointer, '0x') - ) - ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); - }); + const proxy = await pluginFactory + .attach(plugin) + .callStatic.implementation(); - // 1. prepareInstall for pluginId1 => setupId1 - // 2. applyInstall for pluginId1 => setupId1 - // 3. uninstall the plugin with applyUninstall. - // 4. prepareInstall for pluginId1 => setupId1 which succeeds. - it('EDGE-CASE: allows to prepare plugin installation with the same address and setupId if it was installed and then uninstalled', async () => { - // uses plugin setup that returns the same plugin address and dependencies. - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; + expect(proxy).to.equal(await setup.callStatic.implementation()); + } + }); - // Needed so applyUninstallation succeeds + describe('Installation', function () { + beforeEach(async () => { + // Grant necessary permission to `ownerAddress` so it can install plugins on behalf of the DAO. await targetDao.grant( psp.address, ownerAddress, - APPLY_UNINSTALLATION_PERMISSION_ID + APPLY_INSTALLATION_PERMISSION_ID ); + }); - const {plugin, helpers} = await installPlugin( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - ); + describe('prepareInstallation', function () { + it('reverts if `PluginSetupRepo` does not exist on `PluginRepoRegistry`', async () => { + await expect( + psp.prepareInstallation( + targetDao.address, + createPrepareInstallationParams([ADDRESS_TWO, 1, 1], '0x') + ) + ).to.be.revertedWithCustomError(psp, 'PluginRepoNonexistent'); + }); - await uninstallPlugin( - psp, - targetDao.address, - plugin, - helpers, - pluginRepoPointer, - EMPTY_DATA - ); + it('reverts if the plugin version does not exist on `PluginRepoRegistry`', async () => { + // non-existent build which should cause error. + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 15]; - await expect( - psp.prepareInstallation( + await expect( + psp.prepareInstallation( + targetDao.address, + createPrepareInstallationParams(pluginRepoPointer, '0x') + ) + ).to.be.revertedWithCustomError(repoU, 'VersionHashDoesNotExist'); + }); + + it('reverts if plugin with the same setupId is already prepared.', async () => { + // uses plugin setup that returns the same plugin address and dependencies + // each time you call it. Useful to generate the same setup id + // which should revert. + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; + + const {preparedSetupId} = await prepareInstallation( + psp, targetDao.address, - createPrepareInstallationParams(pluginRepoPointer, '0x') + pluginRepoPointer, + '0x' + ); + + await expect( + psp.prepareInstallation( + targetDao.address, + createPrepareInstallationParams(pluginRepoPointer, '0x') + ) ) - ).not.to.be.reverted; - }); + .to.be.revertedWithCustomError(psp, 'SetupAlreadyPrepared') + .withArgs(preparedSetupId); + }); - it("successfully calls plugin setup's prepareInstallation with correct arguments", async () => { - // Uses setupUV1 - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; + it('reverts if plugin with the same address is already installed.', async () => { + // uses plugin setup that returns the same plugin address and dependencies + // each time you call it. Useful to generate the same plugin address + // which should revert. + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; - const data = '0x11'; + await installPlugin( + psp, + targetDao.address, + pluginRepoPointer, + EMPTY_DATA + ); - // Reset the cache so previus tests don't trick this test that - // the function was really called, even though it mightn't have been. - // This is needed because smock contracts are not deployed in beforeEach, - // but in before, so there's only one instance of them for all tests. - setupUV1.prepareInstallation.reset(); + await expect( + psp.prepareInstallation( + targetDao.address, + createPrepareInstallationParams(pluginRepoPointer, '0x') + ) + ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); + }); - await psp.prepareInstallation( - targetDao.address, - createPrepareInstallationParams(pluginRepoPointer, data) - ); + // 1. prepareInstall for pluginId1 => setupId1 + // 2. applyInstall for pluginId1 => setupId1 + // 3. uninstall the plugin with applyUninstall. + // 4. prepareInstall for pluginId1 => setupId1 which succeeds. + it('EDGE-CASE: allows to prepare plugin installation with the same address and setupId if it was installed and then uninstalled', async () => { + // uses plugin setup that returns the same plugin address and dependencies. + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; + + // Needed so applyUninstallation succeeds + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_UNINSTALLATION_PERMISSION_ID + ); - expect(setupUV1.prepareInstallation).to.have.been.calledWith( - targetDao.address, - data - ); - }); + const {plugin, helpers} = await installPlugin( + psp, + targetDao.address, + pluginRepoPointer, + EMPTY_DATA + ); - it('successfully prepares a plugin installation with the correct event arguments', async () => { - const data = '0x11'; - const expectedPermissions = mockPermissionsOperations( - 0, - 2, - Operation.Grant - ); - const expectedHelpers = mockHelpers(2); - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; + await uninstallPlugin( + psp, + targetDao.address, + plugin, + helpers, + pluginRepoPointer, + EMPTY_DATA + ); - const preparedSetupId = getPreparedSetupId( - pluginRepoPointer, - expectedHelpers, - // @ts-ignore - expectedPermissions, - '0x', - PreparationType.Installation - ); + await expect( + psp.prepareInstallation( + targetDao.address, + createPrepareInstallationParams(pluginRepoPointer, '0x') + ) + ).not.to.be.reverted; + }); - await expect( - psp.prepareInstallation( - targetDao.address, - createPrepareInstallationParams(pluginRepoPointer, data) + it("successfully calls plugin setup's prepareInstallation with correct arguments", async () => { + // Uses setupUV1 + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; + + const data = '0x11'; + + await expect( + psp.prepareInstallation( + targetDao.address, + createPrepareInstallationParams(pluginRepoPointer, data) + ) ) - ) - .to.emit(psp, 'InstallationPrepared') - .withArgs( - ownerAddress, - targetDao.address, - preparedSetupId, - pluginRepoPointer[0], - (val: any) => expect(val).to.deep.equal([1, 1]), - data, - anyValue, - (val: any) => - expect(val).to.deep.equal([expectedHelpers, expectedPermissions]) + .to.emit(setupUV1, 'InstallationPrepared') + .withArgs(targetDao.address, data); + }); + + it('successfully prepares a plugin installation with the correct event arguments', async () => { + const data = '0x11'; + const expectedPermissions = mockPermissionsOperations( + 0, + 2, + Operation.Grant ); - }); - }); + const expectedHelpers = mockHelpers(2); + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; - describe('applyInstallation', function () { - it('reverts if caller does not have `APPLY_INSTALLATION_PERMISSION`', async () => { - // revoke `APPLY_INSTALLATION_PERMISSION_ID` on dao for plugin installer - // to see that it can't set permissions without it. - await targetDao.revoke( - psp.address, - ownerAddress, - APPLY_INSTALLATION_PERMISSION_ID - ); + const preparedSetupId = getPreparedSetupId( + pluginRepoPointer, + expectedHelpers, + // @ts-ignore + expectedPermissions, + '0x', + PreparationType.Installation + ); - await expect( - psp.applyInstallation( - targetDao.address, - createApplyInstallationParams( - ethers.constants.AddressZero, - [ethers.constants.AddressZero, 1, 1], - [], - [] + await expect( + psp.prepareInstallation( + targetDao.address, + createPrepareInstallationParams(pluginRepoPointer, data) ) ) - ) - .to.be.revertedWithCustomError(psp, 'SetupApplicationUnauthorized') - .withArgs( - targetDao.address, + .to.emit(psp, 'InstallationPrepared') + .withArgs( + ownerAddress, + targetDao.address, + preparedSetupId, + pluginRepoPointer[0], + (val: any) => expect(val).to.deep.equal([1, 1]), + data, + anyValue, + (val: any) => + expect(val).to.deep.equal([ + expectedHelpers, + expectedPermissions, + ]) + ); + }); + }); + + describe('applyInstallation', function () { + it('reverts if caller does not have `APPLY_INSTALLATION_PERMISSION`', async () => { + // revoke `APPLY_INSTALLATION_PERMISSION_ID` on dao for plugin installer + // to see that it can't set permissions without it. + await targetDao.revoke( + psp.address, ownerAddress, APPLY_INSTALLATION_PERMISSION_ID ); - }); - it("reverts if PluginSetupProcessor does not have DAO's `ROOT_PERMISSION`", async () => { - await targetDao.revoke( - targetDao.address, - psp.address, - ROOT_PERMISSION_ID - ); + await expect( + psp.applyInstallation( + targetDao.address, + createApplyInstallationParams( + ethers.constants.AddressZero, + [ethers.constants.AddressZero, 1, 1], + [], + [] + ) + ) + ) + .to.be.revertedWithCustomError(psp, 'SetupApplicationUnauthorized') + .withArgs( + targetDao.address, + ownerAddress, + APPLY_INSTALLATION_PERMISSION_ID + ); + }); - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; + it("reverts if PluginSetupProcessor does not have DAO's `ROOT_PERMISSION`", async () => { + await targetDao.revoke( + targetDao.address, + psp.address, + ROOT_PERMISSION_ID + ); - const { - plugin, - preparedSetupData: {permissions, helpers}, - } = await prepareInstallation( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - ); + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; - await expect( - psp.applyInstallation( + const { + plugin, + preparedSetupData: {permissions, helpers}, + } = await prepareInstallation( + psp, targetDao.address, - createApplyInstallationParams( - plugin, - pluginRepoPointer, - permissions, - helpers + pluginRepoPointer, + EMPTY_DATA + ); + + await expect( + psp.applyInstallation( + targetDao.address, + createApplyInstallationParams( + plugin, + pluginRepoPointer, + permissions, + helpers + ) ) ) - ) - .to.be.revertedWithCustomError(targetDao, 'Unauthorized') - .withArgs(targetDao.address, psp.address, ROOT_PERMISSION_ID); - }); + .to.be.revertedWithCustomError(targetDao, 'Unauthorized') + .withArgs(targetDao.address, psp.address, ROOT_PERMISSION_ID); + }); - it("reverts if setupId wasn't prepared by `prepareInstallation` first", async () => { - const permissions = mockPermissionsOperations(0, 1, Operation.Grant); - const helpers = mockHelpers(1); + it("reverts if setupId wasn't prepared by `prepareInstallation` first", async () => { + const permissions = mockPermissionsOperations(0, 1, Operation.Grant); + const helpers = mockHelpers(1); - // really don't matter what we choose here for the plugin address. - const pluginAddress = ownerAddress; + // really don't matter what we choose here for the plugin address. + const pluginAddress = ownerAddress; - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; - // The PSP contract should generate the same setupId and revert with it below. - const preparedSetupId = getPreparedSetupId( - pluginRepoPointer, - helpers, - // @ts-ignore - permissions, - '0x', - PreparationType.Installation - ); + // The PSP contract should generate the same setupId and revert with it below. + const preparedSetupId = getPreparedSetupId( + pluginRepoPointer, + helpers, + // @ts-ignore + permissions, + '0x', + PreparationType.Installation + ); - // directly tries to apply installation even if `prepareInstallation` wasn't called first. - await expect( - psp.applyInstallation( - targetDao.address, - createApplyInstallationParams( - pluginAddress, - pluginRepoPointer, - // @ts-ignore - permissions, - helpers + // directly tries to apply installation even if `prepareInstallation` wasn't called first. + await expect( + psp.applyInstallation( + targetDao.address, + createApplyInstallationParams( + pluginAddress, + pluginRepoPointer, + // @ts-ignore + permissions, + helpers + ) ) ) - ) - .to.be.revertedWithCustomError(psp, 'SetupNotApplicable') - .withArgs(preparedSetupId); - }); + .to.be.revertedWithCustomError(psp, 'SetupNotApplicable') + .withArgs(preparedSetupId); + }); - it('reverts if the plugin with the same address is already installed', async () => { - // uses plugin setup that returns the same plugin address and dependencies - // each time you call it. Useful to generate the same plugin address - // which should revert. - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; + it('reverts if the plugin with the same address is already installed', async () => { + // uses plugin setup that returns the same plugin address and dependencies + // each time you call it. Useful to generate the same plugin address + // which should revert. + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; - const {plugin, permissions, helpers} = await installPlugin( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - ); - - await expect( - psp.applyInstallation( + const {plugin, permissions, helpers} = await installPlugin( + psp, targetDao.address, - createApplyInstallationParams( - plugin, - pluginRepoPointer, - permissions, - helpers + pluginRepoPointer, + EMPTY_DATA + ); + + await expect( + psp.applyInstallation( + targetDao.address, + createApplyInstallationParams( + plugin, + pluginRepoPointer, + permissions, + helpers + ) ) - ) - ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); - }); + ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); + }); - it('successfully applies installation if setupId was prepared first by `prepareInstallation`', async () => { - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; + it('successfully applies installation if setupId was prepared first by `prepareInstallation`', async () => { + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 1]; - const { - plugin, - preparedSetupData: {permissions, helpers}, - preparedSetupId, - } = await prepareInstallation( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - ); + const { + plugin, + preparedSetupData: {permissions, helpers}, + preparedSetupId, + } = await prepareInstallation( + psp, + targetDao.address, + pluginRepoPointer, + EMPTY_DATA + ); - const appliedSetupId = getAppliedSetupId(pluginRepoPointer, helpers); + const appliedSetupId = getAppliedSetupId(pluginRepoPointer, helpers); - await expect( - psp.applyInstallation( - targetDao.address, - createApplyInstallationParams( - plugin, - pluginRepoPointer, - permissions, - helpers + await expect( + psp.applyInstallation( + targetDao.address, + createApplyInstallationParams( + plugin, + pluginRepoPointer, + permissions, + helpers + ) ) ) - ) - .to.emit(psp, 'InstallationApplied') - .withArgs(targetDao.address, plugin, preparedSetupId, appliedSetupId); - }); - - // 1. call prepareinstall 2 times for the same plugin version - // to get 2 preparations with same plugin address, but different setup ids. - // 2. call applyInstall for one of them and see that 2nd one - // would no longer be valid for the installation even though it was valid before. - it('EDGE-CASE: reverts for all preparation if one of them was already applied for the install', async () => { - const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; + .to.emit(psp, 'InstallationApplied') + .withArgs( + targetDao.address, + plugin, + preparedSetupId, + appliedSetupId + ); + }); - const { - plugin, - preparedSetupData: { - permissions: firstPreparedPermissions, - helpers: firstPreparedHelpers, - }, - preparedSetupId: firstPreparedSetupId, - } = await prepareInstallation( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - ); + // 1. call prepareinstall 2 times for the same plugin version + // to get 2 preparations with same plugin address, but different setup ids. + // 2. call applyInstall for one of them and see that 2nd one + // would no longer be valid for the installation even though it was valid before. + it('EDGE-CASE: reverts for all preparation if one of them was already applied for the install', async () => { + const pluginRepoPointer: PluginRepoPointer = [repoU.address, 1, 4]; + + const { + plugin, + preparedSetupData: { + permissions: firstPreparedPermissions, + helpers: firstPreparedHelpers, + }, + preparedSetupId: firstPreparedSetupId, + } = await prepareInstallation( + psp, + targetDao.address, + pluginRepoPointer, + EMPTY_DATA + ); - setupUV1Bad.prepareInstallation.returns([ - // Must be the same plugin address that gets returned from pluginRepoPointer's prepareInstallation - ethers.constants.AddressZero, - // modify so it generates different setup id. - [mockHelpers(1), mockPermissionsOperations(0, 2, Operation.Grant)], - ]); + await setupUV1Bad.mockPermissionIndexes(0, 2); - const { - preparedSetupData: { - permissions: secondPreparedPermissions, - helpers: secondPreparedHelpers, - }, - preparedSetupId: secondPreparedSetupId, - } = await prepareInstallation( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - ); + const { + preparedSetupData: { + permissions: secondPreparedPermissions, + helpers: secondPreparedHelpers, + }, + preparedSetupId: secondPreparedSetupId, + } = await prepareInstallation( + psp, + targetDao.address, + pluginRepoPointer, + EMPTY_DATA + ); - const pluginInstallationId = getPluginInstallationId( - targetDao.address, - plugin - ); - // Check that both setupId are valid at this moment as none of them have been applied yet. - await expect( - psp.validatePreparedSetupId( - pluginInstallationId, - firstPreparedSetupId - ) - ).not.to.be.reverted; - await expect( - psp.validatePreparedSetupId( - pluginInstallationId, - secondPreparedSetupId - ) - ).not.to.be.reverted; - await expect( - psp.callStatic.applyInstallation( + const pluginInstallationId = getPluginInstallationId( targetDao.address, - createApplyInstallationParams( - plugin, - pluginRepoPointer, - firstPreparedPermissions, - firstPreparedHelpers + plugin + ); + // Check that both setupId are valid at this moment as none of them have been applied yet. + await expect( + psp.validatePreparedSetupId( + pluginInstallationId, + firstPreparedSetupId ) - ) - ).not.to.be.reverted; - await expect( - psp.callStatic.applyInstallation( - targetDao.address, - createApplyInstallationParams( - plugin, - pluginRepoPointer, - secondPreparedPermissions, - secondPreparedHelpers + ).not.to.be.reverted; + await expect( + psp.validatePreparedSetupId( + pluginInstallationId, + secondPreparedSetupId ) - ) - ).not.to.be.reverted; + ).not.to.be.reverted; + await expect( + psp.callStatic.applyInstallation( + targetDao.address, + createApplyInstallationParams( + plugin, + pluginRepoPointer, + firstPreparedPermissions, + firstPreparedHelpers + ) + ) + ).not.to.be.reverted; + await expect( + psp.callStatic.applyInstallation( + targetDao.address, + createApplyInstallationParams( + plugin, + pluginRepoPointer, + secondPreparedPermissions, + secondPreparedHelpers + ) + ) + ).not.to.be.reverted; - // Lets install one of them. - await applyInstallation( - psp, - targetDao.address, - plugin, - pluginRepoPointer, - firstPreparedPermissions, - firstPreparedHelpers - ); + // Lets install one of them. + await applyInstallation( + psp, + targetDao.address, + plugin, + pluginRepoPointer, + firstPreparedPermissions, + firstPreparedHelpers + ); - await expect( - psp.validatePreparedSetupId( - pluginInstallationId, - firstPreparedSetupId - ) - ).to.be.reverted; - await expect( - psp.validatePreparedSetupId( - pluginInstallationId, - secondPreparedSetupId - ) - ).to.be.reverted; + await expect( + psp.validatePreparedSetupId( + pluginInstallationId, + firstPreparedSetupId + ) + ).to.be.reverted; + await expect( + psp.validatePreparedSetupId( + pluginInstallationId, + secondPreparedSetupId + ) + ).to.be.reverted; - await expect( - psp.applyInstallation( - targetDao.address, - createApplyInstallationParams( - plugin, - pluginRepoPointer, - firstPreparedPermissions, - firstPreparedHelpers + await expect( + psp.applyInstallation( + targetDao.address, + createApplyInstallationParams( + plugin, + pluginRepoPointer, + firstPreparedPermissions, + firstPreparedHelpers + ) ) - ) - ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); + ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); - await expect( - psp.applyInstallation( - targetDao.address, - createApplyInstallationParams( - plugin, - pluginRepoPointer, - secondPreparedPermissions, - secondPreparedHelpers + await expect( + psp.applyInstallation( + targetDao.address, + createApplyInstallationParams( + plugin, + pluginRepoPointer, + secondPreparedPermissions, + secondPreparedHelpers + ) ) - ) - ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); + ).to.be.revertedWithCustomError(psp, 'PluginAlreadyInstalled'); - // Clean up - setupUV1Bad.prepareInstallation.reset(); + await setupUV1Bad.reset(); + }); }); }); - }); - - describe('Uninstallation', function () { - let proxy: string; - let helpersUV1: string[]; - let permissionsUV1: PermissionOperation[]; - let pluginRepoPointer: PluginRepoPointer; - let currentAppliedSetupId: string; - - beforeEach(async () => { - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_INSTALLATION_PERMISSION_ID - ); - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_UNINSTALLATION_PERMISSION_ID - ); - pluginRepoPointer = [repoU.address, 1, 1]; - - ({ - plugin: proxy, - helpers: helpersUV1, - permissions: permissionsUV1, - appliedSetupId: currentAppliedSetupId, - } = await installPlugin(psp, targetDao.address, pluginRepoPointer)); - }); + describe('Uninstallation', function () { + let proxy: string; + let helpersUV1: string[]; + let permissionsUV1: PermissionOperation[]; + let pluginRepoPointer: PluginRepoPointer; + let currentAppliedSetupId: string; - describe('prepareUninstallation', function () { - it('reverts if plugin is not installed yet', async () => { - // For extra safety, let's still call prepareInstall, - // but it should still revert, as it's not installed yet. - const { - plugin, - preparedSetupData: {helpers}, - } = await prepareInstallation( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA + beforeEach(async () => { + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_INSTALLATION_PERMISSION_ID + ); + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_UNINSTALLATION_PERMISSION_ID ); - const appliedSetupId = getAppliedSetupId(pluginRepoPointer, helpers); + pluginRepoPointer = [repoU.address, 1, 1]; - await expect( - psp.prepareUninstallation( - targetDao.address, - createPrepareUninstallationParams( - plugin, - pluginRepoPointer, - helpers, - EMPTY_DATA - ) - ) - ) - .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') - .withArgs(ZERO_BYTES32, appliedSetupId); + ({ + plugin: proxy, + helpers: helpersUV1, + permissions: permissionsUV1, + appliedSetupId: currentAppliedSetupId, + } = await installPlugin(psp, targetDao.address, pluginRepoPointer)); }); - it('reverts if prepare uninstallation params do not match the current `appliedSetupId`', async () => { - { - // helpersUV1 contains two helper addresses. Let's remove one - // to make sure modified helpers will cause test to fail. - const modifiedHelpers = [...helpersUV1].slice(0, -1); - - const appliedSetupId = getAppliedSetupId( + describe('prepareUninstallation', function () { + it('reverts if plugin is not installed yet', async () => { + // For extra safety, let's still call prepareInstall, + // but it should still revert, as it's not installed yet. + const { + plugin, + preparedSetupData: {helpers}, + } = await prepareInstallation( + psp, + targetDao.address, pluginRepoPointer, - modifiedHelpers + EMPTY_DATA ); + const appliedSetupId = getAppliedSetupId(pluginRepoPointer, helpers); + await expect( - prepareUninstallation( - psp, + psp.prepareUninstallation( targetDao.address, - proxy, - pluginRepoPointer, - modifiedHelpers, - EMPTY_DATA + createPrepareUninstallationParams( + plugin, + pluginRepoPointer, + helpers, + EMPTY_DATA + ) ) ) .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') - .withArgs(currentAppliedSetupId, appliedSetupId); - } + .withArgs(ZERO_BYTES32, appliedSetupId); + }); + + it('reverts if prepare uninstallation params do not match the current `appliedSetupId`', async () => { + { + // helpersUV1 contains two helper addresses. Let's remove one + // to make sure modified helpers will cause test to fail. + const modifiedHelpers = [...helpersUV1].slice(0, -1); + + const appliedSetupId = getAppliedSetupId( + pluginRepoPointer, + modifiedHelpers + ); + + await expect( + prepareUninstallation( + psp, + targetDao.address, + proxy, + pluginRepoPointer, + modifiedHelpers, + EMPTY_DATA + ) + ) + .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') + .withArgs(currentAppliedSetupId, appliedSetupId); + } + + { + // Reverse order/sequence which still should cause to revert. + const modifiedHelpers = [...helpersUV1].reverse(); + + const appliedSetupId = getAppliedSetupId( + pluginRepoPointer, + modifiedHelpers + ); - { - // Reverse order/sequence which still should cause to revert. - const modifiedHelpers = [...helpersUV1].reverse(); + await expect( + prepareUninstallation( + psp, + targetDao.address, + proxy, + pluginRepoPointer, + modifiedHelpers, + EMPTY_DATA + ) + ) + .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') + .withArgs(currentAppliedSetupId, appliedSetupId); + } + + { + const modifiedPluginRepoPointer = [ + pluginRepoPointer[0], + pluginRepoPointer[1], + 2, // change the build to trigger generating different setup id. + ]; + + const appliedSetupId = getAppliedSetupId( + // @ts-ignore + modifiedPluginRepoPointer, + helpersUV1 + ); + + await expect( + prepareUninstallation( + psp, + targetDao.address, + proxy, + // @ts-ignore + modifiedPluginRepoPointer, + helpersUV1, + EMPTY_DATA + ) + ) + .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') + .withArgs(currentAppliedSetupId, appliedSetupId); + } + }); - const appliedSetupId = getAppliedSetupId( + it('reverts if plugin uninstallation with the same setup is already prepared', async () => { + const {preparedSetupId} = await prepareUninstallation( + psp, + targetDao.address, + proxy, pluginRepoPointer, - modifiedHelpers + helpersUV1, + EMPTY_DATA ); await expect( @@ -893,287 +977,181 @@ describe('Plugin Setup Processor', function () { targetDao.address, proxy, pluginRepoPointer, - modifiedHelpers, + helpersUV1, EMPTY_DATA ) ) - .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') - .withArgs(currentAppliedSetupId, appliedSetupId); - } + .to.be.revertedWithCustomError(psp, 'SetupAlreadyPrepared') + .withArgs(preparedSetupId); + }); - { - const modifiedPluginRepoPointer = [ - pluginRepoPointer[0], - pluginRepoPointer[1], - 2, // change the build to trigger generating different setup id. - ]; + it('reverts if the plugin was uninstalled and tries to prepare uninstallation for it', async () => { + // make sure that prepare uninstall doesn't revert before applying uninstall. + await expect( + psp.callStatic.prepareUninstallation( + targetDao.address, + createPrepareUninstallationParams( + proxy, + pluginRepoPointer, + helpersUV1, + EMPTY_DATA + ) + ) + ).not.to.be.reverted; - const appliedSetupId = getAppliedSetupId( - // @ts-ignore - modifiedPluginRepoPointer, - helpersUV1 + await uninstallPlugin( + psp, + targetDao.address, + proxy, + helpersUV1, + pluginRepoPointer, + EMPTY_DATA ); await expect( - prepareUninstallation( - psp, + psp.prepareUninstallation( targetDao.address, - proxy, - // @ts-ignore - modifiedPluginRepoPointer, - helpersUV1, - EMPTY_DATA + createPrepareUninstallationParams( + proxy, + pluginRepoPointer, + helpersUV1, + EMPTY_DATA + ) ) ) .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') - .withArgs(currentAppliedSetupId, appliedSetupId); - } - }); - - it('reverts if plugin uninstallation with the same setup is already prepared', async () => { - const {preparedSetupId} = await prepareUninstallation( - psp, - targetDao.address, - proxy, - pluginRepoPointer, - helpersUV1, - EMPTY_DATA - ); + .withArgs( + ZERO_BYTES32, + getAppliedSetupId(pluginRepoPointer, helpersUV1) + ); + }); - await expect( - prepareUninstallation( + it('allows to prepare multiple uninstallation as long as setup is different', async () => { + await prepareUninstallation( psp, targetDao.address, proxy, pluginRepoPointer, helpersUV1, EMPTY_DATA - ) - ) - .to.be.revertedWithCustomError(psp, 'SetupAlreadyPrepared') - .withArgs(preparedSetupId); - }); + ); - it('reverts if the plugin was uninstalled and tries to prepare uninstallation for it', async () => { - // make sure that prepare uninstall doesn't revert before applying uninstall. - await expect( - psp.callStatic.prepareUninstallation( - targetDao.address, - createPrepareUninstallationParams( - proxy, - pluginRepoPointer, - helpersUV1, - EMPTY_DATA - ) - ) - ).not.to.be.reverted; + // Mock the contract call so it returns different + // permissions than the above `prepareUninstallation` by default. + // Needed to generate different setup. + await setupUV1.mockPermissionIndexes(0, 2); - await uninstallPlugin( - psp, - targetDao.address, - proxy, - helpersUV1, - pluginRepoPointer, - EMPTY_DATA - ); - - await expect( - psp.prepareUninstallation( + await prepareUninstallation( + psp, targetDao.address, - createPrepareUninstallationParams( - proxy, - pluginRepoPointer, - helpersUV1, - EMPTY_DATA - ) - ) - ) - .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') - .withArgs( - ZERO_BYTES32, - getAppliedSetupId(pluginRepoPointer, helpersUV1) + proxy, + pluginRepoPointer, + helpersUV1, + EMPTY_DATA ); - }); - it('allows to prepare multiple uninstallation as long as setup is different', async () => { - await prepareUninstallation( - psp, - targetDao.address, - proxy, - pluginRepoPointer, - helpersUV1, - EMPTY_DATA - ); - - // Mock the contract call so it returns different - // permissions than the above `prepareUninstallation` by default. - // Needed to generate different setup. - setupUV1.prepareUninstallation.returns( - mockPermissionsOperations(0, 2, Operation.Revoke) - ); - - await prepareUninstallation( - psp, - targetDao.address, - proxy, - pluginRepoPointer, - helpersUV1, - EMPTY_DATA - ); - - // Clean up - setupUV1.prepareUninstallation.reset(); - }); - - it("successfully calls plugin setup's prepareUninstallation with correct arguments", async () => { - const data = '0x11'; - - // Reset the cache so previus tests don't trick this test that - // the function was really called, even though it mightn't have been. - // This is needed because smock contracts are not deployed in beforeEach, - // but in before, so there's only one instance of them for all tests. - setupUV1.prepareUninstallation.reset(); - - await prepareUninstallation( - psp, - targetDao.address, - proxy, - pluginRepoPointer, - helpersUV1, - data - ); - - expect(setupUV1.prepareUninstallation).to.have.been.calledWith( - targetDao.address, - [proxy, helpersUV1, data] - ); - }); - - it('successfully prepares a plugin uninstallation with the correct event arguments', async () => { - const data = '0x11'; - const uninstallPermissions = mockPermissionsOperations( - 0, - 1, - Operation.Revoke - ); + // Clean up + await setupUV1.reset(); + }); - const preparedSetupId = getPreparedSetupId( - pluginRepoPointer, - null, - // @ts-ignore - uninstallPermissions, - EMPTY_DATA, - PreparationType.Uninstallation - ); + it("successfully calls plugin setup's prepareUninstallation with correct arguments", async () => { + const data = '0x11'; - await expect( - psp.prepareUninstallation( - targetDao.address, - createPrepareUninstallationParams( - proxy, - pluginRepoPointer, - helpersUV1, - data + await expect( + psp.prepareUninstallation( + targetDao.address, + createPrepareUninstallationParams( + proxy, + pluginRepoPointer, + helpersUV1, + data + ) ) ) - ) - .to.emit(psp, 'UninstallationPrepared') - .withArgs( - ownerAddress, - targetDao.address, - preparedSetupId, - pluginRepoPointer[0], - (val: any) => expect(val).to.deep.equal([1, 1]), - (val: any) => expect(val).to.deep.equal([proxy, helpersUV1, data]), - (val: any) => expect(val).to.deep.equal(uninstallPermissions) + .to.emit(setupUV1, 'UninstallationPrepared') + .withArgs(targetDao.address, (val: any) => + expect(val).to.deep.equal([proxy, helpersUV1, data]) + ); + }); + + it('successfully prepares a plugin uninstallation with the correct event arguments', async () => { + const data = '0x11'; + const uninstallPermissions = mockPermissionsOperations( + 0, + 1, + Operation.Revoke ); - }); - }); - describe('applyUninstallation', function () { - it('reverts if caller does not have `APPLY_UNINSTALLATION_PERMISSION`', async () => { - // revoke `APPLY_INSTALLATION_PERMISSION_ID` on dao for plugin installer - // to see that it can't set permissions without it. - await targetDao.revoke( - psp.address, - ownerAddress, - APPLY_UNINSTALLATION_PERMISSION_ID - ); + const preparedSetupId = getPreparedSetupId( + pluginRepoPointer, + null, + // @ts-ignore + uninstallPermissions, + EMPTY_DATA, + PreparationType.Uninstallation + ); - await expect( - psp.applyUninstallation( - targetDao.address, - createApplyUninstallationParams( - proxy, - pluginRepoPointer, - permissionsUV1 + await expect( + psp.prepareUninstallation( + targetDao.address, + createPrepareUninstallationParams( + proxy, + pluginRepoPointer, + helpersUV1, + data + ) ) ) - ) - .to.be.revertedWithCustomError(psp, 'SetupApplicationUnauthorized') - .withArgs( - targetDao.address, + .to.emit(psp, 'UninstallationPrepared') + .withArgs( + ownerAddress, + targetDao.address, + preparedSetupId, + pluginRepoPointer[0], + (val: any) => expect(val).to.deep.equal([1, 1]), + (val: any) => + expect(val).to.deep.equal([proxy, helpersUV1, data]), + (val: any) => expect(val).to.deep.equal(uninstallPermissions) + ); + }); + }); + + describe('applyUninstallation', function () { + it('reverts if caller does not have `APPLY_UNINSTALLATION_PERMISSION`', async () => { + // revoke `APPLY_INSTALLATION_PERMISSION_ID` on dao for plugin installer + // to see that it can't set permissions without it. + await targetDao.revoke( + psp.address, ownerAddress, APPLY_UNINSTALLATION_PERMISSION_ID ); - }); - it("reverts if PluginSetupProcessor does not have DAO's `ROOT_PERMISSION`", async () => { - await targetDao.revoke( - targetDao.address, - psp.address, - ROOT_PERMISSION_ID - ); - - const {permissions} = await prepareUninstallation( - psp, - targetDao.address, - proxy, - pluginRepoPointer, - helpersUV1, - EMPTY_DATA - ); - - await expect( - psp.applyUninstallation( - targetDao.address, - createApplyUninstallationParams( - proxy, - pluginRepoPointer, - permissions + await expect( + psp.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + permissionsUV1 + ) ) ) - ) - .to.be.revertedWithCustomError(targetDao, 'Unauthorized') - .withArgs(targetDao.address, psp.address, ROOT_PERMISSION_ID); - }); + .to.be.revertedWithCustomError(psp, 'SetupApplicationUnauthorized') + .withArgs( + targetDao.address, + ownerAddress, + APPLY_UNINSTALLATION_PERMISSION_ID + ); + }); - it('reverts if uninstallation is not prepared first', async () => { - const preparedSetupId = getPreparedSetupId( - pluginRepoPointer, - null, - permissionsUV1, - EMPTY_DATA, - PreparationType.Uninstallation - ); - await expect( - psp.applyUninstallation( + it("reverts if PluginSetupProcessor does not have DAO's `ROOT_PERMISSION`", async () => { + await targetDao.revoke( targetDao.address, - createApplyUninstallationParams( - proxy, - pluginRepoPointer, - permissionsUV1 - ) - ) - ) - .to.be.revertedWithCustomError(psp, 'SetupNotApplicable') - .withArgs(preparedSetupId); - }); + psp.address, + ROOT_PERMISSION_ID + ); - it('EDGE-CASE: reverts for all uninstall preparations once one of them is applied', async () => { - // First Preparation - const {permissions: firstPreparePermissions} = - await prepareUninstallation( + const {permissions} = await prepareUninstallation( psp, targetDao.address, proxy, @@ -1182,1033 +1160,1230 @@ describe('Plugin Setup Processor', function () { EMPTY_DATA ); - // Confirm that first preparation can be applied. - await expect( - psp.callStatic.applyUninstallation( - targetDao.address, - createApplyUninstallationParams( - proxy, - pluginRepoPointer, - firstPreparePermissions + await expect( + psp.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + permissions + ) ) ) - ).not.to.be.reverted; - - // mock the function so it returns different permissions - // Needed to make sure second preparation results in different setup id and not reverts. - setupUV1.prepareUninstallation.returns( - mockPermissionsOperations(0, 2, Operation.Grant) - ); + .to.be.revertedWithCustomError(targetDao, 'Unauthorized') + .withArgs(targetDao.address, psp.address, ROOT_PERMISSION_ID); + }); - // Second Preparation - const {permissions: secondPreparePermissions} = - await prepareUninstallation( - psp, - targetDao.address, - proxy, + it('reverts if uninstallation is not prepared first', async () => { + const preparedSetupId = getPreparedSetupId( pluginRepoPointer, - helpersUV1, - EMPTY_DATA + null, + permissionsUV1, + EMPTY_DATA, + PreparationType.Uninstallation ); + await expect( + psp.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + permissionsUV1 + ) + ) + ) + .to.be.revertedWithCustomError(psp, 'SetupNotApplicable') + .withArgs(preparedSetupId); + }); - // Check that second preparation can be applied. - await expect( - psp.callStatic.applyUninstallation( - targetDao.address, - createApplyUninstallationParams( + it('EDGE-CASE: reverts for all uninstall preparations once one of them is applied', async () => { + // First Preparation + const {permissions: firstPreparePermissions} = + await prepareUninstallation( + psp, + targetDao.address, proxy, pluginRepoPointer, - secondPreparePermissions + helpersUV1, + EMPTY_DATA + ); + + // Confirm that first preparation can be applied. + await expect( + psp.callStatic.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + firstPreparePermissions + ) ) - ) - ).not.to.be.reverted; + ).not.to.be.reverted; - // apply uninstall for first preparation - await applyUninstallation( - psp, - targetDao.address, - proxy, - pluginRepoPointer, - firstPreparePermissions - ); + // mock the function so it returns different permissions + // Needed to make sure second preparation results in different setup id and not reverts. + await setupUV1.mockPermissionIndexes(0, 2); - // Confirm that the none of the preparations can be applied anymore. - await expect( - psp.applyUninstallation( - targetDao.address, - createApplyUninstallationParams( + // Second Preparation + const {permissions: secondPreparePermissions} = + await prepareUninstallation( + psp, + targetDao.address, proxy, pluginRepoPointer, - firstPreparePermissions + helpersUV1, + EMPTY_DATA + ); + + // Check that second preparation can be applied. + await expect( + psp.callStatic.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + secondPreparePermissions + ) ) - ) - ).to.be.revertedWithCustomError(psp, 'SetupNotApplicable'); + ).not.to.be.reverted; - await expect( - psp.applyUninstallation( + // apply uninstall for first preparation + await applyUninstallation( + psp, targetDao.address, - createApplyUninstallationParams( - proxy, - pluginRepoPointer, - secondPreparePermissions + proxy, + pluginRepoPointer, + firstPreparePermissions + ); + + // Confirm that the none of the preparations can be applied anymore. + await expect( + psp.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + firstPreparePermissions + ) ) - ) - ).to.be.revertedWithCustomError(psp, 'SetupNotApplicable'); - }); + ).to.be.revertedWithCustomError(psp, 'SetupNotApplicable'); - it('successfully uninstalls the plugin and emits the correct event', async () => { - const {permissions, preparedSetupId} = await prepareUninstallation( - psp, - targetDao.address, - proxy, - pluginRepoPointer, - helpersUV1, - EMPTY_DATA - ); + await expect( + psp.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + secondPreparePermissions + ) + ) + ).to.be.revertedWithCustomError(psp, 'SetupNotApplicable'); - await expect( - psp.applyUninstallation( + await setupUV1.reset(); + }); + + it('successfully uninstalls the plugin and emits the correct event', async () => { + const {permissions, preparedSetupId} = await prepareUninstallation( + psp, targetDao.address, - createApplyUninstallationParams( - proxy, - pluginRepoPointer, - permissions + proxy, + pluginRepoPointer, + helpersUV1, + EMPTY_DATA + ); + + await expect( + psp.applyUninstallation( + targetDao.address, + createApplyUninstallationParams( + proxy, + pluginRepoPointer, + permissions + ) ) ) - ) - .to.emit(psp, 'UninstallationApplied') - .withArgs(targetDao.address, proxy, preparedSetupId); + .to.emit(psp, 'UninstallationApplied') + .withArgs(targetDao.address, proxy, preparedSetupId); + }); }); }); - }); - - describe('Update', function () { - beforeEach(async () => { - // Grant necessary permission to `ownerAddress` so it can install and update plugins on behalf of the DAO. - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_INSTALLATION_PERMISSION_ID - ); - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_UPDATE_PERMISSION_ID - ); - }); - - describe('prepareUpdate', function () { - let proxy: string; - let helpersUV1: string[]; - let permissionsUV1: PermissionOperation[]; - let pluginRepoPointer: PluginRepoPointer; - let currentAppliedSetupId: string; - const currentVersion: VersionTag = [1, 1]; // Installs with this in beforeEach below. - const newVersion: VersionTag = [1, 2]; + describe('Update', function () { beforeEach(async () => { - pluginRepoPointer = [repoU.address, ...currentVersion]; - - ({ - plugin: proxy, - helpers: helpersUV1, - permissions: permissionsUV1, - appliedSetupId: currentAppliedSetupId, - } = await installPlugin(psp, targetDao.address, pluginRepoPointer)); + // Grant necessary permission to `ownerAddress` so it can install and update plugins on behalf of the DAO. + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_INSTALLATION_PERMISSION_ID + ); + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_UPDATE_PERMISSION_ID + ); }); - it('reverts if plugin does not support `IPlugin` interface', async () => { - const currentVersion: VersionTag = [1, 2]; - const newVersion: VersionTag = [1, 3]; + describe('prepareUpdate', function () { + let proxy: string; + let helpersUV1: string[]; + let permissionsUV1: PermissionOperation[]; + let pluginRepoPointer: PluginRepoPointer; + let currentAppliedSetupId: string; + const currentVersion: VersionTag = [1, 1]; // Installs with this in beforeEach below. + const newVersion: VersionTag = [1, 2]; - // Uses build 2 that doesn't support IPlugin which is an invalid state. - const pluginRepoPointer: PluginRepoPointer = [ - repoC.address, - ...currentVersion, - ]; + beforeEach(async () => { + pluginRepoPointer = [repoU.address, ...currentVersion]; - const {plugin, helpers} = await installPlugin( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - ); + ({ + plugin: proxy, + helpers: helpersUV1, + permissions: permissionsUV1, + appliedSetupId: currentAppliedSetupId, + } = await installPlugin(psp, targetDao.address, pluginRepoPointer)); + }); - await expect( - psp.prepareUpdate( - targetDao.address, - createPrepareUpdateParams( - plugin, - currentVersion, - newVersion, - pluginRepoPointer[0], - helpers, - EMPTY_DATA - ) - ) - ) - .to.be.revertedWithCustomError(psp, 'IPluginNotSupported') - .withArgs(plugin); - }); + it('reverts if plugin does not support `IPlugin` interface', async () => { + const currentVersion: VersionTag = [1, 2]; + const newVersion: VersionTag = [1, 3]; - it('reverts if plugin supports the `IPlugin` interface, but is non-upgradable', async () => { - let pluginRepoPointer: PluginRepoPointer = [ - repoC.address, - ...currentVersion, - ]; + // Uses build 2 that doesn't support IPlugin which is an invalid state. + const pluginRepoPointer: PluginRepoPointer = [ + repoC.address, + ...currentVersion, + ]; - const {plugin: pluginCloneable, helpers: helpersUV1} = - await installPlugin( + const {plugin, helpers} = await installPlugin( psp, targetDao.address, pluginRepoPointer, EMPTY_DATA ); - const newVersion: VersionTag = [1, 2]; - - await expect( - psp.prepareUpdate( - targetDao.address, - createPrepareUpdateParams( - pluginCloneable, - currentVersion, - newVersion, - pluginRepoPointer[0], - helpersUV1, - EMPTY_DATA + await expect( + psp.prepareUpdate( + targetDao.address, + createPrepareUpdateParams( + plugin, + currentVersion, + newVersion, + pluginRepoPointer[0], + helpers, + EMPTY_DATA + ) ) ) - ) - .to.be.revertedWithCustomError(psp, 'PluginNonupgradeable') - .withArgs(pluginCloneable); - }); + .to.be.revertedWithCustomError(psp, 'IPluginNotSupported') + .withArgs(plugin); + }); + + it('reverts if plugin supports the `IPlugin` interface, but is non-upgradable', async () => { + let pluginRepoPointer: PluginRepoPointer = [ + repoC.address, + ...currentVersion, + ]; + + const {plugin: pluginCloneable, helpers: helpersUV1} = + await installPlugin( + psp, + targetDao.address, + pluginRepoPointer, + EMPTY_DATA + ); + + const newVersion: VersionTag = [1, 2]; - it('reverts if release numbers differ or new build is less than or equal to current build', async () => { - const revert = async ( - currentVersionTag: [number, number], - newVersionTag: [number, number] - ) => { await expect( psp.prepareUpdate( targetDao.address, createPrepareUpdateParams( - ownerAddress, - currentVersionTag, - newVersionTag, - ownerAddress, + pluginCloneable, + currentVersion, + newVersion, + pluginRepoPointer[0], helpersUV1, EMPTY_DATA ) ) - ).to.be.revertedWithCustomError(psp, 'InvalidUpdateVersion'); - }; + ) + .to.be.revertedWithCustomError(psp, 'PluginNonupgradeable') + .withArgs(pluginCloneable); + }); - await revert([1, 1], [2, 2]); - await revert([1, 1], [2, 1]); - await revert([1, 1], [1, 1]); - }); + it('reverts if release numbers differ or new build is less than or equal to current build', async () => { + const revert = async ( + currentVersionTag: [number, number], + newVersionTag: [number, number] + ) => { + await expect( + psp.prepareUpdate( + targetDao.address, + createPrepareUpdateParams( + ownerAddress, + currentVersionTag, + newVersionTag, + ownerAddress, + helpersUV1, + EMPTY_DATA + ) + ) + ).to.be.revertedWithCustomError(psp, 'InvalidUpdateVersion'); + }; - it('reverts if plugin is not installed', async () => { - const pluginRepoPointer: PluginRepoPointer = [ - repoU.address, - ...currentVersion, - ]; + await revert([1, 1], [2, 2]); + await revert([1, 1], [2, 1]); + await revert([1, 1], [1, 1]); + }); - await expect( - psp.prepareUpdate( - targetDao.address, - createPrepareUpdateParams( - ownerAddress, - currentVersion, - newVersion, - pluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ) - ) - ) - .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') - .withArgs( - ZERO_BYTES32, - getAppliedSetupId(pluginRepoPointer, helpersUV1) - ); - }); + it('reverts if plugin is not installed', async () => { + const pluginRepoPointer: PluginRepoPointer = [ + repoU.address, + ...currentVersion, + ]; - it('reverts if prepare update params do not match the current `appliedSetupId`', async () => { - { - // Run the prepare update with modified helpers. - const modifiedHelpers = [...helpersUV1].slice(0, -1); await expect( psp.prepareUpdate( targetDao.address, createPrepareUpdateParams( - proxy, - currentVersion, // current installed version - newVersion, // new version + ownerAddress, + currentVersion, + newVersion, pluginRepoPointer[0], - modifiedHelpers, + helpersUV1, EMPTY_DATA ) ) ) .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') .withArgs( - currentAppliedSetupId, - getAppliedSetupId(pluginRepoPointer, modifiedHelpers) + ZERO_BYTES32, + getAppliedSetupId(pluginRepoPointer, helpersUV1) ); - } - { - // Change helpers's sequence which still should still cause revert. - const modifiedHelpers = [...helpersUV1].reverse(); + }); + + it('reverts if prepare update params do not match the current `appliedSetupId`', async () => { + { + // Run the prepare update with modified helpers. + const modifiedHelpers = [...helpersUV1].slice(0, -1); + await expect( + psp.prepareUpdate( + targetDao.address, + createPrepareUpdateParams( + proxy, + currentVersion, // current installed version + newVersion, // new version + pluginRepoPointer[0], + modifiedHelpers, + EMPTY_DATA + ) + ) + ) + .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') + .withArgs( + currentAppliedSetupId, + getAppliedSetupId(pluginRepoPointer, modifiedHelpers) + ); + } + { + // Change helpers's sequence which still should still cause revert. + const modifiedHelpers = [...helpersUV1].reverse(); + await expect( + psp.prepareUpdate( + targetDao.address, + createPrepareUpdateParams( + proxy, + currentVersion, // current installed version + newVersion, // new version + pluginRepoPointer[0], + modifiedHelpers, + EMPTY_DATA + ) + ) + ) + .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') + .withArgs( + currentAppliedSetupId, + getAppliedSetupId(pluginRepoPointer, modifiedHelpers) + ); + } + + { + // Modify it so it believes the current version is newVersion + // which should cause revert. + const modifiedPluginRepoPointer: PluginRepoPointer = [ + pluginRepoPointer[0], + ...newVersion, + ]; + + await expect( + psp.prepareUpdate( + targetDao.address, + createPrepareUpdateParams( + proxy, + newVersion, + [newVersion[0], newVersion[1] + 1], // increase version so it doesn't fail with invalid version update. + pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + ) + ) + ) + .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') + .withArgs( + currentAppliedSetupId, + getAppliedSetupId(modifiedPluginRepoPointer, helpersUV1) + ); + } + }); + + it('reverts if same setup is already prepared', async () => { + const {preparedSetupId} = await prepareUpdate( + psp, + targetDao.address, + proxy, + currentVersion, + newVersion, + pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + ); + await expect( psp.prepareUpdate( targetDao.address, createPrepareUpdateParams( proxy, - currentVersion, // current installed version - newVersion, // new version + currentVersion, + newVersion, pluginRepoPointer[0], - modifiedHelpers, + helpersUV1, EMPTY_DATA ) ) ) - .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') - .withArgs( - currentAppliedSetupId, - getAppliedSetupId(pluginRepoPointer, modifiedHelpers) - ); - } + .to.be.revertedWithCustomError(psp, 'SetupAlreadyPrepared') + .withArgs(preparedSetupId); + }); + + it('allows to prepare multiple update as long as setup is different', async () => { + await prepareUpdate( + psp, + targetDao.address, + proxy, + currentVersion, + newVersion, + pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + ); - { - // Modify it so it believes the current version is newVersion - // which should cause revert. - const modifiedPluginRepoPointer: PluginRepoPointer = [ + // change prepare update of plugin setup so it returns different struct + // to make sure different setup id is generated. + await setupUV2.mockHelperCount(1); + + await prepareUpdate( + psp, + targetDao.address, + proxy, + currentVersion, + newVersion, pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + ); + + // clean up + await setupUV2.reset(); + }); + + it('correctly prepares updates when plugin setups are same, but UI different', async () => { + // plugin setup addresses are the same, so it treats it as UIs are different. + const currentVersion: VersionTag = [1, 5]; + const newVersion: VersionTag = [1, 6]; + + const currentPluginRepoPointer: PluginRepoPointer = [ + repoU.address, + ...currentVersion, + ]; + const newPluginRepoPointer: PluginRepoPointer = [ + repoU.address, ...newVersion, ]; + const {plugin: proxy, helpers} = await installPlugin( + psp, + targetDao.address, + currentPluginRepoPointer + ); + + const tx = psp.prepareUpdate( + targetDao.address, + createPrepareUpdateParams( + proxy, + currentVersion, + newVersion, + currentPluginRepoPointer[0], + helpers, + EMPTY_DATA + ) + ); + await expect(tx).to.not.emit(setupUV4, 'UpdatePrepared'); + await expect(tx).to.emit(psp, EVENTS.UpdatePrepared); + }); + + it("successfully calls plugin setup's prepareUpdate with correct arguments", async () => { + const data = '0x11'; + + await expect( + psp.prepareUninstallation( + targetDao.address, + createPrepareUninstallationParams( + proxy, + pluginRepoPointer, + helpersUV1, + data + ) + ) + ) + .to.emit(setupUV1, 'UninstallationPrepared') + .withArgs(targetDao.address, (val: any) => + expect(val).to.deep.equal([proxy, helpersUV1, data]) + ); + }); + + it('successfully prepares update and emits the correct arguments', async () => { + // Helpers,permissions and initData are + // what `newVersion`'s prepareUpdate is supposed to return. + const expectedHelpers = mockHelpers(2); + const expectedPermissions = mockPermissionsOperations( + 1, + 2, + Operation.Grant + ); + const initData = '0xe27e9a4e'; + + const preparedSetupId = getPreparedSetupId( + [pluginRepoPointer[0], ...newVersion], + expectedHelpers, + // @ts-ignore + expectedPermissions, + initData, + PreparationType.Update + ); + await expect( psp.prepareUpdate( targetDao.address, createPrepareUpdateParams( proxy, + currentVersion, newVersion, - [newVersion[0], newVersion[1] + 1], // increase version so it doesn't fail with invalid version update. pluginRepoPointer[0], helpersUV1, EMPTY_DATA ) ) ) - .to.be.revertedWithCustomError(psp, 'InvalidAppliedSetupId') + .to.emit(psp, 'UpdatePrepared') .withArgs( - currentAppliedSetupId, - getAppliedSetupId(modifiedPluginRepoPointer, helpersUV1) + ownerAddress, + targetDao.address, + preparedSetupId, + pluginRepoPointer[0], + (val: any) => expect(val).to.deep.equal(newVersion), + (val: any) => + expect(val).to.deep.equal([proxy, helpersUV1, EMPTY_DATA]), + (val: any) => + expect(val).to.deep.equal([ + expectedHelpers, + expectedPermissions, + ]), + initData ); - } + }); }); + }); + + describe('applyUpdate', function () { + let proxy: string; + let helpersUV1: string[]; + let permissionsUV1: PermissionOperation[]; + + let currentPluginRepoPointer: PluginRepoPointer; + let newPluginRepoPointer: PluginRepoPointer; + let currentVersion: VersionTag = [1, 1]; // plugin's version it initially installs. + let newVersion: VersionTag = [1, 2]; + + let currentAppliedSetupId: string; + + beforeEach(async () => { + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_INSTALLATION_PERMISSION_ID + ); + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_UPDATE_PERMISSION_ID + ); + + currentPluginRepoPointer = [repoU.address, ...currentVersion]; + newPluginRepoPointer = [repoU.address, ...newVersion]; - it('reverts if same setup is already prepared', async () => { - const {preparedSetupId} = await prepareUpdate( + ({ + plugin: proxy, + helpers: helpersUV1, + permissions: permissionsUV1, + appliedSetupId: currentAppliedSetupId, + } = await installPlugin( psp, targetDao.address, - proxy, - currentVersion, - newVersion, - pluginRepoPointer[0], - helpersUV1, + currentPluginRepoPointer, EMPTY_DATA + )); + + await targetDao.grant(proxy, psp.address, UPGRADE_PLUGIN_PERMISSION_ID); + }); + + it('reverts if caller does not have `APPLY_UPDATE_PERMISSION` permission', async () => { + await targetDao.revoke( + psp.address, + ownerAddress, + APPLY_UPDATE_PERMISSION_ID ); await expect( - psp.prepareUpdate( + psp.applyUpdate( targetDao.address, - createPrepareUpdateParams( + createApplyUpdateParams( proxy, - currentVersion, - newVersion, - pluginRepoPointer[0], - helpersUV1, - EMPTY_DATA + currentPluginRepoPointer, + EMPTY_DATA, + permissionsUV1, + helpersUV1 ) ) ) - .to.be.revertedWithCustomError(psp, 'SetupAlreadyPrepared') - .withArgs(preparedSetupId); + .to.be.revertedWithCustomError(psp, 'SetupApplicationUnauthorized') + .withArgs( + targetDao.address, + ownerAddress, + APPLY_UPDATE_PERMISSION_ID + ); }); - it('allows to prepare multiple update as long as setup is different', async () => { - await prepareUpdate( + it("reverts if PluginSetupProcessor does not have DAO's `ROOT_PERMISSION`", async () => { + await targetDao.revoke( + targetDao.address, + psp.address, + ROOT_PERMISSION_ID + ); + + const { + preparedSetupData: {permissions, helpers}, + initData, + } = await prepareUpdate( psp, targetDao.address, proxy, currentVersion, newVersion, - pluginRepoPointer[0], + currentPluginRepoPointer[0], helpersUV1, EMPTY_DATA ); - // change prepare update of plugin setup so it returns different struct - // to make sure different setup id is generated. - setupUV2.prepareUpdate.returns([ - EMPTY_DATA, - [mockHelpers(1), mockPermissionsOperations(0, 2, Operation.Grant)], // changed - ]); + await expect( + psp.applyUpdate( + targetDao.address, + createApplyUpdateParams( + proxy, + newPluginRepoPointer, + initData, + permissions, + helpers + ) + ) + ) + .to.be.revertedWithCustomError(targetDao, 'Unauthorized') + .withArgs(targetDao.address, psp.address, ROOT_PERMISSION_ID); + }); + + it('reverts if the plugin setup processor does not have the `UPGRADE_PLUGIN_PERMISSION_ID` permission', async () => { + await targetDao.revoke( + proxy, + psp.address, + UPGRADE_PLUGIN_PERMISSION_ID + ); - await prepareUpdate( + const { + preparedSetupData: {permissions, helpers}, + initData, + } = await prepareUpdate( psp, targetDao.address, proxy, currentVersion, newVersion, - pluginRepoPointer[0], + currentPluginRepoPointer[0], helpersUV1, EMPTY_DATA ); - // clean up - setupUV2.prepareUpdate.reset(); + await expect( + psp.applyUpdate( + targetDao.address, + createApplyUpdateParams( + proxy, + newPluginRepoPointer, + initData, + permissions, + helpers + ) + ) + ) + .to.be.revertedWithCustomError(psp, 'PluginProxyUpgradeFailed') + .withArgs( + proxy, + await setupUV2.callStatic.implementation(), + initData + ); }); - it('correctly prepares updates when plugin setups are same, but UI different', async () => { - // plugin setup addresses are the same, so it treats it as UIs are different. - const currentVersion: VersionTag = [1, 5]; - const newVersion: VersionTag = [1, 6]; - - const currentPluginRepoPointer: PluginRepoPointer = [ - repoU.address, - ...currentVersion, - ]; - const newPluginRepoPointer: PluginRepoPointer = [ - repoU.address, - ...newVersion, - ]; - - const {plugin: proxy, helpers} = await installPlugin( - psp, - targetDao.address, - currentPluginRepoPointer - ); - + it('reverts if preparation has not happened yet for update', async () => { const preparedSetupId = getPreparedSetupId( - newPluginRepoPointer, - helpers, - [], + currentPluginRepoPointer, + helpersUV1, + permissionsUV1, EMPTY_DATA, PreparationType.Update ); + await expect( + psp.applyUpdate( + targetDao.address, + createApplyUpdateParams( + proxy, + currentPluginRepoPointer, + EMPTY_DATA, + permissionsUV1, + helpersUV1 + ) + ) + ) + .to.be.revertedWithCustomError(psp, 'SetupNotApplicable') + .withArgs(preparedSetupId); + }); - const event = await prepareUpdate( + it('EDGE-CASE: reverts for both preparations once one of them gets applied', async () => { + // Prepare first which updates to `newVersion` + const firstPreparationNewVersion = newVersion; + const { + preparedSetupData: { + permissions: firstPreparationPermissions, + helpers: firstPreparationHelpers, + }, + initData: firstPreparationInitData, + } = await prepareUpdate( psp, targetDao.address, proxy, currentVersion, - newVersion, + firstPreparationNewVersion, currentPluginRepoPointer[0], - helpers, + helpersUV1, EMPTY_DATA ); - // Makes sure it correctly generated setup id. - expect(event.preparedSetupId).to.equal(preparedSetupId); - - // When there's UI update, prepareUpdate of plugin setup must not be called. - expect(setupUV4.prepareUpdate).to.have.callCount(0); - }); - - it("successfully calls plugin setup's prepareUpdate with correct arguments", async () => { - const data = '0x11'; - - // Reset the cache so previus tests don't trick this test that - // the function was really called, even though it mightn't have been. - // This is needed because smock contracts are not deployed in beforeEach, - // but in before, so there's only one instance of them for all tests. - setupUV2.prepareUpdate.reset(); - - await prepareUpdate( + // Prepare second which updates to +1 build number than `newVersion`. + const secondPreparationNewVersion: VersionTag = [ + newVersion[0], + newVersion[1] + 1, + ]; + const { + preparedSetupData: { + permissions: secondPreparationPermissions, + helpers: secondPreparationHelpers, + }, + initData: secondPreparationInitData, + preparedSetupId, + } = await prepareUpdate( psp, targetDao.address, proxy, currentVersion, - newVersion, - pluginRepoPointer[0], + secondPreparationNewVersion, + currentPluginRepoPointer[0], helpersUV1, - data + EMPTY_DATA ); - expect(setupUV2.prepareUpdate).to.have.been.calledWith( - targetDao.address, - 1, // build - [proxy, helpersUV1, data] - ); - }); + await expect( + psp.callStatic.applyUpdate( + targetDao.address, + createApplyUpdateParams( + proxy, + newPluginRepoPointer, + firstPreparationInitData, + firstPreparationPermissions, + firstPreparationHelpers + ) + ) + ).not.to.be.reverted; - it('successfully prepares update and emits the correct arguments', async () => { - // Helpers,permissions and initData are - // what `newVersion`'s prepareUpdate is supposed to return. - const expectedHelpers = mockHelpers(2); - const expectedPermissions = mockPermissionsOperations( - 1, - 2, - Operation.Grant - ); - const initData = '0xe27e9a4e'; + await expect( + psp.callStatic.applyUpdate( + targetDao.address, + createApplyUpdateParams( + proxy, + [ + newPluginRepoPointer[0], + secondPreparationNewVersion[0], + secondPreparationNewVersion[1], + ], + secondPreparationInitData, + secondPreparationPermissions, + secondPreparationHelpers + ) + ) + ).not.to.be.reverted; - const preparedSetupId = getPreparedSetupId( - [pluginRepoPointer[0], ...newVersion], - expectedHelpers, - // @ts-ignore - expectedPermissions, - initData, - PreparationType.Update + // Apply one of the preparation + await applyUpdate( + psp, + targetDao.address, + proxy, + [ + newPluginRepoPointer[0], + secondPreparationNewVersion[0], + secondPreparationNewVersion[1], + ], + secondPreparationInitData, + secondPreparationPermissions, + secondPreparationHelpers ); + // confirm that now preparations can't be applied anymore await expect( - psp.prepareUpdate( + psp.applyUpdate( targetDao.address, - createPrepareUpdateParams( + createApplyUpdateParams( proxy, - currentVersion, - newVersion, - pluginRepoPointer[0], - helpersUV1, - EMPTY_DATA + newPluginRepoPointer, + firstPreparationInitData, + firstPreparationPermissions, + firstPreparationHelpers ) ) - ) - .to.emit(psp, 'UpdatePrepared') - .withArgs( - ownerAddress, + ).to.be.reverted; + await expect( + psp.applyUpdate( targetDao.address, - preparedSetupId, - pluginRepoPointer[0], - (val: any) => expect(val).to.deep.equal(newVersion), - (val: any) => - expect(val).to.deep.equal([proxy, helpersUV1, EMPTY_DATA]), - (val: any) => - expect(val).to.deep.equal([expectedHelpers, expectedPermissions]), - initData - ); - }); - }); - }); - - describe('applyUpdate', function () { - let proxy: string; - let helpersUV1: string[]; - let permissionsUV1: PermissionOperation[]; - - let currentPluginRepoPointer: PluginRepoPointer; - let newPluginRepoPointer: PluginRepoPointer; - let currentVersion: VersionTag = [1, 1]; // plugin's version it initially installs. - let newVersion: VersionTag = [1, 2]; - - let currentAppliedSetupId: string; - - beforeEach(async () => { - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_INSTALLATION_PERMISSION_ID - ); - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_UPDATE_PERMISSION_ID - ); - - currentPluginRepoPointer = [repoU.address, ...currentVersion]; - newPluginRepoPointer = [repoU.address, ...newVersion]; - - ({ - plugin: proxy, - helpers: helpersUV1, - permissions: permissionsUV1, - appliedSetupId: currentAppliedSetupId, - } = await installPlugin( - psp, - targetDao.address, - currentPluginRepoPointer, - EMPTY_DATA - )); - - await targetDao.grant(proxy, psp.address, UPGRADE_PLUGIN_PERMISSION_ID); - }); - - it('reverts if caller does not have `APPLY_UPDATE_PERMISSION` permission', async () => { - await targetDao.revoke( - psp.address, - ownerAddress, - APPLY_UPDATE_PERMISSION_ID - ); - - await expect( - psp.applyUpdate( - targetDao.address, - createApplyUpdateParams( - proxy, - currentPluginRepoPointer, - EMPTY_DATA, - permissionsUV1, - helpersUV1 + createApplyUpdateParams( + proxy, + [ + newPluginRepoPointer[0], + secondPreparationNewVersion[0], + secondPreparationNewVersion[1], + ], + secondPreparationInitData, + secondPreparationPermissions, + secondPreparationHelpers + ) ) - ) - ) - .to.be.revertedWithCustomError(psp, 'SetupApplicationUnauthorized') - .withArgs(targetDao.address, ownerAddress, APPLY_UPDATE_PERMISSION_ID); - }); + ).to.be.reverted; + }); - it("reverts if PluginSetupProcessor does not have DAO's `ROOT_PERMISSION`", async () => { - await targetDao.revoke( - targetDao.address, - psp.address, - ROOT_PERMISSION_ID - ); + describe('Whether upgrade functions of proxy get called the right way', async () => { + it('correctly applies updates when plugin setups are same, but UI different', async () => { + // plugin setup addresses are the same, so it treats it as UIs are different. + const currentV: VersionTag = [1, 5]; + const newV: VersionTag = [1, 6]; + const currentPluginRepoPointer: PluginRepoPointer = [ + repoU.address, + 1, + 5, + ]; - const { - preparedSetupData: {permissions, helpers}, - initData, - } = await prepareUpdate( - psp, - targetDao.address, - proxy, - currentVersion, - newVersion, - currentPluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ); + const {plugin, helpers} = await installPlugin( + psp, + targetDao.address, + currentPluginRepoPointer + ); - await expect( - psp.applyUpdate( - targetDao.address, - createApplyUpdateParams( - proxy, - newPluginRepoPointer, + const { initData, - permissions, - helpers - ) - ) - ) - .to.be.revertedWithCustomError(targetDao, 'Unauthorized') - .withArgs(targetDao.address, psp.address, ROOT_PERMISSION_ID); - }); + preparedSetupData: {permissions, helpers: helpersUpdate}, + } = await prepareUpdate( + psp, + targetDao.address, + plugin, + currentV, + newV, + repoU.address, + helpers, + EMPTY_DATA + ); - it('reverts if the plugin setup processor does not have the `UPGRADE_PLUGIN_PERMISSION_ID` permission', async () => { - await targetDao.revoke(proxy, psp.address, UPGRADE_PLUGIN_PERMISSION_ID); + const pluginInstance = PluginUUPSUpgradeable__factory.connect( + plugin, + signers[0] + ); - const { - preparedSetupData: {permissions, helpers}, - initData, - } = await prepareUpdate( - psp, - targetDao.address, - proxy, - currentVersion, - newVersion, - currentPluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ); + await expect( + psp.applyUpdate( + targetDao.address, + createApplyUpdateParams( + plugin, + [repoU.address, ...newV], + initData, + permissions, + helpersUpdate + ) + ) + ).to.not.emit(pluginInstance, 'Upgraded'); + }); - await expect( - psp.applyUpdate( - targetDao.address, - createApplyUpdateParams( - proxy, - newPluginRepoPointer, + it('successfully calls `upgradeToAndCall` on plugin if initData was provided by pluginSetup', async () => { + const { initData, - permissions, - helpers - ) - ) - ) - .to.be.revertedWithCustomError(psp, 'PluginProxyUpgradeFailed') - .withArgs(proxy, await setupUV2.callStatic.implementation(), initData); - }); - - it('reverts if preparation has not happened yet for update', async () => { - const preparedSetupId = getPreparedSetupId( - currentPluginRepoPointer, - helpersUV1, - permissionsUV1, - EMPTY_DATA, - PreparationType.Update - ); - await expect( - psp.applyUpdate( - targetDao.address, - createApplyUpdateParams( - proxy, - currentPluginRepoPointer, - EMPTY_DATA, - permissionsUV1, - helpersUV1 - ) - ) - ) - .to.be.revertedWithCustomError(psp, 'SetupNotApplicable') - .withArgs(preparedSetupId); - }); - - it('EDGE-CASE: reverts for both preparations once one of them gets applied', async () => { - // Prepare first which updates to `newVersion` - const firstPreparationNewVersion = newVersion; - const { - preparedSetupData: { - permissions: firstPreparationPermissions, - helpers: firstPreparationHelpers, - }, - initData: firstPreparationInitData, - } = await prepareUpdate( - psp, - targetDao.address, - proxy, - currentVersion, - firstPreparationNewVersion, - currentPluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ); - - // Prepare second which updates to +1 build number than `newVersion`. - const secondPreparationNewVersion: VersionTag = [ - newVersion[0], - newVersion[1] + 1, - ]; - const { - preparedSetupData: { - permissions: secondPreparationPermissions, - helpers: secondPreparationHelpers, - }, - initData: secondPreparationInitData, - preparedSetupId, - } = await prepareUpdate( - psp, - targetDao.address, - proxy, - currentVersion, - secondPreparationNewVersion, - currentPluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ); - - await expect( - psp.callStatic.applyUpdate( - targetDao.address, - createApplyUpdateParams( + preparedSetupData: {permissions, helpers: helpersUpdate}, + } = await prepareUpdate( + psp, + targetDao.address, proxy, - newPluginRepoPointer, - firstPreparationInitData, - firstPreparationPermissions, - firstPreparationHelpers - ) - ) - ).not.to.be.reverted; + currentVersion, + newVersion, + repoU.address, + helpersUV1, + EMPTY_DATA + ); - await expect( - psp.callStatic.applyUpdate( - targetDao.address, - createApplyUpdateParams( + const pluginInstance = PluginUUPSUpgradeable__factory.connect( proxy, - [ - newPluginRepoPointer[0], - secondPreparationNewVersion[0], - secondPreparationNewVersion[1], - ], - secondPreparationInitData, - secondPreparationPermissions, - secondPreparationHelpers - ) - ) - ).not.to.be.reverted; - - // Apply one of the preparation - await applyUpdate( - psp, - targetDao.address, - proxy, - [ - newPluginRepoPointer[0], - secondPreparationNewVersion[0], - secondPreparationNewVersion[1], - ], - secondPreparationInitData, - secondPreparationPermissions, - secondPreparationHelpers - ); + signers[0] + ); + const newImpl = await setupUV2.implementation(); - // confirm that now preparations can't be applied anymore - await expect( - psp.applyUpdate( - targetDao.address, - createApplyUpdateParams( - proxy, - newPluginRepoPointer, - firstPreparationInitData, - firstPreparationPermissions, - firstPreparationHelpers - ) - ) - ).to.be.reverted; - await expect( - psp.applyUpdate( - targetDao.address, - createApplyUpdateParams( - proxy, - [ - newPluginRepoPointer[0], - secondPreparationNewVersion[0], - secondPreparationNewVersion[1], - ], - secondPreparationInitData, - secondPreparationPermissions, - secondPreparationHelpers + await expect( + psp.applyUpdate( + targetDao.address, + createApplyUpdateParams( + proxy, + [repoU.address, ...newVersion], + initData, + permissions, + helpersUpdate + ) + ) ) - ) - ).to.be.reverted; - }); - - describe('Whether upgrade functions of proxy get called the right way', async () => { - let fake: any; - - beforeEach(async () => { - // create a fake on the same plugin(proxy) address. - fake = await smock.fake(pluginUUPSUpgradeableArtifact, { - address: proxy, + .to.emit(pluginInstance, 'Upgraded') + .withArgs(newImpl); }); - - // Since smock fake will end up having functions that always returns - // Solidity default values, the below overrides them with the correct data - // So when PSP calls it, it can expect the same information as if it would call - // the normal/original `proxy/plugin`. - fake.supportsInterface.whenCalledWith('0xffffffff').returns(false); - fake.supportsInterface.whenCalledWith('0x01ffc9a7').returns(true); - fake.supportsInterface.whenCalledWith('0x41de6830').returns(true); - }); - - it('correctly applies updates when plugin setups are same, but UI different', async () => { - // plugin setup addresses are the same, so it treats it as UIs are different. - const currentV: VersionTag = [1, 5]; - const newV: VersionTag = [1, 6]; - const currentPluginRepoPointer: PluginRepoPointer = [ - repoU.address, - 1, - 5, - ]; - - const {plugin, helpers} = await installPlugin( - psp, - targetDao.address, - currentPluginRepoPointer - ); - - await updatePlugin( - psp, - targetDao.address, - plugin, - currentV, - newV, - repoU.address, - helpers, - EMPTY_DATA - ); - - expect(fake.upgradeTo).to.have.callCount(0); - expect(fake.upgradeToAndCall).to.have.callCount(0); }); - it('successfully calls `upgradeToAndCall` on plugin if initData was provided by pluginSetup', async () => { - const {initData} = await updatePlugin( + it('successfuly updates and emits the correct event arguments', async () => { + const { + preparedSetupId, + initData, + preparedSetupData: {permissions, helpers}, + } = await prepareUpdate( psp, targetDao.address, proxy, currentVersion, - newVersion, // uses setupUV2 - repoU.address, + newVersion, + currentPluginRepoPointer[0], helpersUV1, EMPTY_DATA ); - const newImpl = await setupUV2.implementation(); - expect(fake.upgradeToAndCall).to.have.been.calledWith( - newImpl, - initData + const appliedSetupId = getAppliedSetupId(newPluginRepoPointer, helpers); + + await expect( + psp.applyUpdate( + targetDao.address, + createApplyUpdateParams( + proxy, + newPluginRepoPointer, + initData, + permissions, + helpers + ) + ) + ) + .to.emit(psp, 'UpdateApplied') + .withArgs(targetDao.address, proxy, preparedSetupId, appliedSetupId); + }); + }); + + describe('Update scenarios', function () { + beforeEach(async () => { + // Grant necessary permission to `ownerAddress` so it can install and upadate plugins on behalf of the DAO. + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_INSTALLATION_PERMISSION_ID + ); + await targetDao.grant( + psp.address, + ownerAddress, + APPLY_UPDATE_PERMISSION_ID ); }); - it('successfully calls `upgradeTo` on plugin if no initData was provided', async () => { - setupUV2.prepareUpdate.returns([ - EMPTY_DATA, - [mockHelpers(1), mockPermissionsOperations(0, 1, Operation.Grant)], - ]); + context(`V1 was installed`, function () { + let proxy: string; + let helpersUV1: string[]; + let permissionsUV1: PermissionOperation[]; - const {initData} = await updatePlugin( - psp, - targetDao.address, - proxy, - currentVersion, - newVersion, // uses setupUV2 - repoU.address, - helpersUV1, - EMPTY_DATA - ); + let currentVersion: VersionTag = [1, 1]; + let pluginRepoPointer: PluginRepoPointer; - // Reset so other tests continue using the default/original data - setupUV2.prepareUpdate.reset(); + beforeEach(async () => { + pluginRepoPointer = [repoU.address, 1, 1]; - const newImpl = await setupUV2.implementation(); - expect(fake.upgradeTo).to.have.been.calledWith(newImpl); - }); - }); + ({ + plugin: proxy, + helpers: helpersUV1, + permissions: permissionsUV1, + } = await installPlugin( + psp, + targetDao.address, + pluginRepoPointer, + EMPTY_DATA + )); - it('successfuly updates and emits the correct event arguments', async () => { - const { - preparedSetupId, - initData, - preparedSetupData: {permissions, helpers}, - } = await prepareUpdate( - psp, - targetDao.address, - proxy, - currentVersion, - newVersion, - currentPluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ); + await targetDao.grant( + proxy, + psp.address, + UPGRADE_PLUGIN_PERMISSION_ID + ); + }); + + it('points to the V1 implementation', async () => { + expect( + await PluginUV1.attach(proxy).callStatic.implementation() + ).to.equal(await setupUV1.callStatic.implementation()); + }); - const appliedSetupId = getAppliedSetupId(newPluginRepoPointer, helpers); + it('initializes the members', async () => { + expect(await PluginUV1.attach(proxy).state1()).to.equal(1); + }); - await expect( - psp.applyUpdate( - targetDao.address, - createApplyUpdateParams( + it('sets the V1 helpers', async () => { + expect(helpersUV1).to.deep.equal(mockHelpers(2)); + }); + + it('sets the V1 permissions', async () => { + expect(permissionsUV1).to.deep.equal( + mockPermissionsOperations(0, 2, Operation.Grant) + ); + }); + + it('updates to V2: Contract was actually updated', async () => { + await updateAndValidatePluginUpdate( + psp, + targetDao.address, + proxy, + currentVersion, + [1, 2], + pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + ); + }); + + it('updates to V3', async () => { + await updateAndValidatePluginUpdate( + psp, + targetDao.address, proxy, - newPluginRepoPointer, - initData, - permissions, - helpers - ) - ) - ) - .to.emit(psp, 'UpdateApplied') - .withArgs(targetDao.address, proxy, preparedSetupId, appliedSetupId); - }); - }); + currentVersion, + [1, 3], + pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + ); + }); - describe('Update scenarios', function () { - beforeEach(async () => { - // Grant necessary permission to `ownerAddress` so it can install and upadate plugins on behalf of the DAO. - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_INSTALLATION_PERMISSION_ID - ); - await targetDao.grant( - psp.address, - ownerAddress, - APPLY_UPDATE_PERMISSION_ID - ); - }); + context(`and updated to V2`, function () { + let helpersV2: string[]; + let permissionsUV1V2: PermissionOperation[]; + let initDataV1V2: BytesLike; + let currentVersion: VersionTag = [1, 2]; - context(`V1 was installed`, function () { - let proxy: string; - let helpersUV1: string[]; - let permissionsUV1: PermissionOperation[]; + beforeEach(async () => { + ({ + updatedHelpers: helpersV2, + permissions: permissionsUV1V2, + initData: initDataV1V2, + } = await updatePlugin( + psp, + targetDao.address, + proxy, + [1, 1], + [1, 2], + pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + )); + }); - let currentVersion: VersionTag = [1, 1]; - let pluginRepoPointer: PluginRepoPointer; + it('points to the V2 implementation', async () => { + expect( + await PluginUV2.attach(proxy).callStatic.implementation() + ).to.equal(await setupUV2.callStatic.implementation()); + }); - beforeEach(async () => { - pluginRepoPointer = [repoU.address, 1, 1]; + it('initializes the members', async () => { + expect(await PluginUV2.attach(proxy).state1()).to.equal(1); + expect(await PluginUV2.attach(proxy).state2()).to.equal(2); + }); - ({ - plugin: proxy, - helpers: helpersUV1, - permissions: permissionsUV1, - } = await installPlugin( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - )); + it('sets the V2 helpers', async () => { + expect(helpersV2).to.deep.equal(mockHelpers(2)); + }); - await targetDao.grant(proxy, psp.address, UPGRADE_PLUGIN_PERMISSION_ID); - }); + it('sets the V1 to V2 permissions', async () => { + expect(permissionsUV1V2).to.deep.equal( + mockPermissionsOperations(1, 2, Operation.Grant).map(perm => + Object.values(perm) + ) + ); + }); - it('points to the V1 implementation', async () => { - expect( - await PluginUV1.attach(proxy).callStatic.implementation() - ).to.equal(await setupUV1.callStatic.implementation()); - }); + it('updates to V3', async () => { + await updateAndValidatePluginUpdate( + psp, + targetDao.address, + proxy, + currentVersion, + [1, 3], + pluginRepoPointer[0], + helpersV2, + EMPTY_DATA + ); + }); - it('initializes the members', async () => { - expect(await PluginUV1.attach(proxy).state1()).to.equal(1); - }); + context(`and updated to V3`, function () { + let helpersV3: string[]; + let permissionsV2V3: PermissionOperation[]; + let initDataV2V3: BytesLike; + + beforeEach(async () => { + ({ + updatedHelpers: helpersV3, + permissions: permissionsV2V3, + initData: initDataV2V3, + } = await updatePlugin( + psp, + targetDao.address, + proxy, + [1, 2], + [1, 3], + pluginRepoPointer[0], + helpersV2, + EMPTY_DATA + )); + }); + + it('points to the V3 implementation', async () => { + expect( + await PluginUV3.attach(proxy).callStatic.implementation() + ).to.equal(await setupUV3.callStatic.implementation()); + }); + + it('initializes the members', async () => { + expect(await PluginUV3.attach(proxy).state1()).to.equal(1); + expect(await PluginUV3.attach(proxy).state2()).to.equal(2); + expect(await PluginUV3.attach(proxy).state3()).to.equal(3); + }); + + it('sets the V3 helpers', async () => { + expect(helpersV3).to.deep.equal(mockHelpers(3)); + }); + + it('sets the V2 to V3 permissions', async () => { + expect(permissionsV2V3).to.deep.equal( + mockPermissionsOperations(2, 3, Operation.Grant).map(perm => + Object.values(perm) + ) + ); + }); + }); + }); + context(`and updated to V3`, function () { + let helpersV3: string[]; + let permissionsUV1V3: PermissionOperation[]; + let initDataV1V3: BytesLike; - it('sets the V1 helpers', async () => { - expect(helpersUV1).to.deep.equal(mockHelpers(2)); - }); + beforeEach(async () => { + ({ + updatedHelpers: helpersV3, + permissions: permissionsUV1V3, + initData: initDataV1V3, + } = await updatePlugin( + psp, + targetDao.address, + proxy, + [1, 1], + [1, 3], + pluginRepoPointer[0], + helpersUV1, + EMPTY_DATA + )); + }); - it('sets the V1 permissions', async () => { - expect(permissionsUV1).to.deep.equal( - mockPermissionsOperations(0, 2, Operation.Grant) - ); - }); + it('points to the V3 implementation', async () => { + expect( + await PluginUV3.attach(proxy).callStatic.implementation() + ).to.equal(await setupUV3.callStatic.implementation()); + }); - it('updates to V2: Contract was actually updated', async () => { - await updateAndValidatePluginUpdate( - psp, - targetDao.address, - proxy, - currentVersion, - [1, 2], - pluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ); - }); + it('initializes the members', async () => { + expect(await PluginUV3.attach(proxy).state1()).to.equal(1); + expect(await PluginUV3.attach(proxy).state2()).to.equal(2); + expect(await PluginUV3.attach(proxy).state3()).to.equal(3); + }); - it('updates to V3', async () => { - await updateAndValidatePluginUpdate( - psp, - targetDao.address, - proxy, - currentVersion, - [1, 3], - pluginRepoPointer[0], - helpersUV1, - EMPTY_DATA - ); + it('sets the V3 helpers', async () => { + expect(helpersV3).to.deep.equal(mockHelpers(3)); + }); + + it('sets the V1 to V3 permissions', async () => { + expect(permissionsUV1V3).to.deep.equal( + mockPermissionsOperations(1, 3, Operation.Grant).map(perm => + Object.values(perm) + ) + ); + }); + }); }); - context(`and updated to V2`, function () { + context(`V2 was installed`, function () { + let proxy: string; let helpersV2: string[]; - let permissionsUV1V2: PermissionOperation[]; - let initDataV1V2: BytesLike; - let currentVersion: VersionTag = [1, 2]; - + let permissionsV2: PermissionOperation[]; + let pluginRepoPointer: PluginRepoPointer; beforeEach(async () => { + pluginRepoPointer = [repoU.address, 1, 2]; ({ - updatedHelpers: helpersV2, - permissions: permissionsUV1V2, - initData: initDataV1V2, - } = await updatePlugin( + plugin: proxy, + helpers: helpersV2, + permissions: permissionsV2, + } = await installPlugin( psp, targetDao.address, - proxy, - [1, 1], - [1, 2], - pluginRepoPointer[0], - helpersUV1, + pluginRepoPointer, EMPTY_DATA )); + + await targetDao.grant( + proxy, + psp.address, + UPGRADE_PLUGIN_PERMISSION_ID + ); }); it('points to the V2 implementation', async () => { @@ -2226,9 +2401,9 @@ describe('Plugin Setup Processor', function () { expect(helpersV2).to.deep.equal(mockHelpers(2)); }); - it('sets the V1 to V2 permissions', async () => { - expect(permissionsUV1V2).to.deep.equal( - mockPermissionsOperations(1, 2, Operation.Grant).map(perm => + it('sets the V2 permissions', async () => { + expect(permissionsV2).to.deep.equal( + mockPermissionsOperations(0, 2, Operation.Grant).map(perm => Object.values(perm) ) ); @@ -2239,7 +2414,7 @@ describe('Plugin Setup Processor', function () { psp, targetDao.address, proxy, - currentVersion, + [1, 2], [1, 3], pluginRepoPointer[0], helpersV2, @@ -2294,26 +2469,31 @@ describe('Plugin Setup Processor', function () { }); }); }); - context(`and updated to V3`, function () { - let helpersV3: string[]; - let permissionsUV1V3: PermissionOperation[]; - let initDataV1V3: BytesLike; + context(`V3 was installed`, function () { + let proxy: string; + let helpersV3: string[]; + let permissionsV3: PermissionOperation[]; + let pluginRepoPointer: PluginRepoPointer; beforeEach(async () => { + pluginRepoPointer = [repoU.address, 1, 3]; + ({ - updatedHelpers: helpersV3, - permissions: permissionsUV1V3, - initData: initDataV1V3, - } = await updatePlugin( + plugin: proxy, + helpers: helpersV3, + permissions: permissionsV3, + } = await installPlugin( psp, targetDao.address, - proxy, - [1, 1], - [1, 3], - pluginRepoPointer[0], - helpersUV1, + pluginRepoPointer, EMPTY_DATA )); + + await targetDao.grant( + proxy, + psp.address, + UPGRADE_PLUGIN_PERMISSION_ID + ); }); it('points to the V3 implementation', async () => { @@ -2332,242 +2512,90 @@ describe('Plugin Setup Processor', function () { expect(helpersV3).to.deep.equal(mockHelpers(3)); }); - it('sets the V1 to V3 permissions', async () => { - expect(permissionsUV1V3).to.deep.equal( - mockPermissionsOperations(1, 3, Operation.Grant).map(perm => + it('sets the V3 permissions', async () => { + expect(permissionsV3).to.deep.equal( + mockPermissionsOperations(0, 3, Operation.Grant).map(perm => Object.values(perm) ) ); }); - }); - }); - - context(`V2 was installed`, function () { - let proxy: string; - let helpersV2: string[]; - let permissionsV2: PermissionOperation[]; - let pluginRepoPointer: PluginRepoPointer; - beforeEach(async () => { - pluginRepoPointer = [repoU.address, 1, 2]; - ({ - plugin: proxy, - helpers: helpersV2, - permissions: permissionsV2, - } = await installPlugin( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - )); - - await targetDao.grant(proxy, psp.address, UPGRADE_PLUGIN_PERMISSION_ID); - }); - - it('points to the V2 implementation', async () => { - expect( - await PluginUV2.attach(proxy).callStatic.implementation() - ).to.equal(await setupUV2.callStatic.implementation()); - }); - - it('initializes the members', async () => { - expect(await PluginUV2.attach(proxy).state1()).to.equal(1); - expect(await PluginUV2.attach(proxy).state2()).to.equal(2); - }); - - it('sets the V2 helpers', async () => { - expect(helpersV2).to.deep.equal(mockHelpers(2)); - }); - - it('sets the V2 permissions', async () => { - expect(permissionsV2).to.deep.equal( - mockPermissionsOperations(0, 2, Operation.Grant).map(perm => - Object.values(perm) - ) - ); - }); - it('updates to V3', async () => { - await updateAndValidatePluginUpdate( - psp, - targetDao.address, - proxy, - [1, 2], - [1, 3], - pluginRepoPointer[0], - helpersV2, - EMPTY_DATA - ); - }); - - context(`and updated to V3`, function () { - let helpersV3: string[]; - let permissionsV2V3: PermissionOperation[]; - let initDataV2V3: BytesLike; - - beforeEach(async () => { - ({ - updatedHelpers: helpersV3, - permissions: permissionsV2V3, - initData: initDataV2V3, - } = await updatePlugin( + // Special case where implementations from old and new setups don't change. + it('updates to v5', async () => { + await updateAndValidatePluginUpdate( psp, targetDao.address, proxy, - [1, 2], [1, 3], + [1, 5], pluginRepoPointer[0], - helpersV2, + helpersV3, EMPTY_DATA - )); - }); - - it('points to the V3 implementation', async () => { - expect( - await PluginUV3.attach(proxy).callStatic.implementation() - ).to.equal(await setupUV3.callStatic.implementation()); - }); - - it('initializes the members', async () => { - expect(await PluginUV3.attach(proxy).state1()).to.equal(1); - expect(await PluginUV3.attach(proxy).state2()).to.equal(2); - expect(await PluginUV3.attach(proxy).state3()).to.equal(3); - }); - - it('sets the V3 helpers', async () => { - expect(helpersV3).to.deep.equal(mockHelpers(3)); - }); - - it('sets the V2 to V3 permissions', async () => { - expect(permissionsV2V3).to.deep.equal( - mockPermissionsOperations(2, 3, Operation.Grant).map(perm => - Object.values(perm) - ) ); }); }); }); + }); - context(`V3 was installed`, function () { - let proxy: string; - let helpersV3: string[]; - let permissionsV3: PermissionOperation[]; - let pluginRepoPointer: PluginRepoPointer; - beforeEach(async () => { - pluginRepoPointer = [repoU.address, 1, 3]; - - ({ - plugin: proxy, - helpers: helpersV3, - permissions: permissionsV3, - } = await installPlugin( - psp, - targetDao.address, - pluginRepoPointer, - EMPTY_DATA - )); - - await targetDao.grant(proxy, psp.address, UPGRADE_PLUGIN_PERMISSION_ID); - }); - - it('points to the V3 implementation', async () => { - expect( - await PluginUV3.attach(proxy).callStatic.implementation() - ).to.equal(await setupUV3.callStatic.implementation()); - }); - - it('initializes the members', async () => { - expect(await PluginUV3.attach(proxy).state1()).to.equal(1); - expect(await PluginUV3.attach(proxy).state2()).to.equal(2); - expect(await PluginUV3.attach(proxy).state3()).to.equal(3); - }); + async function updateAndValidatePluginUpdate( + psp: PluginSetupProcessor, + targetDao: string, + proxy: string, + currentVersionTag: VersionTag, + newVersionTag: VersionTag, + pluginRepo: string, + currentHelpers: string[], + data: BytesLike = EMPTY_DATA + ) { + await updatePlugin( + psp, + targetDao, + proxy, + currentVersionTag, + newVersionTag, + pluginRepo, + currentHelpers, + data + ); - it('sets the V3 helpers', async () => { - expect(helpersV3).to.deep.equal(mockHelpers(3)); - }); + const signers = await ethers.getSigners(); - it('sets the V3 permissions', async () => { - expect(permissionsV3).to.deep.equal( - mockPermissionsOperations(0, 3, Operation.Grant).map(perm => - Object.values(perm) - ) - ); - }); + const PluginRepoFactory = new PluginRepo__factory(signers[0]); + const repo = PluginRepoFactory.attach(pluginRepo); - // Special case where implementations from old and new setups don't change. - it('updates to v5', async () => { - await updateAndValidatePluginUpdate( - psp, - targetDao.address, - proxy, - [1, 3], - [1, 5], - pluginRepoPointer[0], - helpersV3, - EMPTY_DATA - ); - }); + const currentVersion = await repo['getVersion((uint8,uint16))']({ + release: currentVersionTag[0], + build: currentVersionTag[1], + }); + const newVersion = await repo['getVersion((uint8,uint16))']({ + release: newVersionTag[0], + build: newVersionTag[1], }); - }); -}); - -async function updateAndValidatePluginUpdate( - psp: PluginSetupProcessor, - targetDao: string, - proxy: string, - currentVersionTag: VersionTag, - newVersionTag: VersionTag, - pluginRepo: string, - currentHelpers: string[], - data: BytesLike = EMPTY_DATA -) { - await updatePlugin( - psp, - targetDao, - proxy, - currentVersionTag, - newVersionTag, - pluginRepo, - currentHelpers, - data - ); - - const signers = await ethers.getSigners(); - - const PluginRepoFactory = new PluginRepo__factory(signers[0]); - const repo = PluginRepoFactory.attach(pluginRepo); - - const currentVersion = await repo['getVersion((uint8,uint16))']({ - release: currentVersionTag[0], - build: currentVersionTag[1], - }); - const newVersion = await repo['getVersion((uint8,uint16))']({ - release: newVersionTag[0], - build: newVersionTag[1], - }); - const PluginSetupFactory = new PluginUUPSUpgradeableSetupV1Mock__factory( - signers[0] - ); - - const currentPluginSetup = PluginSetupFactory.attach( - currentVersion.pluginSetup - ); - const newPluginSetup = PluginSetupFactory.attach(newVersion.pluginSetup); - - // If the base contracts don't change from current and new plugin setups, - // PluginSetupProcessor shouldn't call `upgradeTo` or `upgradeToAndCall` - // on the plugin. The below check for this still is not 100% ensuring, - // As function `upgradeTo` might be called but event `Upgraded` - // not thrown(OZ changed the logic or name) which will trick the test to pass.. - const currentImpl = await currentPluginSetup.implementation(); - const newImpl = await newPluginSetup.implementation(); - - if (currentImpl != newImpl) { - const proxyContract = PluginUUPSUpgradeable__factory.connect( - proxy, + const PluginSetupFactory = new PluginUUPSUpgradeableSetupV1Mock__factory( signers[0] ); - expect(await proxyContract.implementation()).to.equal(newImpl); + const currentPluginSetup = PluginSetupFactory.attach( + currentVersion.pluginSetup + ); + const newPluginSetup = PluginSetupFactory.attach(newVersion.pluginSetup); + + // If the base contracts don't change from current and new plugin setups, + // PluginSetupProcessor shouldn't call `upgradeTo` or `upgradeToAndCall` + // on the plugin. The below check for this still is not 100% ensuring, + // As function `upgradeTo` might be called but event `Upgraded` + // not thrown(OZ changed the logic or name) which will trick the test to pass.. + const currentImpl = await currentPluginSetup.implementation(); + const newImpl = await newPluginSetup.implementation(); + + if (currentImpl != newImpl) { + const proxyContract = PluginUUPSUpgradeable__factory.connect( + proxy, + signers[0] + ); + + expect(await proxyContract.implementation()).to.equal(newImpl); + } } -} +}); diff --git a/packages/contracts/test/framework/utils/ens/ens-subdomain-registry.ts b/packages/contracts/test/framework/utils/ens/ens-subdomain-registry.ts index ee90ffe62..7006f100c 100644 --- a/packages/contracts/test/framework/utils/ens/ens-subdomain-registry.ts +++ b/packages/contracts/test/framework/utils/ens/ens-subdomain-registry.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {artifacts, ethers} from 'hardhat'; import {ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -8,13 +8,10 @@ import { DAO, PublicResolver, ENSRegistry, - ENSRegistry__factory, - PublicResolver__factory, ENSSubdomainRegistrar__factory, } from '../../../../typechain'; import {ENSSubdomainRegistrar__factory as ENSSubdomainRegistrar_V1_0_0__factory} from '../../../../typechain/@aragon/osx-v1.0.1/framework/utils/ens/ENSSubdomainRegistrar.sol'; -import {deployWithProxy} from '../../../test-utils/proxy'; import {deployNewDAO} from '../../../test-utils/dao'; import {ensDomainHash, ensLabelHash} from '../../../../utils/ens'; import {OZ_ERRORS} from '../../../test-utils/error'; @@ -25,6 +22,7 @@ import { ozUpgradeCheckManagedContract, } from '../../../test-utils/uups-upgradeable'; import {CURRENT_PROTOCOL_VERSION} from '../../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../../test-utils/wrapper'; const REGISTER_ENS_SUBDOMAIN_PERMISSION_ID = ethers.utils.id( 'REGISTER_ENS_SUBDOMAIN_PERMISSION' @@ -34,28 +32,23 @@ const REGISTER_ENS_SUBDOMAIN_PERMISSION_ID = ethers.utils.id( async function setupENS( owner: SignerWithAddress ): Promise<[ENSRegistry, PublicResolver, DAO, ENSSubdomainRegistrar]> { - const ENSRegistry = new ENSRegistry__factory(owner); - const PublicResolver = new PublicResolver__factory(owner); - const ENSSubdomainRegistrar = new ENSSubdomainRegistrar__factory(owner); - // Deploy the ENSRegistry - const ens = await ENSRegistry.deploy(); - await ens.deployed(); + const ens = await hre.wrapper.deploy('ENSRegistry'); // Deploy the Resolver - const resolver = await PublicResolver.deploy( - ens.address, - ethers.constants.AddressZero - ); - await resolver.deployed(); + const resolver = await hre.wrapper.deploy('PublicResolver', { + args: [ens.address, ethers.constants.AddressZero], + }); + await setupResolver(ens, resolver, owner); // Deploy the managing DAO const dao = await deployNewDAO(owner); // Deploy the registrar - const registrar = await deployWithProxy( - ENSSubdomainRegistrar + const registrar = await hre.wrapper.deploy( + ARTIFACT_SOURCES.ENS_SUBDOMAIN_REGISTRAR, + {withProxy: true} ); return [ens, resolver, dao, registrar]; @@ -236,7 +229,7 @@ describe('ENSSubdomainRegistrar', function () { it('reverts if the approval of the registrar is removed', async () => { // Initialize the registrar with the 'test' domain - registrar.initialize( + await registrar.initialize( managingDao.address, ens.address, ensDomainHash('test') @@ -304,20 +297,21 @@ describe('ENSSubdomainRegistrar', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, managingDao, { - managingDao: managingDao.address, - ens: ens.address, - parentDomain: ensDomainHash('test'), + initArgs: { + managingDao: managingDao.address, + ens: ens.address, + parentDomain: ensDomainHash('test'), + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.ENS_SUBDOMAIN_REGISTRAR_V1_0_0, + ARTIFACT_SOURCES.ENS_SUBDOMAIN_REGISTRAR, UPGRADE_PERMISSIONS.UPGRADE_REGISTRAR_PERMISSION_ID ); - expect(toImplementation).to.equal(fromImplementation); // The implementation was not changed from 1.0.0 to the current version const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) diff --git a/packages/contracts/test/framework/utils/interface-based-registry.ts b/packages/contracts/test/framework/utils/interface-based-registry.ts index f8f6459b3..066210620 100644 --- a/packages/contracts/test/framework/utils/interface-based-registry.ts +++ b/packages/contracts/test/framework/utils/interface-based-registry.ts @@ -1,17 +1,15 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {deployWithProxy} from '../../test-utils/proxy'; import { DAO, IDAO__factory, InterfaceBasedRegistryMock, - InterfaceBasedRegistryMock__factory, - PluginRepo__factory, } from '../../../typechain'; import {deployNewDAO} from '../../test-utils/dao'; import {getInterfaceID} from '../../test-utils/interfaces'; +import {ARTIFACT_SOURCES} from '../../test-utils/wrapper'; const REGISTER_PERMISSION_ID = ethers.utils.id('REGISTER_PERMISSION'); @@ -34,12 +32,9 @@ describe('InterfaceBasedRegistry', function () { }); beforeEach(async () => { - const InterfaceBasedRegistryMock = new InterfaceBasedRegistryMock__factory( - signers[0] - ); - - interfaceBasedRegistryMock = await deployWithProxy( - InterfaceBasedRegistryMock + interfaceBasedRegistryMock = await hre.wrapper.deploy( + 'InterfaceBasedRegistryMock', + {withProxy: true} ); // Let the interface registry register `DAO` contracts for testing purposes @@ -70,8 +65,9 @@ describe('InterfaceBasedRegistry', function () { it('fail to register if the interface is not supported', async () => { // Use the `PluginRepo` contract for testing purposes here, because the interface differs from the `DAO` interface - const PluginRepo = new PluginRepo__factory(signers[0]); - let contractNotBeingADao = await PluginRepo.deploy(); + let contractNotBeingADao = await hre.wrapper.deploy( + ARTIFACT_SOURCES.PLUGIN_REPO + ); await expect( interfaceBasedRegistryMock.register(contractNotBeingADao.address) diff --git a/packages/contracts/test/framework/utils/registry-utils.ts b/packages/contracts/test/framework/utils/registry-utils.ts index 56a907c4a..2f23cb5a8 100644 --- a/packages/contracts/test/framework/utils/registry-utils.ts +++ b/packages/contracts/test/framework/utils/registry-utils.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {RegistryUtils, RegistryUtils__factory} from '../../../typechain'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -12,8 +12,7 @@ describe('RegistryUtils', () => { }); beforeEach(async () => { - const RegistryUtilsFactory = new RegistryUtils__factory(signers[0]); - registryUtilsContract = await RegistryUtilsFactory.deploy(); + registryUtilsContract = await hre.wrapper.deploy('RegistryUtils'); }); describe('isSubdomainValid', () => { diff --git a/packages/contracts/test/framework/utils/token-factory.ts b/packages/contracts/test/framework/utils/token-factory.ts index 0781b388d..bcf2493ac 100644 --- a/packages/contracts/test/framework/utils/token-factory.ts +++ b/packages/contracts/test/framework/utils/token-factory.ts @@ -51,7 +51,7 @@ interface MintConfig { const zeroAddr = ethers.constants.AddressZero; -describe('Core: TokenFactory', () => { +describe.skip('Core: TokenFactory', () => { let signers: SignerWithAddress[]; let tokenFactory: MockContract; let governanceBase: MockContract; diff --git a/packages/contracts/test/plugins/counter-example/counter-plugin-setup.ts b/packages/contracts/test/plugins/counter-example/counter-plugin-setup.ts index 0aaf2f90a..fdc511bdb 100644 --- a/packages/contracts/test/plugins/counter-example/counter-plugin-setup.ts +++ b/packages/contracts/test/plugins/counter-example/counter-plugin-setup.ts @@ -1,6 +1,6 @@ import {expect} from 'chai'; import {BigNumberish} from 'ethers'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { @@ -41,11 +41,9 @@ describe('CounterPluginSetup(Example)', function () { address1 = await signers[1].getAddress(); address2 = await signers[2].getAddress(); - const DAOMock = new DAOMock__factory(signers[0]); - daoMock = await DAOMock.deploy(ownerAddress); + daoMock = await hre.wrapper.deploy('DAOMock', {args: [ownerAddress]}); - const CounterV1Setup = new CounterV1PluginSetup__factory(signers[0]); - counterV1Setup = await CounterV1Setup.deploy(); + counterV1Setup = await hre.wrapper.deploy('CounterV1PluginSetup'); const counterV1 = CounterV1__factory.connect( await counterV1Setup.multiplyHelperBase(), @@ -55,11 +53,11 @@ describe('CounterPluginSetup(Example)', function () { implementationAddress = await counterV1Setup.implementation(); - const MultiplyHelper = new MultiplyHelper__factory(signers[0]); - multiplyHelper = await MultiplyHelper.deploy(); + multiplyHelper = await hre.wrapper.deploy('MultiplyHelper'); - const CounterV2Setup = new CounterV2PluginSetup__factory(signers[0]); - counterV2Setup = await CounterV2Setup.deploy(multiplyHelper.address); + counterV2Setup = await hre.wrapper.deploy('CounterV2PluginSetup', { + args: [multiplyHelper.address], + }); }); describe('prepareInstallation', async () => { diff --git a/packages/contracts/test/plugins/governance/admin/admin-setup.ts b/packages/contracts/test/plugins/governance/admin/admin-setup.ts index c3d7eb67a..b892f16bb 100644 --- a/packages/contracts/test/plugins/governance/admin/admin-setup.ts +++ b/packages/contracts/test/plugins/governance/admin/admin-setup.ts @@ -12,6 +12,7 @@ import {getInterfaceID} from '../../../test-utils/interfaces'; import metadata from '../../../../src/plugins/governance/admin/build-metadata.json'; import {adminInterface} from './admin'; import {getNamedTypesFromMetadata} from '../../../../utils/metadata'; +import {skipTestSuiteIfNetworkIsZkSync} from '../../../test-utils/skip-functions'; const abiCoder = ethers.utils.defaultAbiCoder; const AddressZero = ethers.constants.AddressZero; @@ -23,161 +24,163 @@ const EXECUTE_PROPOSAL_PERMISSION_ID = ethers.utils.id( ); const EXECUTE_PERMISSION_ID = ethers.utils.id('EXECUTE_PERMISSION'); -describe('AdminSetup', function () { - let ownerAddress: string; - let signers: any; - let adminSetup: AdminSetup; - let implementationAddress: string; - let targetDao: any; - let minimum_data: any; - - before(async () => { - signers = await ethers.getSigners(); - ownerAddress = await signers[0].getAddress(); - targetDao = await deployNewDAO(signers[0]); - - minimum_data = abiCoder.encode( - getNamedTypesFromMetadata( - metadata.pluginSetup.prepareInstallation.inputs - ), - [ownerAddress] - ); - - const AdminSetup = new AdminSetup__factory(signers[0]); - adminSetup = await AdminSetup.deploy(); - - implementationAddress = await adminSetup.implementation(); - }); +skipTestSuiteIfNetworkIsZkSync('AdminSetup', async () => { + describe('AdminSetup', function () { + let ownerAddress: string; + let signers: any; + let adminSetup: AdminSetup; + let implementationAddress: string; + let targetDao: any; + let minimum_data: any; + + before(async () => { + signers = await ethers.getSigners(); + ownerAddress = await signers[0].getAddress(); + targetDao = await deployNewDAO(signers[0]); + + minimum_data = abiCoder.encode( + getNamedTypesFromMetadata( + metadata.pluginSetup.prepareInstallation.inputs + ), + [ownerAddress] + ); - it('does not support the empty interface', async () => { - expect(await adminSetup.supportsInterface('0xffffffff')).to.be.false; - }); + const AdminSetup = new AdminSetup__factory(signers[0]); + adminSetup = await AdminSetup.deploy(); - it('creates admin address base with the correct interface', async () => { - const factory = new Admin__factory(signers[0]); - const adminAddressContract = factory.attach(implementationAddress); + implementationAddress = await adminSetup.implementation(); + }); - expect( - await adminAddressContract.supportsInterface( - getInterfaceID(adminInterface) - ) - ).to.be.eq(true); - }); + it('does not support the empty interface', async () => { + expect(await adminSetup.supportsInterface('0xffffffff')).to.be.false; + }); - describe('prepareInstallation', async () => { - it('fails if data is empty, or not of minimum length', async () => { - await expect( - adminSetup.prepareInstallation(targetDao.address, EMPTY_DATA) - ).to.be.reverted; + it('creates admin address base with the correct interface', async () => { + const factory = new Admin__factory(signers[0]); + const adminAddressContract = factory.attach(implementationAddress); - await expect( - adminSetup.prepareInstallation( - targetDao.address, - minimum_data.substring(0, minimum_data.length - 2) + expect( + await adminAddressContract.supportsInterface( + getInterfaceID(adminInterface) ) - ).to.be.reverted; - - await expect( - adminSetup.prepareInstallation(targetDao.address, minimum_data) - ).not.to.be.reverted; + ).to.be.eq(true); }); - it('reverts if encoded address in `_data` is zero', async () => { - const dataWithAddressZero = abiCoder.encode( - getNamedTypesFromMetadata( - metadata.pluginSetup.prepareInstallation.inputs - ), - [AddressZero] - ); + describe('prepareInstallation', async () => { + it('fails if data is empty, or not of minimum length', async () => { + await expect( + adminSetup.prepareInstallation(targetDao.address, EMPTY_DATA) + ).to.be.reverted; + + await expect( + adminSetup.prepareInstallation( + targetDao.address, + minimum_data.substring(0, minimum_data.length - 2) + ) + ).to.be.reverted; + + await expect( + adminSetup.prepareInstallation(targetDao.address, minimum_data) + ).not.to.be.reverted; + }); - await expect( - adminSetup.prepareInstallation(targetDao.address, dataWithAddressZero) - ) - .to.be.revertedWithCustomError(adminSetup, 'AdminAddressInvalid') - .withArgs(AddressZero); - }); + it('reverts if encoded address in `_data` is zero', async () => { + const dataWithAddressZero = abiCoder.encode( + getNamedTypesFromMetadata( + metadata.pluginSetup.prepareInstallation.inputs + ), + [AddressZero] + ); - it('correctly returns plugin, helpers and permissions', async () => { - const nonce = await ethers.provider.getTransactionCount( - adminSetup.address - ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: adminSetup.address, - nonce, + await expect( + adminSetup.prepareInstallation(targetDao.address, dataWithAddressZero) + ) + .to.be.revertedWithCustomError(adminSetup, 'AdminAddressInvalid') + .withArgs(AddressZero); }); - const { - plugin, - preparedSetupData: {helpers, permissions}, - } = await adminSetup.callStatic.prepareInstallation( - targetDao.address, - minimum_data - ); + it('correctly returns plugin, helpers and permissions', async () => { + const nonce = await ethers.provider.getTransactionCount( + adminSetup.address + ); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: adminSetup.address, + nonce, + }); - expect(plugin).to.be.equal(anticipatedPluginAddress); - expect(helpers.length).to.be.equal(0); - expect(permissions.length).to.be.equal(2); - expect(permissions).to.deep.equal([ - [ - Operation.Grant, + const { plugin, - ownerAddress, - AddressZero, - EXECUTE_PROPOSAL_PERMISSION_ID, - ], - [ - Operation.Grant, + preparedSetupData: {helpers, permissions}, + } = await adminSetup.callStatic.prepareInstallation( targetDao.address, - plugin, - AddressZero, - EXECUTE_PERMISSION_ID, - ], - ]); - }); + minimum_data + ); + + expect(plugin).to.be.equal(anticipatedPluginAddress); + expect(helpers.length).to.be.equal(0); + expect(permissions.length).to.be.equal(2); + expect(permissions).to.deep.equal([ + [ + Operation.Grant, + plugin, + ownerAddress, + AddressZero, + EXECUTE_PROPOSAL_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]); + }); - it('correctly sets up the plugin', async () => { - const daoAddress = targetDao.address; + it('correctly sets up the plugin', async () => { + const daoAddress = targetDao.address; - const nonce = await ethers.provider.getTransactionCount( - adminSetup.address - ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: adminSetup.address, - nonce, - }); + const nonce = await ethers.provider.getTransactionCount( + adminSetup.address + ); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: adminSetup.address, + nonce, + }); - await adminSetup.prepareInstallation(daoAddress, minimum_data); + await adminSetup.prepareInstallation(daoAddress, minimum_data); - const factory = new Admin__factory(signers[0]); - const adminAddressContract = factory.attach(anticipatedPluginAddress); + const factory = new Admin__factory(signers[0]); + const adminAddressContract = factory.attach(anticipatedPluginAddress); - expect(await adminAddressContract.dao()).to.be.equal(daoAddress); + expect(await adminAddressContract.dao()).to.be.equal(daoAddress); + }); }); - }); - - describe('prepareUninstallation', async () => { - it('correctly returns permissions', async () => { - const plugin = ethers.Wallet.createRandom().address; - const permissions = await adminSetup.callStatic.prepareUninstallation( - targetDao.address, - { - plugin, - currentHelpers: [], - data: EMPTY_DATA, - } - ); + describe('prepareUninstallation', async () => { + it('correctly returns permissions', async () => { + const plugin = ethers.Wallet.createRandom().address; - expect(permissions.length).to.be.equal(1); - expect(permissions).to.deep.equal([ - [ - Operation.Revoke, + const permissions = await adminSetup.callStatic.prepareUninstallation( targetDao.address, - plugin, - AddressZero, - EXECUTE_PERMISSION_ID, - ], - ]); + { + plugin, + currentHelpers: [], + data: EMPTY_DATA, + } + ); + + expect(permissions.length).to.be.equal(1); + expect(permissions).to.deep.equal([ + [ + Operation.Revoke, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]); + }); }); }); }); diff --git a/packages/contracts/test/plugins/governance/admin/admin.ts b/packages/contracts/test/plugins/governance/admin/admin.ts index 3ffc1a526..335d48a3a 100644 --- a/packages/contracts/test/plugins/governance/admin/admin.ts +++ b/packages/contracts/test/plugins/governance/admin/admin.ts @@ -25,6 +25,7 @@ import { } from '../../../../typechain'; import {ProposalCreatedEvent} from '../../../../typechain/Admin'; import {ExecutedEvent} from '../../../../typechain/IDAO'; +import {skipTestSuiteIfNetworkIsZkSync} from '../../../test-utils/skip-functions'; // Permissions const EXECUTE_PROPOSAL_PERMISSION_ID = ethers.utils.id( @@ -37,200 +38,150 @@ export const adminInterface = new ethers.utils.Interface([ 'function executeProposal(bytes,tuple(address,uint256,bytes)[],uint256)', ]); -describe('Admin', function () { - let signers: SignerWithAddress[]; - let plugin: any; - let adminCloneFactory: AdminCloneFactory; - let dao: any; - let ownerAddress: string; - let dummyActions: any; - let dummyMetadata: string; - - before(async () => { - signers = await ethers.getSigners(); - ownerAddress = await signers[0].getAddress(); - - dummyActions = [ - { - to: ownerAddress, - data: '0x0000', - value: 0, - }, - ]; - dummyMetadata = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('0x123456789') - ); - - dao = await deployNewDAO(signers[0]); - - const AdminCloneFactory = new AdminCloneFactory__factory(signers[0]); - adminCloneFactory = await AdminCloneFactory.deploy(); - }); - - beforeEach(async () => { - const AdminFactory = new Admin__factory(signers[0]); - - const nonce = await ethers.provider.getTransactionCount( - adminCloneFactory.address - ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: adminCloneFactory.address, - nonce, - }); - - await adminCloneFactory.deployClone(); - plugin = AdminFactory.attach(anticipatedPluginAddress); - - await dao.grant(dao.address, plugin.address, EXECUTE_PERMISSION_ID); - await dao.grant( - plugin.address, - ownerAddress, - EXECUTE_PROPOSAL_PERMISSION_ID - ); - }); - - function initializePlugin() { - return plugin.initialize(dao.address); - } - - describe('initialize: ', async () => { - it('reverts if trying to re-initialize', async () => { - await initializePlugin(); - - await expect(initializePlugin()).to.be.revertedWith( - OZ_ERRORS.ALREADY_INITIALIZED +skipTestSuiteIfNetworkIsZkSync('Admin', async () => { + describe('Admin', function () { + let signers: SignerWithAddress[]; + let plugin: any; + let adminCloneFactory: AdminCloneFactory; + let dao: any; + let ownerAddress: string; + let dummyActions: any; + let dummyMetadata: string; + + before(async () => { + signers = await ethers.getSigners(); + ownerAddress = await signers[0].getAddress(); + + dummyActions = [ + { + to: ownerAddress, + data: '0x0000', + value: 0, + }, + ]; + dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') ); - }); - - it('emits the `MembershipContractAnnounced` event and returns the admin as a member afterwards', async () => { - await expect(plugin.initialize(dao.address)) - .to.emit(plugin, MEMBERSHIP_EVENTS.MEMBERSHIP_CONTRACT_ANNOUNCED) - .withArgs(dao.address); - - expect(await plugin.isMember(signers[0].address)).to.be.true; // signer[0] has `EXECUTE_PROPOSAL_PERMISSION_ID` - expect(await plugin.isMember(signers[1].address)).to.be.false; // signer[1] has not - }); - }); - - describe('plugin interface: ', async () => { - it('does not support the empty interface', async () => { - expect(await plugin.supportsInterface('0xffffffff')).to.be.false; - }); - it('supports the `IERC165Upgradeable` interface', async () => { - const iface = IERC165Upgradeable__factory.createInterface(); - expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be.true; - }); - - it('supports the `IPlugin` interface', async () => { - const iface = IPlugin__factory.createInterface(); - expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be.true; - }); + dao = await deployNewDAO(signers[0]); - it('supports the `IProposal` interface', async () => { - const iface = IProposal__factory.createInterface(); - expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be.true; + const AdminCloneFactory = new AdminCloneFactory__factory(signers[0]); + adminCloneFactory = await AdminCloneFactory.deploy(); }); - it('supports the `IMembership` interface', async () => { - const iface = IMembership__factory.createInterface(); - expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be.true; - }); - - it('supports the `Admin` interface', async () => { - expect(await plugin.supportsInterface(getInterfaceID(adminInterface))).to - .be.true; - }); - }); - - describe('execute proposal: ', async () => { beforeEach(async () => { - await initializePlugin(); - }); + const AdminFactory = new Admin__factory(signers[0]); - it("fails to call DAO's `execute()` if `EXECUTE_PERMISSION` is not granted to the plugin address", async () => { - await dao.revoke(dao.address, plugin.address, EXECUTE_PERMISSION_ID); + const nonce = await ethers.provider.getTransactionCount( + adminCloneFactory.address + ); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: adminCloneFactory.address, + nonce, + }); - await expect(plugin.executeProposal(dummyMetadata, dummyActions, 0)) - .to.be.revertedWithCustomError(dao, 'Unauthorized') - .withArgs(dao.address, plugin.address, EXECUTE_PERMISSION_ID); - }); + await adminCloneFactory.deployClone(); + plugin = AdminFactory.attach(anticipatedPluginAddress); - it('fails to call `executeProposal()` if `EXECUTE_PROPOSAL_PERMISSION_ID` is not granted for the admin address', async () => { - await dao.revoke( + await dao.grant(dao.address, plugin.address, EXECUTE_PERMISSION_ID); + await dao.grant( plugin.address, ownerAddress, EXECUTE_PROPOSAL_PERMISSION_ID ); - - await expect(plugin.executeProposal(dummyMetadata, dummyActions, 0)) - .to.be.revertedWithCustomError(plugin, 'DaoUnauthorized') - .withArgs( - dao.address, - plugin.address, - ownerAddress, - EXECUTE_PROPOSAL_PERMISSION_ID - ); }); - it('correctly emits the ProposalCreated event', async () => { - const currentExpectedProposalId = 0; + function initializePlugin() { + return plugin.initialize(dao.address); + } - const allowFailureMap = 1; + describe('initialize: ', async () => { + it('reverts if trying to re-initialize', async () => { + await initializePlugin(); - const tx = await plugin.executeProposal( - dummyMetadata, - dummyActions, - allowFailureMap - ); - - await expect(tx).to.emit(plugin, PROPOSAL_EVENTS.PROPOSAL_CREATED); + await expect(initializePlugin()).to.be.revertedWith( + OZ_ERRORS.ALREADY_INITIALIZED + ); + }); - const event = await findEvent( - tx, - PROPOSAL_EVENTS.PROPOSAL_CREATED - ); + it('emits the `MembershipContractAnnounced` event and returns the admin as a member afterwards', async () => { + await expect(plugin.initialize(dao.address)) + .to.emit(plugin, MEMBERSHIP_EVENTS.MEMBERSHIP_CONTRACT_ANNOUNCED) + .withArgs(dao.address); - expect(event.args.proposalId).to.equal(currentExpectedProposalId); - expect(event.args.creator).to.equal(ownerAddress); - 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); + expect(await plugin.isMember(signers[0].address)).to.be.true; // signer[0] has `EXECUTE_PROPOSAL_PERMISSION_ID` + expect(await plugin.isMember(signers[1].address)).to.be.false; // signer[1] has not + }); }); - it('correctly emits the `ProposalExecuted` event', async () => { - const currentExpectedProposalId = 0; - - await expect(plugin.executeProposal(dummyMetadata, dummyActions, 0)) - .to.emit(plugin, PROPOSAL_EVENTS.PROPOSAL_EXECUTED) - .withArgs(currentExpectedProposalId); + describe('plugin interface: ', async () => { + it('does not support the empty interface', async () => { + expect(await plugin.supportsInterface('0xffffffff')).to.be.false; + }); + + it('supports the `IERC165Upgradeable` interface', async () => { + const iface = IERC165Upgradeable__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be + .true; + }); + + it('supports the `IPlugin` interface', async () => { + const iface = IPlugin__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be + .true; + }); + + it('supports the `IProposal` interface', async () => { + const iface = IProposal__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be + .true; + }); + + it('supports the `IMembership` interface', async () => { + const iface = IMembership__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceID(iface))).to.be + .true; + }); + + it('supports the `Admin` interface', async () => { + expect(await plugin.supportsInterface(getInterfaceID(adminInterface))) + .to.be.true; + }); }); - it('correctly increments the proposal ID', async () => { - const currentExpectedProposalId = 0; - - await plugin.executeProposal(dummyMetadata, dummyActions, 0); + describe('execute proposal: ', async () => { + beforeEach(async () => { + await initializePlugin(); + }); - const nextExpectedProposalId = currentExpectedProposalId + 1; + it("fails to call DAO's `execute()` if `EXECUTE_PERMISSION` is not granted to the plugin address", async () => { + await dao.revoke(dao.address, plugin.address, EXECUTE_PERMISSION_ID); - const tx = await plugin.executeProposal(dummyMetadata, dummyActions, 0); + await expect(plugin.executeProposal(dummyMetadata, dummyActions, 0)) + .to.be.revertedWithCustomError(dao, 'Unauthorized') + .withArgs(dao.address, plugin.address, EXECUTE_PERMISSION_ID); + }); - await expect(tx).to.emit(plugin, PROPOSAL_EVENTS.PROPOSAL_CREATED); + it('fails to call `executeProposal()` if `EXECUTE_PROPOSAL_PERMISSION_ID` is not granted for the admin address', async () => { + await dao.revoke( + plugin.address, + ownerAddress, + EXECUTE_PROPOSAL_PERMISSION_ID + ); - const event = await findEvent( - tx, - PROPOSAL_EVENTS.PROPOSAL_CREATED - ); + await expect(plugin.executeProposal(dummyMetadata, dummyActions, 0)) + .to.be.revertedWithCustomError(plugin, 'DaoUnauthorized') + .withArgs( + dao.address, + plugin.address, + ownerAddress, + EXECUTE_PROPOSAL_PERMISSION_ID + ); + }); - expect(event.args.proposalId).to.equal(nextExpectedProposalId); - }); + it('correctly emits the ProposalCreated event', async () => { + const currentExpectedProposalId = 0; - it("calls the DAO's execute function correctly with proposalId", async () => { - { - const proposalId = 0; const allowFailureMap = 1; const tx = await plugin.executeProposal( @@ -239,34 +190,94 @@ describe('Admin', function () { allowFailureMap ); - const event = await findEventTopicLog( + await expect(tx).to.emit(plugin, PROPOSAL_EVENTS.PROPOSAL_CREATED); + + const event = await findEvent( tx, - DAO__factory.createInterface(), - DAO_EVENTS.EXECUTED + PROPOSAL_EVENTS.PROPOSAL_CREATED ); - expect(event.args.actor).to.equal(plugin.address); - expect(event.args.callId).to.equal(toBytes32(proposalId)); + expect(event.args.proposalId).to.equal(currentExpectedProposalId); + expect(event.args.creator).to.equal(ownerAddress); + 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); - // note that failureMap is different than allowFailureMap. See DAO.sol for details - expect(event.args.failureMap).to.equal(0); - } + expect(event.args.allowFailureMap).to.equal(allowFailureMap); + }); - { - const proposalId = 1; + it('correctly emits the `ProposalExecuted` event', async () => { + const currentExpectedProposalId = 0; + + await expect(plugin.executeProposal(dummyMetadata, dummyActions, 0)) + .to.emit(plugin, PROPOSAL_EVENTS.PROPOSAL_EXECUTED) + .withArgs(currentExpectedProposalId); + }); + + it('correctly increments the proposal ID', async () => { + const currentExpectedProposalId = 0; + + await plugin.executeProposal(dummyMetadata, dummyActions, 0); + + const nextExpectedProposalId = currentExpectedProposalId + 1; const tx = await plugin.executeProposal(dummyMetadata, dummyActions, 0); - const event = await findEventTopicLog( + await expect(tx).to.emit(plugin, PROPOSAL_EVENTS.PROPOSAL_CREATED); + + const event = await findEvent( tx, - DAO__factory.createInterface(), - DAO_EVENTS.EXECUTED + PROPOSAL_EVENTS.PROPOSAL_CREATED ); - expect(event.args.callId).to.equal(toBytes32(proposalId)); - } + + expect(event.args.proposalId).to.equal(nextExpectedProposalId); + }); + + it("calls the DAO's execute function correctly with proposalId", async () => { + { + const proposalId = 0; + const allowFailureMap = 1; + + const tx = await plugin.executeProposal( + dummyMetadata, + dummyActions, + allowFailureMap + ); + + const event = await findEventTopicLog( + tx, + DAO__factory.createInterface(), + DAO_EVENTS.EXECUTED + ); + + expect(event.args.actor).to.equal(plugin.address); + expect(event.args.callId).to.equal(toBytes32(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 plugin.executeProposal( + dummyMetadata, + dummyActions, + 0 + ); + + const event = await findEventTopicLog( + tx, + DAO__factory.createInterface(), + DAO_EVENTS.EXECUTED + ); + expect(event.args.callId).to.equal(toBytes32(proposalId)); + } + }); }); }); }); diff --git a/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting-setup.ts b/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting-setup.ts index 6bdf3bebf..f98f483a1 100644 --- a/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting-setup.ts +++ b/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting-setup.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { @@ -57,10 +57,7 @@ describe('AddresslistVotingSetup', function () { }; defaultMembers = [signers[0].address]; - const AddresslistVotingSetup = new AddresslistVotingSetup__factory( - signers[0] - ); - addresslistVotingSetup = await AddresslistVotingSetup.deploy(); + addresslistVotingSetup = await hre.wrapper.deploy('AddresslistVotingSetup'); implementationAddress = await addresslistVotingSetup.implementation(); @@ -113,13 +110,11 @@ describe('AddresslistVotingSetup', function () { }); it('correctly returns plugin, helpers and permissions', async () => { - const nonce = await ethers.provider.getTransactionCount( - addresslistVotingSetup.address + const nonce = await hre.wrapper.getNonce(addresslistVotingSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + addresslistVotingSetup.address, + nonce ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: addresslistVotingSetup.address, - nonce, - }); const { plugin, @@ -165,13 +160,11 @@ describe('AddresslistVotingSetup', function () { }); it('correctly sets up the plugin', async () => { - const nonce = await ethers.provider.getTransactionCount( - addresslistVotingSetup.address + const nonce = await hre.wrapper.getNonce(addresslistVotingSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + addresslistVotingSetup.address, + nonce ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: addresslistVotingSetup.address, - nonce, - }); await addresslistVotingSetup.prepareInstallation( targetDao.address, diff --git a/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts b/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts index 9fe7ade2e..7b4f38fa5 100644 --- a/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts +++ b/packages/contracts/test/plugins/governance/majority-voting/addresslist/addresslist-voting.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -46,7 +46,6 @@ import { import {deployNewDAO} from '../../../../test-utils/dao'; import {OZ_ERRORS} from '../../../../test-utils/error'; import {UPGRADE_PERMISSIONS} from '../../../../test-utils/permissions'; -import {deployWithProxy} from '../../../../test-utils/proxy'; import {getInterfaceID} from '../../../../test-utils/interfaces'; import { @@ -54,6 +53,8 @@ import { ozUpgradeCheckManagedContract, } from '../../../../test-utils/uups-upgradeable'; import {CURRENT_PROTOCOL_VERSION} from '../../../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../../../test-utils/wrapper'; +import {skipTestIfNetworkIsZkSync} from '../../../../test-utils/skip-functions'; export const addresslistVotingInterface = new ethers.utils.Interface([ 'function initialize(address,tuple(uint8,uint32,uint32,uint64,uint256),address[])', @@ -61,6 +62,13 @@ export const addresslistVotingInterface = new ethers.utils.Interface([ 'function removeAddresses(address[])', ]); +function expectedBlockNumber(blockNumber: number) { + if (hre.network.config.zksync) { + return blockNumber - 2; + } + return blockNumber - 1; +} + describe('AddresslistVoting', function () { let signers: SignerWithAddress[]; let voting: AddresslistVoting; @@ -100,9 +108,9 @@ describe('AddresslistVoting', function () { minProposerVotingPower: 0, }; - const AddresslistVotingFactory = new AddresslistVoting__factory(signers[0]); - - voting = await deployWithProxy(AddresslistVotingFactory); + voting = await hre.wrapper.deploy(ARTIFACT_SOURCES.ADDRESSLIST_VOTING, { + withProxy: true, + }); startDate = (await getTime()) + startOffset; endDate = startDate + votingSettings.minDuration; @@ -142,17 +150,19 @@ describe('AddresslistVoting', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, dao, { - dao: dao.address, - votingSettings: votingSettings, - members: [signers[0].address, signers[1].address], + initArgs: { + dao: dao.address, + votingSettings: votingSettings, + members: [signers[0].address, signers[1].address], + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.ADDRESSLIST_VOTING_V1_0_0, + ARTIFACT_SOURCES.ADDRESSLIST_VOTING, UPGRADE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID ); expect(toImplementation).to.not.equal(fromImplementation); // The build did change @@ -309,28 +319,46 @@ describe('AddresslistVoting', function () { ).not.to.be.reverted; }); - it('reverts if `_msgSender` is not listed in the current block although he was listed in the last block', async () => { - votingSettings.minProposerVotingPower = 1; + skipTestIfNetworkIsZkSync( + 'reverts if the user is not allowed to create a proposal and minProposerPower > 1 is selected', + async () => { + votingSettings.minProposerVotingPower = 1; - await voting.initialize( - dao.address, - votingSettings, - [signers[0].address] // signers[0] is listed - ); + await voting.initialize( + dao.address, + votingSettings, + [signers[0].address] // signers[0] is listed + ); - await ethers.provider.send('evm_setAutomine', [false]); - const expectedSnapshotBlockNumber = ( - await ethers.provider.getBlock('latest') - ).number; + await ethers.provider.send('evm_setAutomine', [false]); + const expectedSnapshotBlockNumber = ( + await ethers.provider.getBlock('latest') + ).number; - // Transaction 1 & 2: Add signers[1] and remove signers[0] - const tx1 = await voting.addAddresses([signers[1].address]); - const tx2 = await voting.removeAddresses([signers[0].address]); + // Transaction 1 & 2: Add signers[1] and remove signers[0] + const tx1 = await voting.addAddresses([signers[1].address]); + const tx2 = await voting.removeAddresses([signers[0].address]); - // Transaction 3: Expect the proposal creation to fail for signers[0] because he was removed as a member in transaction 2. - await expect( - voting - .connect(signers[0]) + // Transaction 3: Expect the proposal creation to fail for signers[0] because he was removed as a member in transaction 2. + await expect( + voting + .connect(signers[0]) + .createProposal( + dummyMetadata, + [], + 0, + startDate, + endDate, + VoteOption.None, + false + ) + ) + .to.be.revertedWithCustomError(voting, 'ProposalCreationForbidden') + .withArgs(signers[0].address); + + // Transaction 4: Create the proposal as signers[1] + const tx4 = await voting + .connect(signers[1]) .createProposal( dummyMetadata, [], @@ -339,59 +367,44 @@ describe('AddresslistVoting', function () { endDate, VoteOption.None, false - ) - ) - .to.be.revertedWithCustomError(voting, 'ProposalCreationForbidden') - .withArgs(signers[0].address); + ); - // Transaction 4: Create the proposal as signers[1] - const tx4 = await voting - .connect(signers[1]) - .createProposal( - dummyMetadata, - [], - 0, - startDate, - endDate, - VoteOption.None, - false + // Check the listed members before the block is mined + expect(await voting.isListed(signers[0].address)).to.equal(true); + expect(await voting.isListed(signers[1].address)).to.equal(false); + + // Mine the block + await ethers.provider.send('evm_mine', []); + const minedBlockNumber = (await ethers.provider.getBlock('latest')) + .number; + + // Expect all transaction receipts to be in the same block after the snapshot block. + expect((await tx1.wait()).blockNumber).to.equal(minedBlockNumber); + expect((await tx2.wait()).blockNumber).to.equal(minedBlockNumber); + expect((await tx4.wait()).blockNumber).to.equal(minedBlockNumber); + expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); + + // Expect the listed members to have changed + expect(await voting.isListed(signers[0].address)).to.equal(false); + expect(await voting.isListed(signers[1].address)).to.equal(true); + + // Check the `ProposalCreatedEvent` for the creator and proposalId + const event = await findEvent( + tx4, + 'ProposalCreated' ); + expect(event.args.proposalId).to.equal(id); + expect(event.args.creator).to.equal(signers[1].address); - // Check the listed members before the block is mined - expect(await voting.isListed(signers[0].address)).to.equal(true); - expect(await voting.isListed(signers[1].address)).to.equal(false); - - // Mine the block - await ethers.provider.send('evm_mine', []); - const minedBlockNumber = (await ethers.provider.getBlock('latest')) - .number; - - // Expect all transaction receipts to be in the same block after the snapshot block. - expect((await tx1.wait()).blockNumber).to.equal(minedBlockNumber); - expect((await tx2.wait()).blockNumber).to.equal(minedBlockNumber); - expect((await tx4.wait()).blockNumber).to.equal(minedBlockNumber); - expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); - - // Expect the listed members to have changed - expect(await voting.isListed(signers[0].address)).to.equal(false); - expect(await voting.isListed(signers[1].address)).to.equal(true); - - // Check the `ProposalCreatedEvent` for the creator and proposalId - const event = await findEvent( - tx4, - 'ProposalCreated' - ); - expect(event.args.proposalId).to.equal(id); - expect(event.args.creator).to.equal(signers[1].address); - - // Check that the snapshot block stored in the proposal struct - const proposal = await voting.getProposal(id); - expect(proposal.parameters.snapshotBlock).to.equal( - expectedSnapshotBlockNumber - ); + // Check that the snapshot block stored in the proposal struct + const proposal = await voting.getProposal(id); + expect(proposal.parameters.snapshotBlock).to.equal( + expectedSnapshotBlockNumber + ); - await ethers.provider.send('evm_setAutomine', [true]); - }); + await ethers.provider.send('evm_setAutomine', [true]); + } + ); it('reverts if the user is not allowed to create a proposal and minProposerPower > 1 is selected', async () => { votingSettings.minProposerVotingPower = 123; @@ -630,7 +643,9 @@ describe('AddresslistVoting', function () { expect(proposal.open).to.be.true; expect(proposal.executed).to.be.false; expect(proposal.allowFailureMap).to.equal(allowFailureMap); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + expect(proposal.parameters.snapshotBlock).to.equal( + expectedBlockNumber(block.number) + ); expect(proposal.parameters.supportThreshold).to.equal( votingSettings.supportThreshold ); @@ -651,8 +666,6 @@ describe('AddresslistVoting', function () { ).to.equal(10); expect(await voting.canVote(id, signers[0].address, VoteOption.Yes)).to.be .true; - expect(await voting.canVote(id, signers[10].address, VoteOption.Yes)).to - .be.false; expect(await voting.canVote(1, signers[0].address, VoteOption.Yes)).to.be .false; @@ -702,7 +715,9 @@ describe('AddresslistVoting', function () { expect(proposal.open).to.be.true; expect(proposal.executed).to.be.false; expect(proposal.allowFailureMap).to.equal(0); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + expect(proposal.parameters.snapshotBlock).to.equal( + expectedBlockNumber(block.number) + ); expect(proposal.parameters.supportThreshold).to.equal( votingSettings.supportThreshold ); diff --git a/packages/contracts/test/plugins/governance/majority-voting/majority-voting.ts b/packages/contracts/test/plugins/governance/majority-voting/majority-voting.ts index 79086f6d0..a29b25ed0 100644 --- a/packages/contracts/test/plugins/governance/majority-voting/majority-voting.ts +++ b/packages/contracts/test/plugins/governance/majority-voting/majority-voting.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { @@ -20,10 +20,10 @@ import { ONE_HOUR, ONE_YEAR, } from '../../../test-utils/voting'; -import {deployWithProxy} from '../../../test-utils/proxy'; import {OZ_ERRORS} from '../../../test-utils/error'; import {daoExampleURI} from '../../../test-utils/dao'; import {getInterfaceID} from '../../../test-utils/interfaces'; +import {ARTIFACT_SOURCES} from '../../../test-utils/wrapper'; export const majorityVotingBaseInterface = new ethers.utils.Interface([ 'function minDuration()', @@ -46,8 +46,7 @@ describe('MajorityVotingMock', function () { signers = await ethers.getSigners(); ownerAddress = await signers[0].getAddress(); - const DAO = new DAO__factory(signers[0]); - dao = await deployWithProxy(DAO); + dao = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO, {withProxy: true}); await dao.initialize( '0x', ownerAddress, @@ -67,7 +66,9 @@ describe('MajorityVotingMock', function () { const MajorityVotingBase = new MajorityVotingMock__factory(signers[0]); - votingBase = await deployWithProxy(MajorityVotingBase); + votingBase = await hre.wrapper.deploy('MajorityVotingMock', { + withProxy: true, + }); await dao.grant( votingBase.address, ownerAddress, diff --git a/packages/contracts/test/plugins/governance/majority-voting/token/token-voting-setup-zksync.ts b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting-setup-zksync.ts new file mode 100644 index 000000000..9fead03fa --- /dev/null +++ b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting-setup-zksync.ts @@ -0,0 +1,554 @@ +import {expect, use} from 'chai'; +import hre, {ethers} from 'hardhat'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; + +import { + ERC20, + GovernanceERC20, + GovernanceERC20__factory, + GovernanceWrappedERC20, + GovernanceWrappedERC20__factory, + TokenVotingSetup, + TokenVoting__factory, +} from '../../../../../typechain'; +import {deployNewDAO} from '../../../../test-utils/dao'; +import {getInterfaceID} from '../../../../test-utils/interfaces'; +import {Operation} from '../../../../../utils/types'; +import metadata from '../../../../../src/plugins/governance/majority-voting/token/build-metadata.json'; + +import { + VotingSettings, + VotingMode, + pctToRatio, + ONE_HOUR, +} from '../../../../test-utils/voting'; +import {tokenVotingInterface} from './token-voting'; +import {getNamedTypesFromMetadata} from '../../../../../utils/metadata'; +import {supportRevertedWith} from '../../../../test-utils/matcher'; + +let defaultData: any; +let defaultVotingSettings: VotingSettings; +let defaultTokenSettings: {addr: string; name: string; symbol: string}; +let defaultMintSettings: {receivers: string[]; amounts: number[]}; + +const abiCoder = ethers.utils.defaultAbiCoder; +const AddressZero = ethers.constants.AddressZero; +const EMPTY_DATA = '0x'; + +const prepareInstallationDataTypes = getNamedTypesFromMetadata( + metadata.pluginSetup.prepareInstallation.inputs +); + +const tokenName = 'name'; +const tokenSymbol = 'symbol'; +const merkleMintToAddressArray = [ethers.Wallet.createRandom().address]; +const merkleMintToAmountArray = [1]; + +// Permissions +const UPDATE_VOTING_SETTINGS_PERMISSION_ID = ethers.utils.id( + 'UPDATE_VOTING_SETTINGS_PERMISSION' +); +const UPGRADE_PERMISSION_ID = ethers.utils.id('UPGRADE_PLUGIN_PERMISSION'); + +const EXECUTE_PERMISSION_ID = ethers.utils.id('EXECUTE_PERMISSION'); +const MINT_PERMISSION_ID = ethers.utils.id('MINT_PERMISSION'); + +describe('TokenVotingSetupZkSync', function () { + let signers: SignerWithAddress[]; + let tokenVotingSetup: TokenVotingSetup; + let implementationAddress: string; + let targetDao: any; + let erc20Token: ERC20; + + before(async () => { + signers = await ethers.getSigners(); + targetDao = await deployNewDAO(signers[0]); + + defaultVotingSettings = { + votingMode: VotingMode.EarlyExecution, + supportThreshold: pctToRatio(50), + minParticipation: pctToRatio(20), + minDuration: ONE_HOUR, + minProposerVotingPower: 0, + }; + + const emptyName = ''; + const emptySymbol = ''; + + defaultTokenSettings = { + addr: AddressZero, + name: emptyName, + symbol: emptySymbol, + }; + defaultMintSettings = {receivers: [], amounts: []}; + + tokenVotingSetup = await hre.wrapper.deploy('TokenVotingSetupZkSync'); + implementationAddress = await tokenVotingSetup.implementation(); + + erc20Token = await hre.wrapper.deploy('ERC20', { + args: [tokenName, tokenSymbol], + }); + + defaultData = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + Object.values(defaultTokenSettings), + Object.values(defaultMintSettings), + ]); + }); + + it('does not support the empty interface', async () => { + expect(await tokenVotingSetup.supportsInterface('0xffffffff')).to.be.false; + }); + + it('creates token voting base with the correct interface', async () => { + const factory = new TokenVoting__factory(signers[0]); + const tokenVoting = factory.attach(implementationAddress); + + expect( + await tokenVoting.supportsInterface(getInterfaceID(tokenVotingInterface)) + ).to.be.eq(true); + }); + + describe('prepareInstallation', async () => { + it('fails if data is empty, or not of minimum length', async () => { + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, EMPTY_DATA) + ).to.be.reverted; + + await expect( + tokenVotingSetup.prepareInstallation( + targetDao.address, + defaultData.substring(0, defaultData.length - 2) + ) + ).to.be.reverted; + + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, defaultData) + ).not.to.be.reverted; + }); + + it('fails if `MintSettings` arrays do not have the same length', async () => { + const receivers: string[] = [AddressZero]; + const amounts: number[] = []; + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + Object.values(defaultTokenSettings), + {receivers: receivers, amounts: amounts}, + ]); + + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + + const GovernanceERC20 = new GovernanceERC20__factory(signers[0]); + + const govToken = GovernanceERC20.attach(anticipatedPluginAddress); + + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, data) + ) + .to.be.revertedWithCustomError( + govToken, + 'MintSettingsArrayLengthMismatch' + ) + .withArgs(1, 0); + }); + + it('fails if passed token address is not a contract', async () => { + const tokenAddress = signers[0].address; + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [tokenAddress, '', ''], + Object.values(defaultMintSettings), + ]); + + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, data) + ) + .to.be.revertedWithCustomError(tokenVotingSetup, 'TokenNotContract') + .withArgs(tokenAddress); + }); + + it('fails if passed token address is not ERC20', async () => { + const tokenAddress = implementationAddress; + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [tokenAddress, '', ''], + Object.values(defaultMintSettings), + ]); + + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, data) + ) + .to.be.revertedWithCustomError(tokenVotingSetup, 'TokenNotERC20') + .withArgs(tokenAddress); + }); + + it('correctly returns plugin, helpers and permissions, when an ERC20 token address is supplied', async () => { + let nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedWrappedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + 1 + ); + + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [erc20Token.address, tokenName, tokenSymbol], + Object.values(defaultMintSettings), + ]); + + const { + plugin, + preparedSetupData: {helpers, permissions}, + } = await tokenVotingSetup.callStatic.prepareInstallation( + targetDao.address, + data + ); + + expect(plugin).to.be.equal(anticipatedPluginAddress); + expect(helpers.length).to.be.equal(1); + expect(helpers).to.be.deep.equal([anticipatedWrappedTokenAddress]); + expect(permissions.length).to.be.equal(3); + expect(permissions).to.deep.equal([ + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]); + }); + + it('correctly sets up `GovernanceWrappedERC20` helper, when an ERC20 token address is supplied', async () => { + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedWrappedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [erc20Token.address, tokenName, tokenSymbol], + Object.values(defaultMintSettings), + ]); + + await tokenVotingSetup.prepareInstallation(targetDao.address, data); + + const GovernanceWrappedERC20Factory = new GovernanceWrappedERC20__factory( + signers[0] + ); + const governanceWrappedERC20Contract = + GovernanceWrappedERC20Factory.attach(anticipatedWrappedTokenAddress); + + expect(await governanceWrappedERC20Contract.name()).to.be.equal( + tokenName + ); + expect(await governanceWrappedERC20Contract.symbol()).to.be.equal( + tokenSymbol + ); + + expect(await governanceWrappedERC20Contract.underlying()).to.be.equal( + erc20Token.address + ); + }); + + it('correctly returns plugin, helpers and permissions, when a governance token address is supplied', async () => { + const governanceERC20 = await hre.wrapper.deploy('GovernanceERC20', { + args: [ + targetDao.address, + 'name', + 'symbol', + {receivers: [], amounts: []}, + ], + }); + + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [governanceERC20.address, '', ''], + Object.values(defaultMintSettings), + ]); + + const { + plugin, + preparedSetupData: {helpers, permissions}, + } = await tokenVotingSetup.callStatic.prepareInstallation( + targetDao.address, + data + ); + + expect(plugin).to.be.equal(anticipatedPluginAddress); + expect(helpers.length).to.be.equal(1); + expect(helpers).to.be.deep.equal([governanceERC20.address]); + expect(permissions.length).to.be.equal(3); + expect(permissions).to.deep.equal([ + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]); + }); + + it('correctly returns plugin, helpers and permissions, when a token address is not supplied', async () => { + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + 1 + ); + + const { + plugin, + preparedSetupData: {helpers, permissions}, + } = await tokenVotingSetup.callStatic.prepareInstallation( + targetDao.address, + defaultData + ); + + expect(plugin).to.be.equal(anticipatedPluginAddress); + expect(helpers.length).to.be.equal(1); + expect(helpers).to.be.deep.equal([anticipatedTokenAddress]); + expect(permissions.length).to.be.equal(4); + expect(permissions).to.deep.equal([ + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + [ + Operation.Grant, + anticipatedTokenAddress, + targetDao.address, + AddressZero, + MINT_PERMISSION_ID, + ], + ]); + }); + + it('correctly sets up the plugin and helpers, when a token address is not passed', async () => { + const daoAddress = targetDao.address; + + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [AddressZero, tokenName, tokenSymbol], + [merkleMintToAddressArray, merkleMintToAmountArray], + ]); + + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + 1 + ); + + await tokenVotingSetup.prepareInstallation(daoAddress, data); + + // check plugin + const PluginFactory = new TokenVoting__factory(signers[0]); + const tokenVoting = PluginFactory.attach(anticipatedPluginAddress); + + expect(await tokenVoting.dao()).to.be.equal(daoAddress); + + expect(await tokenVoting.minParticipation()).to.be.equal( + defaultVotingSettings.minParticipation + ); + expect(await tokenVoting.supportThreshold()).to.be.equal( + defaultVotingSettings.supportThreshold + ); + expect(await tokenVoting.minDuration()).to.be.equal( + defaultVotingSettings.minDuration + ); + expect(await tokenVoting.minProposerVotingPower()).to.be.equal( + defaultVotingSettings.minProposerVotingPower + ); + expect(await tokenVoting.getVotingToken()).to.be.equal( + anticipatedTokenAddress + ); + + // check helpers + const GovernanceTokenFactory = new GovernanceERC20__factory(signers[0]); + const governanceTokenContract = GovernanceTokenFactory.attach( + anticipatedTokenAddress + ); + + expect(await governanceTokenContract.dao()).to.be.equal(daoAddress); + expect(await governanceTokenContract.name()).to.be.equal(tokenName); + expect(await governanceTokenContract.symbol()).to.be.equal(tokenSymbol); + }); + }); + + describe('prepareUninstallation', async () => { + it('fails when the wrong number of helpers is supplied', async () => { + const plugin = ethers.Wallet.createRandom().address; + + await expect( + tokenVotingSetup.prepareUninstallation(targetDao.address, { + plugin, + currentHelpers: [], + data: EMPTY_DATA, + }) + ) + .to.be.revertedWithCustomError( + tokenVotingSetup, + 'WrongHelpersArrayLength' + ) + .withArgs(0); + + await expect( + tokenVotingSetup.prepareUninstallation(targetDao.address, { + plugin, + currentHelpers: [AddressZero, AddressZero, AddressZero], + data: EMPTY_DATA, + }) + ) + .to.be.revertedWithCustomError( + tokenVotingSetup, + 'WrongHelpersArrayLength' + ) + .withArgs(3); + }); + + it('correctly returns permissions, when the required number of helpers is supplied', async () => { + const plugin = ethers.Wallet.createRandom().address; + + const governanceERC20 = await hre.wrapper.deploy('GovernanceERC20', { + args: [ + targetDao.address, + tokenName, + tokenSymbol, + {receivers: [], amounts: []}, + ], + }); + + const governanceWrappedERC20 = await hre.wrapper.deploy( + 'GovernanceWrappedERC20', + {args: [governanceERC20.address, tokenName, tokenSymbol]} + ); + + // When the helpers contain governanceWrappedERC20 token + const permissions1 = + await tokenVotingSetup.callStatic.prepareUninstallation( + targetDao.address, + { + plugin, + currentHelpers: [governanceWrappedERC20.address], + data: EMPTY_DATA, + } + ); + + const essentialPermissions = [ + [ + Operation.Revoke, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Revoke, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Revoke, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]; + + expect(permissions1.length).to.be.equal(3); + expect(permissions1).to.deep.equal([...essentialPermissions]); + + const permissions2 = + await tokenVotingSetup.callStatic.prepareUninstallation( + targetDao.address, + { + plugin, + currentHelpers: [governanceERC20.address], + data: EMPTY_DATA, + } + ); + + expect(permissions2.length).to.be.equal(4); + expect(permissions2).to.deep.equal([ + ...essentialPermissions, + [ + Operation.Revoke, + governanceERC20.address, + targetDao.address, + AddressZero, + MINT_PERMISSION_ID, + ], + ]); + }); + }); +}); diff --git a/packages/contracts/test/plugins/governance/majority-voting/token/token-voting-setup.ts b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting-setup.ts index 96e90b36f..996013ee3 100644 --- a/packages/contracts/test/plugins/governance/majority-voting/token/token-voting-setup.ts +++ b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting-setup.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { @@ -26,6 +26,7 @@ import { } from '../../../../test-utils/voting'; import {tokenVotingInterface} from './token-voting'; import {getNamedTypesFromMetadata} from '../../../../../utils/metadata'; +import {skipTestSuiteIfNetworkIsZkSync} from '../../../../test-utils/skip-functions'; let defaultData: any; let defaultVotingSettings: VotingSettings; @@ -53,549 +54,526 @@ const UPGRADE_PERMISSION_ID = ethers.utils.id('UPGRADE_PLUGIN_PERMISSION'); const EXECUTE_PERMISSION_ID = ethers.utils.id('EXECUTE_PERMISSION'); const MINT_PERMISSION_ID = ethers.utils.id('MINT_PERMISSION'); -describe('TokenVotingSetup', function () { - let signers: SignerWithAddress[]; - let tokenVotingSetup: TokenVotingSetup; - let governanceERC20Base: GovernanceERC20; - let governanceWrappedERC20Base: GovernanceWrappedERC20; - let implementationAddress: string; - let targetDao: any; - let erc20Token: ERC20; - - before(async () => { - signers = await ethers.getSigners(); - targetDao = await deployNewDAO(signers[0]); - - defaultVotingSettings = { - votingMode: VotingMode.EarlyExecution, - supportThreshold: pctToRatio(50), - minParticipation: pctToRatio(20), - minDuration: ONE_HOUR, - minProposerVotingPower: 0, - }; - - const emptyName = ''; - const emptySymbol = ''; - - defaultTokenSettings = { - addr: AddressZero, - name: emptyName, - symbol: emptySymbol, - }; - defaultMintSettings = {receivers: [], amounts: []}; - - const GovernanceERC20Factory = new GovernanceERC20__factory(signers[0]); - governanceERC20Base = await GovernanceERC20Factory.deploy( - AddressZero, - emptyName, - emptySymbol, - defaultMintSettings - ); - - const GovernanceWrappedERC20Factory = new GovernanceWrappedERC20__factory( - signers[0] - ); - governanceWrappedERC20Base = await GovernanceWrappedERC20Factory.deploy( - AddressZero, - emptyName, - emptySymbol - ); - - const TokenVotingSetup = new TokenVotingSetup__factory(signers[0]); - tokenVotingSetup = await TokenVotingSetup.deploy( - governanceERC20Base.address, - governanceWrappedERC20Base.address - ); - - implementationAddress = await tokenVotingSetup.implementation(); - - const ERC20Token = new ERC20__factory(signers[0]); - erc20Token = await ERC20Token.deploy(tokenName, tokenSymbol); - - defaultData = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - Object.values(defaultTokenSettings), - Object.values(defaultMintSettings), - ]); - }); +skipTestSuiteIfNetworkIsZkSync('TokenVotingSetup', async () => { + describe('TokenVotingSetup', function () { + let signers: SignerWithAddress[]; + let tokenVotingSetup: TokenVotingSetup; + let governanceERC20Base: GovernanceERC20; + let governanceWrappedERC20Base: GovernanceWrappedERC20; + let implementationAddress: string; + let targetDao: any; + let erc20Token: ERC20; + + before(async () => { + signers = await ethers.getSigners(); + targetDao = await deployNewDAO(signers[0]); + + defaultVotingSettings = { + votingMode: VotingMode.EarlyExecution, + supportThreshold: pctToRatio(50), + minParticipation: pctToRatio(20), + minDuration: ONE_HOUR, + minProposerVotingPower: 0, + }; + + const emptyName = ''; + const emptySymbol = ''; + + defaultTokenSettings = { + addr: AddressZero, + name: emptyName, + symbol: emptySymbol, + }; + defaultMintSettings = {receivers: [], amounts: []}; + + governanceERC20Base = await hre.wrapper.deploy('GovernanceERC20', { + args: [AddressZero, emptyName, emptySymbol, defaultMintSettings], + }); + governanceWrappedERC20Base = await hre.wrapper.deploy( + 'GovernanceWrappedERC20', + {args: [AddressZero, emptyName, emptySymbol]} + ); + tokenVotingSetup = await hre.wrapper.deploy('TokenVotingSetup', { + args: [governanceERC20Base.address, governanceWrappedERC20Base.address], + }); + implementationAddress = await tokenVotingSetup.implementation(); - it('does not support the empty interface', async () => { - expect(await tokenVotingSetup.supportsInterface('0xffffffff')).to.be.false; - }); + erc20Token = await hre.wrapper.deploy('ERC20', { + args: [tokenName, tokenSymbol], + }); - it('stores the bases provided through the constructor', async () => { - expect(await tokenVotingSetup.governanceERC20Base()).to.be.eq( - governanceERC20Base.address - ); - expect(await tokenVotingSetup.governanceWrappedERC20Base()).to.be.eq( - governanceWrappedERC20Base.address - ); - }); + defaultData = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + Object.values(defaultTokenSettings), + Object.values(defaultMintSettings), + ]); + }); - it('creates token voting base with the correct interface', async () => { - const factory = new TokenVoting__factory(signers[0]); - const tokenVoting = factory.attach(implementationAddress); + it('does not support the empty interface', async () => { + expect(await tokenVotingSetup.supportsInterface('0xffffffff')).to.be + .false; + }); - expect( - await tokenVoting.supportsInterface(getInterfaceID(tokenVotingInterface)) - ).to.be.eq(true); - }); + it('stores the bases provided through the constructor', async () => { + expect(await tokenVotingSetup.governanceERC20Base()).to.be.eq( + governanceERC20Base.address + ); + expect(await tokenVotingSetup.governanceWrappedERC20Base()).to.be.eq( + governanceWrappedERC20Base.address + ); + }); - describe('prepareInstallation', async () => { - it('fails if data is empty, or not of minimum length', async () => { - await expect( - tokenVotingSetup.prepareInstallation(targetDao.address, EMPTY_DATA) - ).to.be.reverted; + it('creates token voting base with the correct interface', async () => { + const factory = new TokenVoting__factory(signers[0]); + const tokenVoting = factory.attach(implementationAddress); - await expect( - tokenVotingSetup.prepareInstallation( - targetDao.address, - defaultData.substring(0, defaultData.length - 2) + expect( + await tokenVoting.supportsInterface( + getInterfaceID(tokenVotingInterface) ) - ).to.be.reverted; - - await expect( - tokenVotingSetup.prepareInstallation(targetDao.address, defaultData) - ).not.to.be.reverted; + ).to.be.eq(true); }); - it('fails if `MintSettings` arrays do not have the same length', async () => { - const receivers: string[] = [AddressZero]; - const amounts: number[] = []; - const data = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - Object.values(defaultTokenSettings), - {receivers: receivers, amounts: amounts}, - ]); - - const nonce = await ethers.provider.getTransactionCount( - tokenVotingSetup.address - ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce, + describe('prepareInstallation', async () => { + it('fails if data is empty, or not of minimum length', async () => { + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, EMPTY_DATA) + ).to.be.reverted; + + await expect( + tokenVotingSetup.prepareInstallation( + targetDao.address, + defaultData.substring(0, defaultData.length - 2) + ) + ).to.be.reverted; + + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, defaultData) + ).not.to.be.reverted; }); - const GovernanceERC20 = new GovernanceERC20__factory(signers[0]); + it('fails if `MintSettings` arrays do not have the same length', async () => { + const receivers: string[] = [AddressZero]; + const amounts: number[] = []; + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + Object.values(defaultTokenSettings), + {receivers: receivers, amounts: amounts}, + ]); + + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); - const govToken = GovernanceERC20.attach(anticipatedPluginAddress); + const GovernanceERC20 = new GovernanceERC20__factory(signers[0]); - await expect( - tokenVotingSetup.prepareInstallation(targetDao.address, data) - ) - .to.be.revertedWithCustomError( - govToken, - 'MintSettingsArrayLengthMismatch' - ) - .withArgs(1, 0); - }); + const govToken = GovernanceERC20.attach(anticipatedPluginAddress); - it('fails if passed token address is not a contract', async () => { - const tokenAddress = signers[0].address; - const data = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - [tokenAddress, '', ''], - Object.values(defaultMintSettings), - ]); + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, data) + ) + .to.be.revertedWithCustomError( + govToken, + 'MintSettingsArrayLengthMismatch' + ) + .withArgs(1, 0); + }); - await expect( - tokenVotingSetup.prepareInstallation(targetDao.address, data) - ) - .to.be.revertedWithCustomError(tokenVotingSetup, 'TokenNotContract') - .withArgs(tokenAddress); - }); + it('fails if passed token address is not a contract', async () => { + const tokenAddress = signers[0].address; + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [tokenAddress, '', ''], + Object.values(defaultMintSettings), + ]); - it('fails if passed token address is not ERC20', async () => { - const tokenAddress = implementationAddress; - const data = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - [tokenAddress, '', ''], - Object.values(defaultMintSettings), - ]); + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, data) + ) + .to.be.revertedWithCustomError(tokenVotingSetup, 'TokenNotContract') + .withArgs(tokenAddress); + }); - await expect( - tokenVotingSetup.prepareInstallation(targetDao.address, data) - ) - .to.be.revertedWithCustomError(tokenVotingSetup, 'TokenNotERC20') - .withArgs(tokenAddress); - }); + it('fails if passed token address is not ERC20', async () => { + const tokenAddress = implementationAddress; + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [tokenAddress, '', ''], + Object.values(defaultMintSettings), + ]); - it('correctly returns plugin, helpers and permissions, when an ERC20 token address is supplied', async () => { - const nonce = await ethers.provider.getTransactionCount( - tokenVotingSetup.address - ); - const anticipatedWrappedTokenAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce, - }); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce + 1, + await expect( + tokenVotingSetup.prepareInstallation(targetDao.address, data) + ) + .to.be.revertedWithCustomError(tokenVotingSetup, 'TokenNotERC20') + .withArgs(tokenAddress); }); - const data = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - [erc20Token.address, tokenName, tokenSymbol], - Object.values(defaultMintSettings), - ]); + it('correctly returns plugin, helpers and permissions, when an ERC20 token address is supplied', async () => { + let nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedWrappedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + 1 + ); - const { - plugin, - preparedSetupData: {helpers, permissions}, - } = await tokenVotingSetup.callStatic.prepareInstallation( - targetDao.address, - data - ); + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [erc20Token.address, tokenName, tokenSymbol], + Object.values(defaultMintSettings), + ]); - expect(plugin).to.be.equal(anticipatedPluginAddress); - expect(helpers.length).to.be.equal(1); - expect(helpers).to.be.deep.equal([anticipatedWrappedTokenAddress]); - expect(permissions.length).to.be.equal(3); - expect(permissions).to.deep.equal([ - [ - Operation.Grant, - plugin, - targetDao.address, - AddressZero, - UPDATE_VOTING_SETTINGS_PERMISSION_ID, - ], - [ - Operation.Grant, + const { plugin, + preparedSetupData: {helpers, permissions}, + } = await tokenVotingSetup.callStatic.prepareInstallation( targetDao.address, - AddressZero, - UPGRADE_PERMISSION_ID, - ], - [ - Operation.Grant, - targetDao.address, - plugin, - AddressZero, - EXECUTE_PERMISSION_ID, - ], - ]); - }); + data + ); - it('correctly sets up `GovernanceWrappedERC20` helper, when an ERC20 token address is supplied', async () => { - const nonce = await ethers.provider.getTransactionCount( - tokenVotingSetup.address - ); - const anticipatedWrappedTokenAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce, + expect(plugin).to.be.equal(anticipatedPluginAddress); + expect(helpers.length).to.be.equal(1); + expect(helpers).to.be.deep.equal([anticipatedWrappedTokenAddress]); + expect(permissions.length).to.be.equal(3); + expect(permissions).to.deep.equal([ + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]); }); - const data = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - [erc20Token.address, tokenName, tokenSymbol], - Object.values(defaultMintSettings), - ]); - - await tokenVotingSetup.prepareInstallation(targetDao.address, data); - - const GovernanceWrappedERC20Factory = new GovernanceWrappedERC20__factory( - signers[0] - ); - const governanceWrappedERC20Contract = - GovernanceWrappedERC20Factory.attach(anticipatedWrappedTokenAddress); + it('correctly sets up `GovernanceWrappedERC20` helper, when an ERC20 token address is supplied', async () => { + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedWrappedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); - expect(await governanceWrappedERC20Contract.name()).to.be.equal( - tokenName - ); - expect(await governanceWrappedERC20Contract.symbol()).to.be.equal( - tokenSymbol - ); + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [erc20Token.address, tokenName, tokenSymbol], + Object.values(defaultMintSettings), + ]); - expect(await governanceWrappedERC20Contract.underlying()).to.be.equal( - erc20Token.address - ); - }); + await tokenVotingSetup.prepareInstallation(targetDao.address, data); - it('correctly returns plugin, helpers and permissions, when a governance token address is supplied', async () => { - const GovernanceERC20 = new GovernanceERC20__factory(signers[0]); - const governanceERC20 = await GovernanceERC20.deploy( - targetDao.address, - 'name', - 'symbol', - {receivers: [], amounts: []} - ); + const GovernanceWrappedERC20Factory = + new GovernanceWrappedERC20__factory(signers[0]); + const governanceWrappedERC20Contract = + GovernanceWrappedERC20Factory.attach(anticipatedWrappedTokenAddress); - const nonce = await ethers.provider.getTransactionCount( - tokenVotingSetup.address - ); + expect(await governanceWrappedERC20Contract.name()).to.be.equal( + tokenName + ); + expect(await governanceWrappedERC20Contract.symbol()).to.be.equal( + tokenSymbol + ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce, + expect(await governanceWrappedERC20Contract.underlying()).to.be.equal( + erc20Token.address + ); }); - const data = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - [governanceERC20.address, '', ''], - Object.values(defaultMintSettings), - ]); + it('correctly returns plugin, helpers and permissions, when a governance token address is supplied', async () => { + const governanceERC20 = await hre.wrapper.deploy('GovernanceERC20', { + args: [ + targetDao.address, + 'name', + 'symbol', + {receivers: [], amounts: []}, + ], + }); + + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); - const { - plugin, - preparedSetupData: {helpers, permissions}, - } = await tokenVotingSetup.callStatic.prepareInstallation( - targetDao.address, - data - ); + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [governanceERC20.address, '', ''], + Object.values(defaultMintSettings), + ]); - expect(plugin).to.be.equal(anticipatedPluginAddress); - expect(helpers.length).to.be.equal(1); - expect(helpers).to.be.deep.equal([governanceERC20.address]); - expect(permissions.length).to.be.equal(3); - expect(permissions).to.deep.equal([ - [ - Operation.Grant, + const { plugin, + preparedSetupData: {helpers, permissions}, + } = await tokenVotingSetup.callStatic.prepareInstallation( targetDao.address, - AddressZero, - UPDATE_VOTING_SETTINGS_PERMISSION_ID, - ], - [ - Operation.Grant, - plugin, - targetDao.address, - AddressZero, - UPGRADE_PERMISSION_ID, - ], - [ - Operation.Grant, - targetDao.address, - plugin, - AddressZero, - EXECUTE_PERMISSION_ID, - ], - ]); - }); - - it('correctly returns plugin, helpers and permissions, when a token address is not supplied', async () => { - const nonce = await ethers.provider.getTransactionCount( - tokenVotingSetup.address - ); - const anticipatedTokenAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce, - }); + data + ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce + 1, + expect(plugin).to.be.equal(anticipatedPluginAddress); + expect(helpers.length).to.be.equal(1); + expect(helpers).to.be.deep.equal([governanceERC20.address]); + expect(permissions.length).to.be.equal(3); + expect(permissions).to.deep.equal([ + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]); }); - const { - plugin, - preparedSetupData: {helpers, permissions}, - } = await tokenVotingSetup.callStatic.prepareInstallation( - targetDao.address, - defaultData - ); + it('correctly returns plugin, helpers and permissions, when a token address is not supplied', async () => { + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + 1 + ); - expect(plugin).to.be.equal(anticipatedPluginAddress); - expect(helpers.length).to.be.equal(1); - expect(helpers).to.be.deep.equal([anticipatedTokenAddress]); - expect(permissions.length).to.be.equal(4); - expect(permissions).to.deep.equal([ - [ - Operation.Grant, - plugin, - targetDao.address, - AddressZero, - UPDATE_VOTING_SETTINGS_PERMISSION_ID, - ], - [ - Operation.Grant, - plugin, - targetDao.address, - AddressZero, - UPGRADE_PERMISSION_ID, - ], - [ - Operation.Grant, - targetDao.address, + const { plugin, - AddressZero, - EXECUTE_PERMISSION_ID, - ], - [ - Operation.Grant, - anticipatedTokenAddress, + preparedSetupData: {helpers, permissions}, + } = await tokenVotingSetup.callStatic.prepareInstallation( targetDao.address, - AddressZero, - MINT_PERMISSION_ID, - ], - ]); - }); - - it('correctly sets up the plugin and helpers, when a token address is not passed', async () => { - const daoAddress = targetDao.address; - - const data = abiCoder.encode(prepareInstallationDataTypes, [ - Object.values(defaultVotingSettings), - [AddressZero, tokenName, tokenSymbol], - [merkleMintToAddressArray, merkleMintToAmountArray], - ]); + defaultData + ); - const nonce = await ethers.provider.getTransactionCount( - tokenVotingSetup.address - ); - const anticipatedTokenAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce, - }); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: tokenVotingSetup.address, - nonce: nonce + 1, + expect(plugin).to.be.equal(anticipatedPluginAddress); + expect(helpers.length).to.be.equal(1); + expect(helpers).to.be.deep.equal([anticipatedTokenAddress]); + expect(permissions.length).to.be.equal(4); + expect(permissions).to.deep.equal([ + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + [ + Operation.Grant, + anticipatedTokenAddress, + targetDao.address, + AddressZero, + MINT_PERMISSION_ID, + ], + ]); }); - await tokenVotingSetup.prepareInstallation(daoAddress, data); + it('correctly sets up the plugin and helpers, when a token address is not passed', async () => { + const daoAddress = targetDao.address; - // check plugin - const PluginFactory = new TokenVoting__factory(signers[0]); - const tokenVoting = PluginFactory.attach(anticipatedPluginAddress); + const data = abiCoder.encode(prepareInstallationDataTypes, [ + Object.values(defaultVotingSettings), + [AddressZero, tokenName, tokenSymbol], + [merkleMintToAddressArray, merkleMintToAmountArray], + ]); - expect(await tokenVoting.dao()).to.be.equal(daoAddress); + const nonce = await hre.wrapper.getNonce(tokenVotingSetup.address); + const anticipatedTokenAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + ); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + tokenVotingSetup.address, + nonce + 1 + ); - expect(await tokenVoting.minParticipation()).to.be.equal( - defaultVotingSettings.minParticipation - ); - expect(await tokenVoting.supportThreshold()).to.be.equal( - defaultVotingSettings.supportThreshold - ); - expect(await tokenVoting.minDuration()).to.be.equal( - defaultVotingSettings.minDuration - ); - expect(await tokenVoting.minProposerVotingPower()).to.be.equal( - defaultVotingSettings.minProposerVotingPower - ); - expect(await tokenVoting.getVotingToken()).to.be.equal( - anticipatedTokenAddress - ); + await tokenVotingSetup.prepareInstallation(daoAddress, data); - // check helpers - const GovernanceTokenFactory = new GovernanceERC20__factory(signers[0]); - const governanceTokenContract = GovernanceTokenFactory.attach( - anticipatedTokenAddress - ); + // check plugin + const PluginFactory = new TokenVoting__factory(signers[0]); + const tokenVoting = PluginFactory.attach(anticipatedPluginAddress); - expect(await governanceTokenContract.dao()).to.be.equal(daoAddress); - expect(await governanceTokenContract.name()).to.be.equal(tokenName); - expect(await governanceTokenContract.symbol()).to.be.equal(tokenSymbol); - }); - }); + expect(await tokenVoting.dao()).to.be.equal(daoAddress); - describe('prepareUninstallation', async () => { - it('fails when the wrong number of helpers is supplied', async () => { - const plugin = ethers.Wallet.createRandom().address; + expect(await tokenVoting.minParticipation()).to.be.equal( + defaultVotingSettings.minParticipation + ); + expect(await tokenVoting.supportThreshold()).to.be.equal( + defaultVotingSettings.supportThreshold + ); + expect(await tokenVoting.minDuration()).to.be.equal( + defaultVotingSettings.minDuration + ); + expect(await tokenVoting.minProposerVotingPower()).to.be.equal( + defaultVotingSettings.minProposerVotingPower + ); + expect(await tokenVoting.getVotingToken()).to.be.equal( + anticipatedTokenAddress + ); - await expect( - tokenVotingSetup.prepareUninstallation(targetDao.address, { - plugin, - currentHelpers: [], - data: EMPTY_DATA, - }) - ) - .to.be.revertedWithCustomError( - tokenVotingSetup, - 'WrongHelpersArrayLength' - ) - .withArgs(0); + // check helpers + const GovernanceTokenFactory = new GovernanceERC20__factory(signers[0]); + const governanceTokenContract = GovernanceTokenFactory.attach( + anticipatedTokenAddress + ); - await expect( - tokenVotingSetup.prepareUninstallation(targetDao.address, { - plugin, - currentHelpers: [AddressZero, AddressZero, AddressZero], - data: EMPTY_DATA, - }) - ) - .to.be.revertedWithCustomError( - tokenVotingSetup, - 'WrongHelpersArrayLength' - ) - .withArgs(3); + expect(await governanceTokenContract.dao()).to.be.equal(daoAddress); + expect(await governanceTokenContract.name()).to.be.equal(tokenName); + expect(await governanceTokenContract.symbol()).to.be.equal(tokenSymbol); + }); }); - it('correctly returns permissions, when the required number of helpers is supplied', async () => { - const plugin = ethers.Wallet.createRandom().address; - const GovernanceERC20 = new GovernanceERC20__factory(signers[0]); - const GovernanceWrappedERC20 = new GovernanceWrappedERC20__factory( - signers[0] - ); - const governanceERC20 = await GovernanceERC20.deploy( - targetDao.address, - tokenName, - tokenSymbol, - {receivers: [], amounts: []} - ); - - const governanceWrappedERC20 = await GovernanceWrappedERC20.deploy( - governanceERC20.address, - tokenName, - tokenSymbol - ); + describe('prepareUninstallation', async () => { + it('fails when the wrong number of helpers is supplied', async () => { + const plugin = ethers.Wallet.createRandom().address; - // When the helpers contain governanceWrappedERC20 token - const permissions1 = - await tokenVotingSetup.callStatic.prepareUninstallation( - targetDao.address, - { + await expect( + tokenVotingSetup.prepareUninstallation(targetDao.address, { plugin, - currentHelpers: [governanceWrappedERC20.address], + currentHelpers: [], data: EMPTY_DATA, - } - ); - - const essentialPermissions = [ - [ - Operation.Revoke, - plugin, - targetDao.address, - AddressZero, - UPDATE_VOTING_SETTINGS_PERMISSION_ID, - ], - [ - Operation.Revoke, - plugin, - targetDao.address, - AddressZero, - UPGRADE_PERMISSION_ID, - ], - [ - Operation.Revoke, - targetDao.address, - plugin, - AddressZero, - EXECUTE_PERMISSION_ID, - ], - ]; - - expect(permissions1.length).to.be.equal(3); - expect(permissions1).to.deep.equal([...essentialPermissions]); - - const permissions2 = - await tokenVotingSetup.callStatic.prepareUninstallation( - targetDao.address, - { + }) + ) + .to.be.revertedWithCustomError( + tokenVotingSetup, + 'WrongHelpersArrayLength' + ) + .withArgs(0); + + await expect( + tokenVotingSetup.prepareUninstallation(targetDao.address, { plugin, - currentHelpers: [governanceERC20.address], + currentHelpers: [AddressZero, AddressZero, AddressZero], data: EMPTY_DATA, - } + }) + ) + .to.be.revertedWithCustomError( + tokenVotingSetup, + 'WrongHelpersArrayLength' + ) + .withArgs(3); + }); + + it('correctly returns permissions, when the required number of helpers is supplied', async () => { + const plugin = ethers.Wallet.createRandom().address; + + const governanceERC20 = await hre.wrapper.deploy('GovernanceERC20', { + args: [ + targetDao.address, + tokenName, + tokenSymbol, + {receivers: [], amounts: []}, + ], + }); + + const governanceWrappedERC20 = await hre.wrapper.deploy( + 'GovernanceWrappedERC20', + {args: [governanceERC20.address, tokenName, tokenSymbol]} ); - expect(permissions2.length).to.be.equal(4); - expect(permissions2).to.deep.equal([ - ...essentialPermissions, - [ - Operation.Revoke, - governanceERC20.address, - targetDao.address, - AddressZero, - MINT_PERMISSION_ID, - ], - ]); + // When the helpers contain governanceWrappedERC20 token + const permissions1 = + await tokenVotingSetup.callStatic.prepareUninstallation( + targetDao.address, + { + plugin, + currentHelpers: [governanceWrappedERC20.address], + data: EMPTY_DATA, + } + ); + + const essentialPermissions = [ + [ + Operation.Revoke, + plugin, + targetDao.address, + AddressZero, + UPDATE_VOTING_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Revoke, + plugin, + targetDao.address, + AddressZero, + UPGRADE_PERMISSION_ID, + ], + [ + Operation.Revoke, + targetDao.address, + plugin, + AddressZero, + EXECUTE_PERMISSION_ID, + ], + ]; + + expect(permissions1.length).to.be.equal(3); + expect(permissions1).to.deep.equal([...essentialPermissions]); + + const permissions2 = + await tokenVotingSetup.callStatic.prepareUninstallation( + targetDao.address, + { + plugin, + currentHelpers: [governanceERC20.address], + data: EMPTY_DATA, + } + ); + + expect(permissions2.length).to.be.equal(4); + expect(permissions2).to.deep.equal([ + ...essentialPermissions, + [ + Operation.Revoke, + governanceERC20.address, + targetDao.address, + AddressZero, + MINT_PERMISSION_ID, + ], + ]); + }); }); }); }); diff --git a/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts index 730fefd78..0584f795c 100644 --- a/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts +++ b/packages/contracts/test/plugins/governance/majority-voting/token/token-voting.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {BigNumber, ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -46,7 +46,6 @@ import { } from '../../../../test-utils/voting'; import {deployNewDAO} from '../../../../test-utils/dao'; import {OZ_ERRORS} from '../../../../test-utils/error'; -import {deployWithProxy} from '../../../../test-utils/proxy'; import {getInterfaceID} from '../../../../test-utils/interfaces'; import {UPGRADE_PERMISSIONS} from '../../../../test-utils/permissions'; import { @@ -55,6 +54,8 @@ import { } from '../../../../test-utils/uups-upgradeable'; import {majorityVotingBaseInterface} from '../majority-voting'; import {CURRENT_PROTOCOL_VERSION} from '../../../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../../../test-utils/wrapper'; +import {skipTestIfNetworkIsZkSync} from '../../../../test-utils/skip-functions'; export const tokenVotingInterface = new ethers.utils.Interface([ 'function initialize(address,tuple(uint8,uint32,uint32,uint64,uint256),address)', @@ -103,25 +104,26 @@ describe('TokenVoting', function () { minProposerVotingPower: 0, }; - GovernanceERC20Mock = new GovernanceERC20Mock__factory(signers[0]); - governanceErc20Mock = await GovernanceERC20Mock.deploy( - dao.address, - 'GOV', - 'GOV', - { - receivers: [], - amounts: [], - } - ); - - const TokenVotingFactory = new TokenVoting__factory(signers[0]); + governanceErc20Mock = await hre.wrapper.deploy('GovernanceERC20Mock', { + args: [ + dao.address, + 'GOV', + 'GOV', + { + receivers: [], + amounts: [], + }, + ], + }); - voting = await deployWithProxy(TokenVotingFactory); + voting = await hre.wrapper.deploy(ARTIFACT_SOURCES.TOKEN_VOTING, { + withProxy: true, + }); startDate = (await getTime()) + startOffset; endDate = startDate + votingSettings.minDuration; - dao.grant( + await dao.grant( dao.address, voting.address, ethers.utils.id('EXECUTE_PERMISSION') @@ -131,10 +133,10 @@ describe('TokenVoting', function () { async function setBalances( balances: {receiver: string; amount: number | BigNumber}[] ) { - const promises = balances.map(balance => - governanceErc20Mock.setBalance(balance.receiver, balance.amount) - ); - await Promise.all(promises); + const receivers = balances.map(item => item.receiver); + const amounts = balances.map(item => item.amount); + + await governanceErc20Mock.setBalances(receivers, amounts); } async function setTotalSupply(totalSupply: number) { @@ -144,9 +146,9 @@ describe('TokenVoting', function () { const currentTotalSupply: BigNumber = await governanceErc20Mock.getPastTotalSupply(block.number - 1); - await governanceErc20Mock.setBalance( - `0x${'0'.repeat(39)}1`, // address(1) - BigNumber.from(totalSupply).sub(currentTotalSupply) + await governanceErc20Mock.setBalances( + [`0x${'0'.repeat(39)}1`], // address(1) + [BigNumber.from(totalSupply).sub(currentTotalSupply)] ); } @@ -209,20 +211,21 @@ describe('TokenVoting', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, dao, { - dao: dao.address, - votingSettings: votingSettings, - token: governanceErc20Mock.address, + initArgs: { + dao: dao.address, + votingSettings: votingSettings, + token: governanceErc20Mock.address, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.TOKEN_VOTING_V1_0_0, + ARTIFACT_SOURCES.TOKEN_VOTING, UPGRADE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID ); - expect(toImplementation).to.not.equal(fromImplementation); // The build did change const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) @@ -412,28 +415,49 @@ describe('TokenVoting', function () { ).not.to.be.reverted; }); - it('reverts if `_msgSender` owns no tokens and has no tokens delegated to her/him in the current block although having them in the last block', async () => { - await setBalances([ - { - receiver: signers[0].address, - amount: votingSettings.minProposerVotingPower, - }, - ]); - - await ethers.provider.send('evm_setAutomine', [false]); - const expectedSnapshotBlockNumber = ( - await ethers.provider.getBlock('latest') - ).number; - - // Transaction 1: Transfer the tokens from signers[0] to signers[1] - const tx1 = await governanceErc20Mock - .connect(signers[0]) - .transfer(signers[1].address, votingSettings.minProposerVotingPower); - - // Transaction 2: Expect the proposal creation to fail for signers[0] because he transferred the tokens in transaction 1 - await expect( - voting + skipTestIfNetworkIsZkSync( + 'reverts if `_msgSender` owns no tokens and has no tokens delegated to her/him in the current block although having them in the last block', + async () => { + await setBalances([ + { + receiver: signers[0].address, + amount: votingSettings.minProposerVotingPower, + }, + ]); + + await ethers.provider.send('evm_setAutomine', [false]); + const expectedSnapshotBlockNumber = ( + await ethers.provider.getBlock('latest') + ).number; + + // Transaction 1: Transfer the tokens from signers[0] to signers[1] + const tx1 = await governanceErc20Mock .connect(signers[0]) + .transfer( + signers[1].address, + votingSettings.minProposerVotingPower + ); + + // Transaction 2: Expect the proposal creation to fail for signers[0] because he transferred the tokens in transaction 1 + await expect( + voting + .connect(signers[0]) + .createProposal( + dummyMetadata, + [], + 0, + startDate, + endDate, + VoteOption.None, + false + ) + ) + .to.be.revertedWithCustomError(voting, 'ProposalCreationForbidden') + .withArgs(signers[0].address); + + // Transaction 3: Create the proposal as signers[1] + const tx3 = await voting + .connect(signers[1]) .createProposal( dummyMetadata, [], @@ -442,66 +466,51 @@ describe('TokenVoting', function () { endDate, VoteOption.None, false - ) - ) - .to.be.revertedWithCustomError(voting, 'ProposalCreationForbidden') - .withArgs(signers[0].address); - - // Transaction 3: Create the proposal as signers[1] - const tx3 = await voting - .connect(signers[1]) - .createProposal( - dummyMetadata, - [], - 0, - startDate, - endDate, - VoteOption.None, - false + ); + + // Check the balances before the block is mined + expect( + await governanceErc20Mock.balanceOf(signers[0].address) + ).to.equal(votingSettings.minProposerVotingPower); + expect( + await governanceErc20Mock.balanceOf(signers[1].address) + ).to.equal(0); + + // Mine the block + await ethers.provider.send('evm_mine', []); + const minedBlockNumber = (await ethers.provider.getBlock('latest')) + .number; + + // Expect all transaction receipts to be in the same block after the snapshot block. + expect((await tx1.wait()).blockNumber).to.equal(minedBlockNumber); + expect((await tx3.wait()).blockNumber).to.equal(minedBlockNumber); + expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); + + // Expect the balances to have changed + expect( + await governanceErc20Mock.balanceOf(signers[0].address) + ).to.equal(0); + expect( + await governanceErc20Mock.balanceOf(signers[1].address) + ).to.equal(votingSettings.minProposerVotingPower); + + // Check the `ProposalCreatedEvent` for the creator and proposalId + const event = await findEvent( + tx3, + 'ProposalCreated' ); + expect(event.args.proposalId).to.equal(id); + expect(event.args.creator).to.equal(signers[1].address); - // Check the balances before the block is mined - expect( - await governanceErc20Mock.balanceOf(signers[0].address) - ).to.equal(votingSettings.minProposerVotingPower); - expect( - await governanceErc20Mock.balanceOf(signers[1].address) - ).to.equal(0); - - // Mine the block - await ethers.provider.send('evm_mine', []); - const minedBlockNumber = (await ethers.provider.getBlock('latest')) - .number; - - // Expect all transaction receipts to be in the same block after the snapshot block. - expect((await tx1.wait()).blockNumber).to.equal(minedBlockNumber); - expect((await tx3.wait()).blockNumber).to.equal(minedBlockNumber); - expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); - - // Expect the balances to have changed - expect( - await governanceErc20Mock.balanceOf(signers[0].address) - ).to.equal(0); - expect( - await governanceErc20Mock.balanceOf(signers[1].address) - ).to.equal(votingSettings.minProposerVotingPower); - - // Check the `ProposalCreatedEvent` for the creator and proposalId - const event = await findEvent( - tx3, - 'ProposalCreated' - ); - expect(event.args.proposalId).to.equal(id); - expect(event.args.creator).to.equal(signers[1].address); - - // Check that the snapshot block stored in the proposal struct - const proposal = await voting.getProposal(id); - expect(proposal.parameters.snapshotBlock).to.equal( - expectedSnapshotBlockNumber - ); + // Check that the snapshot block stored in the proposal struct + const proposal = await voting.getProposal(id); + expect(proposal.parameters.snapshotBlock).to.equal( + expectedSnapshotBlockNumber + ); - await ethers.provider.send('evm_setAutomine', [true]); - }); + await ethers.provider.send('evm_setAutomine', [true]); + } + ); it('creates a proposal if `_msgSender` owns enough tokens in the current block', async () => { await setBalances([ @@ -687,15 +696,17 @@ describe('TokenVoting', function () { }); it('reverts if the total token supply is 0', async () => { - governanceErc20Mock = await GovernanceERC20Mock.deploy( - dao.address, - 'GOV', - 'GOV', - { - receivers: [], - amounts: [], - } - ); + governanceErc20Mock = await hre.wrapper.deploy('GovernanceERC20Mock', { + args: [ + dao.address, + 'GOV', + 'GOV', + { + receivers: [], + amounts: [], + }, + ], + }); await voting.initialize( dao.address, @@ -953,7 +964,10 @@ describe('TokenVoting', function () { .mul(votingSettings.minParticipation) .div(pctToRatio(100)) ); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + const expectedBlockNumber = hre.network.config.zksync + ? block.number - 2 + : block.number - 1; + expect(proposal.parameters.snapshotBlock).to.equal(expectedBlockNumber); expect( proposal.parameters.startDate.add(votingSettings.minDuration) ).to.equal(proposal.parameters.endDate); @@ -1025,7 +1039,10 @@ describe('TokenVoting', function () { .mul(votingSettings.minParticipation) .div(pctToRatio(100)) ); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + const expectedBlockNumber = hre.network.config.zksync + ? block.number - 2 + : block.number - 1; + expect(proposal.parameters.snapshotBlock).to.equal(expectedBlockNumber); expect( await voting.totalVotingPower(proposal.parameters.snapshotBlock) diff --git a/packages/contracts/test/plugins/governance/multisig/multisig-setup.ts b/packages/contracts/test/plugins/governance/multisig/multisig-setup.ts index a9aee2ea6..4a7be37cb 100644 --- a/packages/contracts/test/plugins/governance/multisig/multisig-setup.ts +++ b/packages/contracts/test/plugins/governance/multisig/multisig-setup.ts @@ -1,19 +1,15 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import { DAO, InterfaceBasedRegistryMock, - InterfaceBasedRegistryMock__factory, IPluginRepo__factory, Multisig, MultisigSetup, - MultisigSetup__factory, Multisig__factory, PluginRepo, - PluginRepo__factory, PluginSetupProcessor, - PluginSetupProcessor__factory, } from '../../../../typechain'; import {deployNewDAO} from '../../../test-utils/dao'; import {getInterfaceID} from '../../../test-utils/interfaces'; @@ -21,7 +17,6 @@ import {Operation} from '../../../../utils/types'; import metadata from '../../../../src/plugins/governance/multisig/build-metadata.json'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {MultisigSettings, multisigInterface} from './multisig'; -import {deployWithProxy} from '../../../test-utils/proxy'; import {findEvent} from '../../../../utils/event'; import { InstallationPreparedEvent, @@ -29,6 +24,7 @@ import { } from '../../../../typechain/PluginSetupProcessor'; import {hashHelpers} from '../../../../utils/psp'; import {getNamedTypesFromMetadata} from '../../../../utils/metadata'; +import {ARTIFACT_SOURCES} from '../../../test-utils/wrapper'; const abiCoder = ethers.utils.defaultAbiCoder; const AddressZero = ethers.constants.AddressZero; @@ -69,8 +65,7 @@ describe('MultisigSetup', function () { [[signers[0].address], Object.values(defaultMultisigSettings)] ); - const MultisigSetup = new MultisigSetup__factory(signers[0]); - multisigSetup = await MultisigSetup.deploy(); + multisigSetup = await hre.wrapper.deploy('MultisigSetup'); MultisigFactory = new Multisig__factory(signers[0]); @@ -120,13 +115,11 @@ describe('MultisigSetup', function () { [noMembers, defaultMultisigSettings] ); - const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + const nonce = await hre.wrapper.getNonce(multisigSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + multisigSetup.address, + nonce ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, - nonce, - }); const multisig = MultisigFactory.attach(anticipatedPluginAddress); @@ -154,13 +147,12 @@ describe('MultisigSetup', function () { [members, multisigSettings] ); - const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + const nonce = await hre.wrapper.getNonce(multisigSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + multisigSetup.address, + nonce ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, - nonce, - }); + const multisig = MultisigFactory.attach(anticipatedPluginAddress); await expect( @@ -187,13 +179,12 @@ describe('MultisigSetup', function () { [members, multisigSettings] ); - const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + const nonce = await hre.wrapper.getNonce(multisigSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + multisigSetup.address, + nonce ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, - nonce, - }); + const multisig = MultisigFactory.attach(anticipatedPluginAddress); await expect( @@ -207,13 +198,11 @@ describe('MultisigSetup', function () { }); it('returns the plugin, helpers, and permissions', async () => { - const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + const nonce = await hre.wrapper.getNonce(multisigSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + multisigSetup.address, + nonce ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, - nonce, - }); const { plugin, @@ -254,13 +243,11 @@ describe('MultisigSetup', function () { it('sets up the plugin', async () => { const daoAddress = targetDao.address; - const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + const nonce = await hre.wrapper.getNonce(multisigSetup.address); + const anticipatedPluginAddress = hre.wrapper.getCreateAddress( + multisigSetup.address, + nonce ); - const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, - nonce, - }); await multisigSetup.prepareInstallation(daoAddress, minimum_data); @@ -337,7 +324,6 @@ describe('MultisigSetup', function () { }); }); - // TODO: Improve checks by using smock with the proxy (We don't know how yet) describe('Updates', async () => { let psp: PluginSetupProcessor; let setup1: MultisigSetup; @@ -353,16 +339,17 @@ describe('MultisigSetup', function () { managingDAO = await deployNewDAO(owner); // Create the PluginRepo - const pluginRepoFactory = new PluginRepo__factory(owner); - pluginRepo = await deployWithProxy(pluginRepoFactory); + pluginRepo = await hre.wrapper.deploy(ARTIFACT_SOURCES.PLUGIN_REPO, { + withProxy: true, + }); await pluginRepo.initialize(owner.address); // Create the PluginRepoRegistry - const pluginRepoRegistryFactory = new InterfaceBasedRegistryMock__factory( - owner + pluginRepoRegistry = await hre.wrapper.deploy( + 'InterfaceBasedRegistryMock' ); - pluginRepoRegistry = await pluginRepoRegistryFactory.deploy(); - pluginRepoRegistry.initialize( + + await pluginRepoRegistry.initialize( managingDAO.address, getInterfaceID(IPluginRepo__factory.createInterface()) ); @@ -378,13 +365,13 @@ describe('MultisigSetup', function () { await pluginRepoRegistry.register(pluginRepo.address); // Create the PluginSetupProcessor - const pspFactory = new PluginSetupProcessor__factory(owner); - psp = await pspFactory.deploy(pluginRepoRegistry.address); + psp = await hre.wrapper.deploy('PluginSetupProcessor', { + args: [pluginRepoRegistry.address], + }); // Prepare all MultisigSetup' - We can reuse the same for now - const multisigSetupFactory = new MultisigSetup__factory(owner); - setup1 = await multisigSetupFactory.deploy(); - setup2 = await multisigSetupFactory.deploy(); + setup1 = await hre.wrapper.deploy('MultisigSetup'); + setup2 = await hre.wrapper.deploy('MultisigSetup'); // Create the versions in the plugin repo await expect(pluginRepo.createVersion(1, setup1.address, '0x00', '0x00')) diff --git a/packages/contracts/test/plugins/governance/multisig/multisig.ts b/packages/contracts/test/plugins/governance/multisig/multisig.ts index a68d1236c..58434be49 100644 --- a/packages/contracts/test/plugins/governance/multisig/multisig.ts +++ b/packages/contracts/test/plugins/governance/multisig/multisig.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {Contract, ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -33,20 +33,20 @@ import { import {deployNewDAO} from '../../../test-utils/dao'; import {OZ_ERRORS} from '../../../test-utils/error'; import { - advanceTime, getTime, setTimeForNextBlock, timestampIn, toBytes32, } from '../../../test-utils/voting'; import {UPGRADE_PERMISSIONS} from '../../../test-utils/permissions'; -import {deployWithProxy} from '../../../test-utils/proxy'; import {getInterfaceID} from '../../../test-utils/interfaces'; import { getProtocolVersion, ozUpgradeCheckManagedContract, } from '../../../test-utils/uups-upgradeable'; import {CURRENT_PROTOCOL_VERSION} from '../../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../../test-utils/wrapper'; +import {skipTestIfNetworkIsZkSync} from '../../../test-utils/skip-functions'; export const multisigInterface = new ethers.utils.Interface([ 'function initialize(address,address[],tuple(bool,uint16))', @@ -73,6 +73,28 @@ export async function approveWithSigners( await Promise.all(promises); } +async function advanceTime(time: number) { + if (hre.network.config.zksync) { + time = time / 1000; + } + await ethers.provider.send('evm_increaseTime', [time]); + await ethers.provider.send('evm_mine', []); +} + +function expectedBlockNumber(blockNumber: number) { + if (hre.network.config.zksync) { + return blockNumber - 2; + } + return blockNumber - 1; +} + +function expectedTime(time: number) { + if (hre.network.config.zksync) { + return time + 1; + } + return time; +} + describe('Multisig', function () { let signers: SignerWithAddress[]; let multisig: Multisig; @@ -105,15 +127,16 @@ describe('Multisig', function () { onlyListed: true, }; - const MultisigFactory = new Multisig__factory(signers[0]); - multisig = await deployWithProxy(MultisigFactory); + multisig = await hre.wrapper.deploy(ARTIFACT_SOURCES.MULTISIG, { + withProxy: true, + }); - dao.grant( + await dao.grant( dao.address, multisig.address, ethers.utils.id('EXECUTE_PERMISSION') ); - dao.grant( + await dao.grant( multisig.address, signers[0].address, ethers.utils.id('UPDATE_MULTISIG_SETTINGS_PERMISSION') @@ -186,13 +209,18 @@ describe('Multisig', function () { .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); }); - it('should revert if members list is longer than uint16 max', async () => { - const megaMember = signers[1]; - const members: string[] = new Array(65537).fill(megaMember.address); - await expect(multisig.initialize(dao.address, members, multisigSettings)) - .to.revertedWithCustomError(multisig, 'AddresslistLengthOutOfBounds') - .withArgs(65535, members.length); - }); + skipTestIfNetworkIsZkSync( + 'should revert if members list is longer than uint16 max', + async () => { + const megaMember = signers[1]; + const members: string[] = new Array(65537).fill(megaMember.address); + await expect( + multisig.initialize(dao.address, members, multisigSettings) + ) + .to.revertedWithCustomError(multisig, 'AddresslistLengthOutOfBounds') + .withArgs(65535, members.length); + } + ); }); describe('Upgrades', () => { @@ -208,24 +236,25 @@ describe('Multisig', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, dao, { - dao: dao.address, - members: [ - signers[0].address, - signers[1].address, - signers[2].address, - ], - multisigSettings: multisigSettings, + initArgs: { + dao: dao.address, + members: [ + signers[0].address, + signers[1].address, + signers[2].address, + ], + multisigSettings: multisigSettings, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.MULTISIG_V1_0_0, + ARTIFACT_SOURCES.MULTISIG, UPGRADE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID ); - expect(toImplementation).to.not.equal(fromImplementation); // The build did change const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) @@ -540,55 +569,58 @@ describe('Multisig', function () { ); }); - it('reverts if the multisig settings have been changed in the same block', async () => { - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings - ); - await dao.grant( - multisig.address, - dao.address, - await multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); + skipTestIfNetworkIsZkSync( + 'reverts if the multisig settings have been changed in the same block', + async () => { + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + await dao.grant( + multisig.address, + dao.address, + await multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); - await ethers.provider.send('evm_setAutomine', [false]); + await ethers.provider.send('evm_setAutomine', [false]); - const endDate = await timestampIn(5000); + const endDate = await timestampIn(5000); - await multisig.connect(signers[0]).createProposal( - dummyMetadata, - [ - { - to: multisig.address, - value: 0, - data: multisig.interface.encodeFunctionData( - 'updateMultisigSettings', - [ - { - onlyListed: false, - minApprovals: 1, - }, - ] - ), - }, - ], - 0, - true, - true, - 0, - endDate - ); - await expect( - multisig - .connect(signers[0]) - .createProposal(dummyMetadata, [], 0, true, true, 0, endDate) - ) - .to.revertedWithCustomError(multisig, 'ProposalCreationForbidden') - .withArgs(signers[0].address); + await multisig.connect(signers[0]).createProposal( + dummyMetadata, + [ + { + to: multisig.address, + value: 0, + data: multisig.interface.encodeFunctionData( + 'updateMultisigSettings', + [ + { + onlyListed: false, + minApprovals: 1, + }, + ] + ), + }, + ], + 0, + true, + true, + 0, + endDate + ); + await expect( + multisig + .connect(signers[0]) + .createProposal(dummyMetadata, [], 0, true, true, 0, endDate) + ) + .to.revertedWithCustomError(multisig, 'ProposalCreationForbidden') + .withArgs(signers[0].address); - await ethers.provider.send('evm_setAutomine', [true]); - }); + await ethers.provider.send('evm_setAutomine', [true]); + } + ); context('`onlyListed` is set to `false`:', async () => { beforeEach(async () => { @@ -674,20 +706,41 @@ describe('Multisig', function () { ).not.to.be.reverted; }); - it('reverts if `_msgSender` is not listed in the current block although he was listed in the last block', async () => { - await ethers.provider.send('evm_setAutomine', [false]); - const expectedSnapshotBlockNumber = ( - await ethers.provider.getBlock('latest') - ).number; - - // Transaction 1 & 2: Add signers[1] and remove signers[0] - const tx1 = await multisig.addAddresses([signers[1].address]); - const tx2 = await multisig.removeAddresses([signers[0].address]); + skipTestIfNetworkIsZkSync( + 'reverts if `_msgSender` is not listed in the current block although he was listed in the last block', + async () => { + await ethers.provider.send('evm_setAutomine', [false]); + const expectedSnapshotBlockNumber = ( + await ethers.provider.getBlock('latest') + ).number; + + // Transaction 1 & 2: Add signers[1] and remove signers[0] + const tx1 = await multisig.addAddresses([signers[1].address]); + const tx2 = await multisig.removeAddresses([signers[0].address]); + + // Transaction 3: Expect the proposal creation to fail for signers[0] because he was removed as a member in transaction 2. + await expect( + multisig + .connect(signers[0]) + .createProposal( + dummyMetadata, + [], + 0, + false, + false, + 0, + await timestampIn(1000) + ) + ) + .to.be.revertedWithCustomError( + multisig, + 'ProposalCreationForbidden' + ) + .withArgs(signers[0].address); - // Transaction 3: Expect the proposal creation to fail for signers[0] because he was removed as a member in transaction 2. - await expect( - multisig - .connect(signers[0]) + // Transaction 4: Create the proposal as signers[1] + const tx4 = await multisig + .connect(signers[1]) .createProposal( dummyMetadata, [], @@ -696,59 +749,44 @@ describe('Multisig', function () { false, 0, await timestampIn(1000) - ) - ) - .to.be.revertedWithCustomError(multisig, 'ProposalCreationForbidden') - .withArgs(signers[0].address); - - // Transaction 4: Create the proposal as signers[1] - const tx4 = await multisig - .connect(signers[1]) - .createProposal( - dummyMetadata, - [], - 0, - false, - false, - 0, - await timestampIn(1000) + ); + + // Check the listed members before the block is mined + expect(await multisig.isListed(signers[0].address)).to.equal(true); + expect(await multisig.isListed(signers[1].address)).to.equal(false); + + // Mine the block + await ethers.provider.send('evm_mine', []); + const minedBlockNumber = (await ethers.provider.getBlock('latest')) + .number; + + // Expect all transaction receipts to be in the same block after the snapshot block. + expect((await tx1.wait()).blockNumber).to.equal(minedBlockNumber); + expect((await tx2.wait()).blockNumber).to.equal(minedBlockNumber); + expect((await tx4.wait()).blockNumber).to.equal(minedBlockNumber); + expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); + + // Expect the listed member to have changed + expect(await multisig.isListed(signers[0].address)).to.equal(false); + expect(await multisig.isListed(signers[1].address)).to.equal(true); + + // Check the `ProposalCreatedEvent` for the creator and proposalId + const event = await findEvent( + tx4, + 'ProposalCreated' ); + expect(event.args.proposalId).to.equal(id); + expect(event.args.creator).to.equal(signers[1].address); - // Check the listed members before the block is mined - expect(await multisig.isListed(signers[0].address)).to.equal(true); - expect(await multisig.isListed(signers[1].address)).to.equal(false); - - // Mine the block - await ethers.provider.send('evm_mine', []); - const minedBlockNumber = (await ethers.provider.getBlock('latest')) - .number; - - // Expect all transaction receipts to be in the same block after the snapshot block. - expect((await tx1.wait()).blockNumber).to.equal(minedBlockNumber); - expect((await tx2.wait()).blockNumber).to.equal(minedBlockNumber); - expect((await tx4.wait()).blockNumber).to.equal(minedBlockNumber); - expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); - - // Expect the listed member to have changed - expect(await multisig.isListed(signers[0].address)).to.equal(false); - expect(await multisig.isListed(signers[1].address)).to.equal(true); - - // Check the `ProposalCreatedEvent` for the creator and proposalId - const event = await findEvent( - tx4, - 'ProposalCreated' - ); - expect(event.args.proposalId).to.equal(id); - expect(event.args.creator).to.equal(signers[1].address); - - // Check that the snapshot block stored in the proposal struct - const proposal = await multisig.getProposal(id); - expect(proposal.parameters.snapshotBlock).to.equal( - expectedSnapshotBlockNumber - ); + // Check that the snapshot block stored in the proposal struct + const proposal = await multisig.getProposal(id); + expect(proposal.parameters.snapshotBlock).to.equal( + expectedSnapshotBlockNumber + ); - await ethers.provider.send('evm_setAutomine', [true]); - }); + await ethers.provider.send('evm_setAutomine', [true]); + } + ); it('creates a proposal successfully and does not approve if not specified', async () => { const startDate = await timestampIn(1000); @@ -756,6 +794,10 @@ describe('Multisig', function () { await ethers.provider.send('evm_setNextBlockTimestamp', [startDate]); + // on zksync, even though evm_setNextBlockTimestamp sets the timestamp to our desired value, + // once the transaction is being made, timestamp on the smart contract is equal to our value + 1, + // whereas on hardhat, it's the same value that we set. So, we must pass startDate + 1 to avoid + // the "if(startDate < block.timestamp) { revert ... }" error. await expect( multisig.createProposal( dummyMetadata, @@ -763,7 +805,7 @@ describe('Multisig', function () { 0, false, false, - startDate, + startDate + 1, endDate ) ) @@ -771,7 +813,7 @@ describe('Multisig', function () { .withArgs( id, signers[0].address, - startDate, + startDate + 1, endDate, dummyMetadata, [], @@ -782,16 +824,22 @@ describe('Multisig', function () { const proposal = await multisig.getProposal(id); expect(proposal.executed).to.equal(false); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + + expect(proposal.parameters.snapshotBlock).to.equal( + expectedBlockNumber(block.number) + ); + expect(proposal.parameters.minApprovals).to.equal( multisigSettings.minApprovals ); expect(proposal.allowFailureMap).to.equal(0); - expect(proposal.parameters.startDate).to.equal(startDate); + expect(proposal.parameters.startDate).to.equal(startDate + 1); expect(proposal.parameters.endDate).to.equal(endDate); expect(proposal.approvals).to.equal(0); expect(proposal.actions.length).to.equal(0); + await ethers.provider.send('evm_mine', []); + expect(await multisig.canApprove(id, signers[0].address)).to.be.true; expect(await multisig.canApprove(id, signers[1].address)).to.be.false; }); @@ -804,6 +852,7 @@ describe('Multisig', function () { await ethers.provider.send('evm_setNextBlockTimestamp', [startDate]); + const expectedStartDate = expectedTime(startDate); await expect( multisig.createProposal( dummyMetadata, @@ -819,7 +868,7 @@ describe('Multisig', function () { .withArgs( id, signers[0].address, - startDate, + expectedStartDate, endDate, dummyMetadata, [], @@ -833,11 +882,13 @@ describe('Multisig', function () { const proposal = await multisig.getProposal(id); expect(proposal.executed).to.equal(false); expect(proposal.allowFailureMap).to.equal(allowFailureMap); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + expect(proposal.parameters.snapshotBlock).to.equal( + expectedBlockNumber(block.number) + ); expect(proposal.parameters.minApprovals).to.equal( multisigSettings.minApprovals ); - expect(proposal.parameters.startDate).to.equal(startDate); + expect(proposal.parameters.startDate).to.equal(expectedStartDate); expect(proposal.parameters.endDate).to.equal(endDate); expect(proposal.approvals).to.equal(1); }); @@ -870,12 +921,10 @@ describe('Multisig', function () { }); it('should revert if startDate is < than now', async () => { - // set next block time & mine a block with this time. - const block1Timestamp = (await getTime()) + 12; - await ethers.provider.send('evm_mine', [block1Timestamp]); - // set next block's timestamp - const block2Timestamp = block1Timestamp + 12; - await setTimeForNextBlock(block2Timestamp); + const blockTimestamp = (await getTime()) + 20; + await setTimeForNextBlock(blockTimestamp); + + const expectedTimestamp = expectedTime(blockTimestamp); await expect( multisig.createProposal( @@ -889,16 +938,20 @@ describe('Multisig', function () { ) ) .to.be.revertedWithCustomError(multisig, 'DateOutOfBounds') - .withArgs(block2Timestamp, 5); + .withArgs(expectedTimestamp, 5); }); it('should revert if endDate is < than startDate', async () => { // set next block time & mine a block with this time. const nextBlockTime = (await getTime()) + 500; - await ethers.provider.send('evm_mine', [nextBlockTime]); + + await setTimeForNextBlock(nextBlockTime); + // set next block's timestamp - const nextTimeStamp = nextBlockTime + 500; + let nextTimeStamp = nextBlockTime + 500; await setTimeForNextBlock(nextTimeStamp); + + const expectedTimestamp = expectedTime(nextTimeStamp); await expect( multisig.createProposal( dummyMetadata, @@ -911,7 +964,7 @@ describe('Multisig', function () { ) ) .to.be.revertedWithCustomError(multisig, 'DateOutOfBounds') - .withArgs(nextTimeStamp, 5); + .withArgs(expectedTimestamp, 5); }); }); diff --git a/packages/contracts/test/plugins/token/distribution/merkle-distributor.ts b/packages/contracts/test/plugins/token/distribution/merkle-distributor.ts index 6123926fa..3c2069a71 100644 --- a/packages/contracts/test/plugins/token/distribution/merkle-distributor.ts +++ b/packages/contracts/test/plugins/token/distribution/merkle-distributor.ts @@ -1,7 +1,7 @@ // Copied and modified from: https://github.com/Uniswap/merkle-distributor/blob/master/test/MerkleDistributor.spec.ts import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {BigNumber, ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -17,7 +17,6 @@ import { } from '../../../../typechain'; import {MerkleDistributor__factory as MerkleDistributor_V1_0_0__factory} from '../../../../typechain/@aragon/osx-v1.0.1/plugins/token/MerkleDistributor.sol'; -import {deployWithProxy} from '../../../test-utils/proxy'; import BalanceTree from './src/balance-tree'; import {deployNewDAO} from '../../../test-utils/dao'; import {getInterfaceID} from '../../../test-utils/interfaces'; @@ -27,6 +26,7 @@ import { ozUpgradeCheckManagedContract, } from '../../../test-utils/uups-upgradeable'; import {CURRENT_PROTOCOL_VERSION} from '../../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../../test-utils/wrapper'; const ZERO_BYTES32 = `0x${`0`.repeat(64)}`; @@ -48,11 +48,12 @@ describe('MerkleDistributor', function () { // create a DAO dao = await deployNewDAO(signers[0]); - const TestERC20 = new TestERC20__factory(signers[0]); - token = await TestERC20.deploy('FOO', 'FOO', 0); // mint 0 FOO tokens + token = await hre.wrapper.deploy('TestERC20', {args: ['FOO', 'FOO', 0]}); - const MerkleDistributor = new MerkleDistributor__factory(signers[0]); - distributor = await deployWithProxy(MerkleDistributor); + distributor = await hre.wrapper.deploy( + ARTIFACT_SOURCES.MERKLE_DISTRIBUTOR, + {withProxy: true} + ); }); describe('plugin interface: ', async () => { @@ -92,20 +93,21 @@ describe('MerkleDistributor', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, dao, { - dao: dao.address, - token: token.address, - merkleRoot: ZERO_BYTES32, + initArgs: { + dao: dao.address, + token: token.address, + merkleRoot: ZERO_BYTES32, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.MERKLE_DISTRIBUTOR_V1_0_0, + ARTIFACT_SOURCES.MERKLE_DISTRIBUTOR, UPGRADE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID ); - expect(toImplementation).to.equal(fromImplementation); // The build did not change const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) diff --git a/packages/contracts/test/plugins/token/distribution/merkle-minter.ts b/packages/contracts/test/plugins/token/distribution/merkle-minter.ts index 82d6091a9..84470f526 100644 --- a/packages/contracts/test/plugins/token/distribution/merkle-minter.ts +++ b/packages/contracts/test/plugins/token/distribution/merkle-minter.ts @@ -1,7 +1,7 @@ // Copied and modified from: https://github.com/Uniswap/merkle-distributor/blob/master/test/MerkleDistributor.spec.ts import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {artifacts, ethers} from 'hardhat'; import {BigNumber, ContractFactory} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -21,7 +21,6 @@ import {MerkleMinter__factory as MerkleMinter_V1_0_0__factory} from '../../../.. import BalanceTree from './src/balance-tree'; import {deployNewDAO} from '../../../test-utils/dao'; -import {deployWithProxy} from '../../../test-utils/proxy'; import {getInterfaceID} from '../../../test-utils/interfaces'; import {UPGRADE_PERMISSIONS} from '../../../test-utils/permissions'; import { @@ -29,6 +28,7 @@ import { ozUpgradeCheckManagedContract, } from '../../../test-utils/uups-upgradeable'; import {CURRENT_PROTOCOL_VERSION} from '../../../test-utils/protocol-version'; +import {ARTIFACT_SOURCES} from '../../../test-utils/wrapper'; const MERKLE_MINT_PERMISSION_ID = ethers.utils.id('MERKLE_MINT_PERMISSION'); const MINT_PERMISSION_ID = ethers.utils.id('MINT_PERMISSION'); @@ -64,17 +64,17 @@ describe('MerkleMinter', function () { // create a DAO managingDao = await deployNewDAO(signers[0]); - const GovernanceERC20 = new GovernanceERC20__factory(signers[0]); - token = await GovernanceERC20.deploy(managingDao.address, 'GOV', 'GOV', { - receivers: [], - amounts: [], + token = await hre.wrapper.deploy('GovernanceERC20', { + args: [managingDao.address, 'GOV', 'GOV', {receivers: [], amounts: []}], }); - const MerkleDistributor = new MerkleDistributor__factory(signers[0]); - distributorBase = await MerkleDistributor.deploy(); + distributorBase = await hre.wrapper.deploy( + ARTIFACT_SOURCES.MERKLE_DISTRIBUTOR + ); - const MerkleMinter = new MerkleMinter__factory(signers[0]); - minter = await deployWithProxy(MerkleMinter); + minter = await hre.wrapper.deploy(ARTIFACT_SOURCES.MERKLE_MINTER, { + withProxy: true, + }); await minter.initialize( managingDao.address, @@ -102,20 +102,21 @@ describe('MerkleMinter', function () { const {fromImplementation, toImplementation} = await ozUpgradeCheckManagedContract( - signers[0], - signers[1], + 0, + 1, managingDao, { - dao: managingDao.address, - token: token.address, - merkleDistributor: distributorBase.address, + initArgs: { + dao: managingDao.address, + token: token.address, + merkleDistributor: distributorBase.address, + }, + initializer: 'initialize', }, - 'initialize', - legacyContractFactory, - currentContractFactory, + ARTIFACT_SOURCES.MERKLE_MINTER_V1_0_0, + ARTIFACT_SOURCES.MERKLE_MINTER, UPGRADE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID ); - expect(toImplementation).to.equal(fromImplementation); // The build did not change const fromProtocolVersion = await getProtocolVersion( legacyContractFactory.attach(fromImplementation) diff --git a/packages/contracts/test/plugins/utils/addresslist.ts b/packages/contracts/test/plugins/utils/addresslist.ts index e834bcec0..6b20d23a9 100644 --- a/packages/contracts/test/plugins/utils/addresslist.ts +++ b/packages/contracts/test/plugins/utils/addresslist.ts @@ -1,8 +1,8 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {AddresslistMock, AddresslistMock__factory} from '../../../typechain'; +import {AddresslistMock} from '../../../typechain'; describe('AddresslistMock', function () { let signers: SignerWithAddress[]; @@ -13,8 +13,7 @@ describe('AddresslistMock', function () { }); beforeEach(async () => { - const AddresslistMock = new AddresslistMock__factory(signers[0]); - addresslist = await AddresslistMock.deploy(); + addresslist = await hre.wrapper.deploy('AddresslistMock'); }); context('addresslistLength', function () { diff --git a/packages/contracts/test/plugins/utils/ratio.ts b/packages/contracts/test/plugins/utils/ratio.ts index 0405d1e52..4892fc0ef 100644 --- a/packages/contracts/test/plugins/utils/ratio.ts +++ b/packages/contracts/test/plugins/utils/ratio.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {RatioTest, RatioTest__factory} from '../../../typechain'; import {pctToRatio, RATIO_BASE} from '../../test-utils/voting'; @@ -8,9 +8,7 @@ describe('Ratio', function () { let ratio: RatioTest; before(async () => { - const signers = await ethers.getSigners(); - const RatioTest = new RatioTest__factory(signers[0]); - ratio = await RatioTest.deploy(); + ratio = await hre.wrapper.deploy('RatioTest'); }); describe('RATIO_BASE', async () => { diff --git a/packages/contracts/test/test-utils/conditions.ts b/packages/contracts/test/test-utils/conditions.ts index 17cc58971..54b3c561f 100644 --- a/packages/contracts/test/test-utils/conditions.ts +++ b/packages/contracts/test/test-utils/conditions.ts @@ -1,13 +1,11 @@ -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; -import { - PermissionConditionMock, - PermissionConditionMock__factory, -} from '../../typechain'; +import {PermissionConditionMock} from '../../typechain'; export async function DeployTestPermissionCondition(): Promise { - const signers = await ethers.getSigners(); - const aclConditionFactory = new PermissionConditionMock__factory(signers[0]); - const permissionCondition = await aclConditionFactory.deploy(); + const permissionCondition = await hre.wrapper.deploy( + 'PermissionConditionMock' + ); + return permissionCondition; } diff --git a/packages/contracts/test/test-utils/dao.ts b/packages/contracts/test/test-utils/dao.ts index 202f5b7dc..22ae50779 100644 --- a/packages/contracts/test/test-utils/dao.ts +++ b/packages/contracts/test/test-utils/dao.ts @@ -1,15 +1,15 @@ import {BigNumber} from 'ethers'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import { DAO, ActionExecute__factory, TestERC721__factory, GovernanceERC20__factory, TestERC1155__factory, - DAO__factory, + ActionExecute, } from '../../typechain'; -import {deployWithProxy} from './proxy'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {ARTIFACT_SOURCES, Wrapper} from './wrapper'; export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -24,8 +24,7 @@ export const TOKEN_INTERFACE_IDS = { }; export async function deployNewDAO(signer: SignerWithAddress): Promise { - const DAO = new DAO__factory(signer); - const dao = await deployWithProxy(DAO); + const dao = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO, {withProxy: true}); await dao.initialize( '0x00', @@ -38,9 +37,9 @@ export async function deployNewDAO(signer: SignerWithAddress): Promise { } export async function getActions() { - const signers = await ethers.getSigners(); - const ActionExecuteFactory = new ActionExecute__factory(signers[0]); - let ActionExecute = await ActionExecuteFactory.deploy(); + const ActionExecute = (await hre.wrapper.deploy( + 'ActionExecute' + )) as ActionExecute; const iface = new ethers.utils.Interface(ActionExecute__factory.abi); const num = 20; diff --git a/packages/contracts/test/test-utils/ens.ts b/packages/contracts/test/test-utils/ens.ts index f1093a2a5..06ded7f0a 100644 --- a/packages/contracts/test/test-utils/ens.ts +++ b/packages/contracts/test/test-utils/ens.ts @@ -1,6 +1,5 @@ -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {deployWithProxy} from './proxy'; import {ensDomainHash, ensLabelHash, setupENS} from '../../utils/ens'; import { @@ -9,25 +8,18 @@ import { ENSRegistry, PublicResolver, } from '../../typechain'; -import { - ENSRegistry__factory, - ENSSubdomainRegistrar__factory, - PublicResolver__factory, -} from '../../typechain'; + +import {ARTIFACT_SOURCES} from './wrapper'; export async function deployENSSubdomainRegistrar( owner: SignerWithAddress, managingDao: DAO, domain: string ): Promise { - const ENSRegistryFactory = new ENSRegistry__factory(owner); - const ensRegistry = await ENSRegistryFactory.connect(owner).deploy(); - - const PublicResolverFactory = new PublicResolver__factory(owner); - const publicResolver = await PublicResolverFactory.connect(owner).deploy( - ensRegistry.address, - owner.address - ); + const ensRegistry = await hre.wrapper.deploy('ENSRegistry'); + const publicResolver = await hre.wrapper.deploy('PublicResolver', { + args: [ensRegistry.address, owner.address], + }); // Register subdomains in the reverse order let domainNamesReversed = domain.split('.'); @@ -50,12 +42,11 @@ export async function deployENSSubdomainRegistrar( ); } - const ENSSubdomainRegistrar = new ENSSubdomainRegistrar__factory(owner); - - // Deploy the ENS and approve the subdomain registrar - const ensSubdomainRegistrar = await deployWithProxy( - ENSSubdomainRegistrar + const ensSubdomainRegistrar = await hre.wrapper.deploy( + ARTIFACT_SOURCES.ENS_SUBDOMAIN_REGISTRAR, + {withProxy: true} ); + await ensRegistry .connect(owner) .setApprovalForAll(ensSubdomainRegistrar.address, true); diff --git a/packages/contracts/test/test-utils/matcher.ts b/packages/contracts/test/test-utils/matcher.ts new file mode 100644 index 000000000..094e8aac8 --- /dev/null +++ b/packages/contracts/test/test-utils/matcher.ts @@ -0,0 +1,506 @@ +import {AssertionError} from 'chai'; +import {buildAssert} from '@nomicfoundation/hardhat-chai-matchers/utils.js'; +import {decodeReturnData} from '@nomicfoundation/hardhat-chai-matchers/internal/reverted/utils.js'; +import chai from 'chai'; + +/// The below code overwrites the behaviour of the `revertedWith` matcher to support how zkSync and ethers-v5 +/// encode and handle errors. The functions below are lifted from the `hardhat-chai-matchers` package and modified +/// to check for deeper nesting of error.data in the error object. +/// Unfortunately, the `hardhat-chai-matchers` package does not have a way to super the `revertedWith` matcher, so +/// we have to copy the code here and modify i,t. +/// We also directly import the javascript files from the `hardhat-chai-matchers` package to avoid issues with import paths. +/// See https://github.com/ethers-io/ethers.js/discussions/4715 for full details. + +chai.use(({Assertion}) => { + supportReverted(Assertion); + supportRevertedWith(Assertion); + supportRevertedWithCustomError(Assertion, chai.util); +}); + +/** + * Try to obtain the return data of a transaction from the given value. + * + * If the value is an error but it doesn't have data, we assume it's not related + * to a reverted transaction and we re-throw it. + */ +export function getReturnDataFromError(error: any): string { + if (!(error instanceof Error)) { + throw new AssertionError('Expected an Error object'); + } + + // cast to any again so we don't have to cast it every time we access + // some property that doesn't exist on Error + error = error as any; + + // This is the changed line, we have to check for deeply nested error.data otherwise + // ethers will re-throw our error and our tests won't work. + // If you can find a better way to do this, please let me know. + const errorData = + error.data ?? + error.error?.data ?? + error.error?.error?.data ?? + error.error?.error?.error?.data; + + if (errorData === undefined) { + throw error; + } + + const returnData = typeof errorData === 'string' ? errorData : errorData.data; + + if (returnData === undefined || typeof returnData !== 'string') { + throw error; + } + + return returnData; +} + +export function supportRevertedWith(Assertion: Chai.AssertionStatic) { + console.debug('Overwriting revertedWith matcher'); + + Assertion.addMethod( + 'revertedWith', + function (this: any, expectedReason: string | RegExp) { + // capture negated flag before async code executes; see buildAssert's jsdoc + const negated = this.__flags.negate; + + // validate expected reason + if ( + !(expectedReason instanceof RegExp) && + typeof expectedReason !== 'string' + ) { + throw new TypeError( + 'Expected the revert reason to be a string or a regular expression' + ); + } + + const expectedReasonString = + expectedReason instanceof RegExp + ? expectedReason.source + : expectedReason; + + const onSuccess = () => { + const assert = buildAssert(negated, onSuccess); + + assert( + false, + `Expected transaction to be reverted with reason '${expectedReasonString}', but it didn't revert` + ); + }; + + const onError = (error: any) => { + const assert = buildAssert(negated, onError); + + const returnData = getReturnDataFromError(error); + const decodedReturnData = decodeReturnData(returnData); + if (decodedReturnData.kind === 'Empty') { + assert( + false, + `Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted without a reason` + ); + } else if (decodedReturnData.kind === 'Error') { + const matchesExpectedReason = + expectedReason instanceof RegExp + ? expectedReason.test(decodedReturnData.reason) + : decodedReturnData.reason === expectedReasonString; + + assert( + matchesExpectedReason, + `Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with reason '${decodedReturnData.reason}'`, + `Expected transaction NOT to be reverted with reason '${expectedReasonString}', but it was` + ); + } else if (decodedReturnData.kind === 'Panic') { + assert( + false, + `Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ + decodedReturnData.description + })` + ); + } else if (decodedReturnData.kind === 'Custom') { + assert( + false, + `Expected transaction to be reverted with reason '${expectedReasonString}', but it reverted with a custom error` + ); + } else { + const _exhaustiveCheck: never = decodedReturnData; + } + }; + + const derivedPromise = Promise.resolve(this._obj).then( + onSuccess, + onError + ); + + this.then = derivedPromise.then.bind(derivedPromise); + this.catch = derivedPromise.catch.bind(derivedPromise); + + return this; + } + ); +} + +export const REVERTED_WITH_CUSTOM_ERROR_CALLED = 'customErrorAssertionCalled'; + +interface CustomErrorAssertionData { + contractInterface: any; + returnData: string; + customError: CustomError; +} + +export function supportRevertedWithCustomError( + Assertion: Chai.AssertionStatic, + utils: Chai.ChaiUtils +) { + Assertion.addMethod( + 'revertedWithCustomError', + function (this: any, contract: any, expectedCustomErrorName: string) { + // capture negated flag before async code executes; see buildAssert's jsdoc + const negated = this.__flags.negate; + + // check the case where users forget to pass the contract as the first + // argument + if (typeof contract === 'string' || contract?.interface === undefined) { + throw new TypeError( + 'The first argument of .revertedWithCustomError must be the contract that defines the custom error' + ); + } + + // validate custom error name + if (typeof expectedCustomErrorName !== 'string') { + throw new TypeError('Expected the custom error name to be a string'); + } + + const iface: any = contract.interface; + + const expectedCustomError = findCustomErrorByName( + iface, + expectedCustomErrorName + ); + + // check that interface contains the given custom error + if (expectedCustomError === undefined) { + throw new Error( + `The given contract doesn't have a custom error named '${expectedCustomErrorName}'` + ); + } + + const onSuccess = () => { + const assert = buildAssert(negated, onSuccess); + + assert( + false, + `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it didn't revert` + ); + }; + + const onError = (error: any) => { + const assert = buildAssert(negated, onError); + + const returnData = getReturnDataFromError(error); + const decodedReturnData = decodeReturnData(returnData); + + if (decodedReturnData.kind === 'Empty') { + assert( + false, + `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted without a reason` + ); + } else if (decodedReturnData.kind === 'Error') { + assert( + false, + `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted with reason '${decodedReturnData.reason}'` + ); + } else if (decodedReturnData.kind === 'Panic') { + assert( + false, + `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ + decodedReturnData.description + })` + ); + } else if (decodedReturnData.kind === 'Custom') { + if (decodedReturnData.id === expectedCustomError.id) { + // add flag with the data needed for .withArgs + const customErrorAssertionData: CustomErrorAssertionData = { + contractInterface: iface, + customError: expectedCustomError, + returnData, + }; + this.customErrorData = customErrorAssertionData; + + assert( + true, + undefined, + `Expected transaction NOT to be reverted with custom error '${expectedCustomErrorName}', but it was` + ); + } else { + // try to decode the actual custom error + // this will only work when the error comes from the given contract + const actualCustomError = findCustomErrorById( + iface, + decodedReturnData.id + ); + + if (actualCustomError === undefined) { + assert( + false, + `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted with a different custom error` + ); + } else { + assert( + false, + `Expected transaction to be reverted with custom error '${expectedCustomErrorName}', but it reverted with custom error '${actualCustomError.name}'` + ); + } + } + } else { + const _exhaustiveCheck: never = decodedReturnData; + } + }; + + const derivedPromise = Promise.resolve(this._obj).then( + onSuccess, + onError + ); + + // needed for .withArgs + utils.flag(this, REVERTED_WITH_CUSTOM_ERROR_CALLED, true); + this.promise = derivedPromise; + + this.then = derivedPromise.then.bind(derivedPromise); + this.catch = derivedPromise.catch.bind(derivedPromise); + + return this; + } + ); +} +export type Ssfi = (...args: any[]) => any; +export async function revertedWithCustomErrorWithArgs( + context: any, + Assertion: Chai.AssertionStatic, + _: Chai.ChaiUtils, + expectedArgs: any[], + ssfi: Ssfi +) { + const negated = false; // .withArgs cannot be negated + const assert = buildAssert(negated, ssfi); + + const customErrorAssertionData: CustomErrorAssertionData = + context.customErrorData; + + if (customErrorAssertionData === undefined) { + throw new Error( + '[.withArgs] should never happen, please submit an issue to the Hardhat repository' + ); + } + + const {contractInterface, customError, returnData} = customErrorAssertionData; + + const errorFragment = contractInterface.errors[customError.signature]; + // We transform ether's Array-like object into an actual array as it's safer + const actualArgs = Array.from( + contractInterface.decodeErrorResult(errorFragment, returnData) + ); + + new Assertion(actualArgs).to.have.same.length( + expectedArgs.length, + `expected ${expectedArgs.length} args but got ${actualArgs.length}` + ); + + for (const [i, actualArg] of actualArgs.entries()) { + const expectedArg = expectedArgs[i]; + if (typeof expectedArg === 'function') { + const errorPrefix = `The predicate for custom error argument with index ${i}`; + try { + assert( + expectedArg(actualArg), + `${errorPrefix} returned false` + // no need for a negated message, since we disallow mixing .not. with + // .withArgs + ); + } catch (e) { + if (e instanceof AssertionError) { + assert( + false, + `${errorPrefix} threw an AssertionError: ${e.message}` + // no need for a negated message, since we disallow mixing .not. with + // .withArgs + ); + } + throw e; + } + } else if (Array.isArray(expectedArg)) { + new Assertion(actualArg).to.deep.equal(expectedArg); + } else { + new Assertion(actualArg).to.equal(expectedArg); + } + } +} + +interface CustomError { + name: string; + id: string; + signature: string; +} + +function findCustomErrorByName( + iface: any, + name: string +): CustomError | undefined { + const ethers = require('ethers'); + + const customErrorEntry = Object.entries(iface.errors).find( + ([, fragment]: any) => fragment.name === name + ); + + if (customErrorEntry === undefined) { + return undefined; + } + + const [customErrorSignature] = customErrorEntry; + const customErrorId = ethers.utils.id(customErrorSignature).slice(0, 10); + + return { + id: customErrorId, + name, + signature: customErrorSignature, + }; +} + +function findCustomErrorById(iface: any, id: string): CustomError | undefined { + const ethers = require('ethers'); + + const customErrorEntry: any = Object.entries(iface.errors).find( + ([signature]: any) => ethers.utils.id(signature).slice(0, 10) === id + ); + + if (customErrorEntry === undefined) { + return undefined; + } + + return { + id, + name: customErrorEntry[1].name, + signature: customErrorEntry[0], + }; +} + +export function supportReverted(Assertion: Chai.AssertionStatic) { + Assertion.addProperty('reverted', function (this: any) { + // capture negated flag before async code executes; see buildAssert's jsdoc + const negated = this.__flags.negate; + + const subject: unknown = this._obj; + + // Check if the received value can be linked to a transaction, and then + // get the receipt of that transaction and check its status. + // + // If the value doesn't correspond to a transaction, then the `reverted` + // assertion is false. + const onSuccess = async (value: unknown) => { + const assert = buildAssert(negated, onSuccess); + + if (isTransactionResponse(value) || typeof value === 'string') { + const hash = typeof value === 'string' ? value : value.hash; + + if (!isValidTransactionHash(hash)) { + throw new TypeError( + `Expected a valid transaction hash, but got '${hash}'` + ); + } + + const receipt = await getTransactionReceipt(hash); + + assert( + receipt.status === 0, + 'Expected transaction to be reverted', + 'Expected transaction NOT to be reverted' + ); + } else if (isTransactionReceipt(value)) { + const receipt = value; + + assert( + receipt.status === 0, + 'Expected transaction to be reverted', + 'Expected transaction NOT to be reverted' + ); + } else { + // If the subject of the assertion is not connected to a transaction + // (hash, receipt, etc.), then the assertion fails. + // Since we use `false` here, this means that `.not.to.be.reverted` + // assertions will pass instead of always throwing a validation error. + // This allows users to do things like: + // `expect(c.callStatic.f()).to.not.be.reverted` + assert(false, 'Expected transaction to be reverted'); + } + }; + + const onError = (error: any) => { + const assert = buildAssert(negated, onError); + const returnData = getReturnDataFromError(error); + const decodedReturnData = decodeReturnData(returnData); + + if ( + decodedReturnData.kind === 'Empty' || + decodedReturnData.kind === 'Custom' + ) { + // in the negated case, if we can't decode the reason, we just indicate + // that the transaction didn't revert + assert(true, undefined, `Expected transaction NOT to be reverted`); + } else if (decodedReturnData.kind === 'Error') { + assert( + true, + undefined, + `Expected transaction NOT to be reverted, but it reverted with reason '${decodedReturnData.reason}'` + ); + } else if (decodedReturnData.kind === 'Panic') { + assert( + true, + undefined, + `Expected transaction NOT to be reverted, but it reverted with panic code ${decodedReturnData.code.toHexString()} (${ + decodedReturnData.description + })` + ); + } else { + const _exhaustiveCheck: never = decodedReturnData; + } + }; + + // we use `Promise.resolve(subject)` so we can process both values and + // promises of values in the same way + const derivedPromise = Promise.resolve(subject).then(onSuccess, onError); + + this.then = derivedPromise.then.bind(derivedPromise); + this.catch = derivedPromise.catch.bind(derivedPromise); + + return this; + }); +} + +async function getTransactionReceipt(hash: string) { + const hre = await import('hardhat'); + + return hre.ethers.provider.getTransactionReceipt(hash); +} + +function isTransactionResponse(x: unknown): x is {hash: string} { + if (typeof x === 'object' && x !== null) { + return 'hash' in x; + } + + return false; +} + +function isTransactionReceipt(x: unknown): x is {status: number} { + if (typeof x === 'object' && x !== null && 'status' in x) { + const status = (x as any).status; + + // this means we only support ethers's receipts for now; adding support for + // raw receipts, where the status is an hexadecimal string, should be easy + // and we can do it if there's demand for that + return typeof status === 'number'; + } + + return false; +} + +function isValidTransactionHash(x: string): boolean { + return /0x[0-9a-fA-F]{64}/.test(x); +} diff --git a/packages/contracts/test/test-utils/permissions.ts b/packages/contracts/test/test-utils/permissions.ts index e17bf2756..fc4c33fce 100644 --- a/packages/contracts/test/test-utils/permissions.ts +++ b/packages/contracts/test/test-utils/permissions.ts @@ -10,4 +10,5 @@ export const UPGRADE_PERMISSIONS = { 'UPGRADE_REGISTRAR_PERMISSION' ), UPGRADE_REPO_PERMISSION_ID: ethers.utils.id('UPGRADE_REPO_PERMISSION'), + UPGRADE_PSP_PERMISSION_ID: ethers.utils.id('UPGRADE_PSP_PERMISSION'), }; diff --git a/packages/contracts/test/test-utils/plugin-setup-processor.ts b/packages/contracts/test/test-utils/plugin-setup-processor.ts index bf139ac17..14064df6a 100644 --- a/packages/contracts/test/test-utils/plugin-setup-processor.ts +++ b/packages/contracts/test/test-utils/plugin-setup-processor.ts @@ -1,9 +1,10 @@ -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import { PluginSetupProcessor__factory, PluginRepoRegistry, PluginSetupProcessor, + DAO, } from '../../typechain'; export async function deployPluginSetupProcessor( @@ -11,11 +12,26 @@ export async function deployPluginSetupProcessor( ): Promise { let psp: PluginSetupProcessor; - const PluginSetupProcessor = new PluginSetupProcessor__factory( - (await ethers.getSigners())[0] - ); + psp = await hre.wrapper.deploy('PluginSetupProcessor', { + args: [pluginRepoRegistry.address], + }); - psp = await PluginSetupProcessor.deploy(pluginRepoRegistry.address); + return psp; +} + +export async function deployUpgradeablePluginSetupProcessor( + dao: DAO, + pluginRepoRegistry: PluginRepoRegistry +): Promise { + let psp: PluginSetupProcessor; + + psp = await hre.wrapper.deploy('PluginSetupProcessorUpgradeable', { + withProxy: true, + initArgs: [dao.address, pluginRepoRegistry.address], + proxySettings: { + initializer: 'initialize', + }, + }); return psp; } diff --git a/packages/contracts/test/test-utils/repo.ts b/packages/contracts/test/test-utils/repo.ts index e024e2872..6d5b09d0c 100644 --- a/packages/contracts/test/test-utils/repo.ts +++ b/packages/contracts/test/test-utils/repo.ts @@ -1,4 +1,4 @@ -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import { PluginRepoRegistry, @@ -10,23 +10,22 @@ import { PluginRepoRegistry__factory, PluginRepoFactory__factory, } from '../../typechain'; -import {deployWithProxy} from './proxy'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {ARTIFACT_SOURCES} from './wrapper'; export async function deployMockPluginSetup( signer: SignerWithAddress ): Promise { - const PluginSetupMock = new PluginUUPSUpgradeableSetupV1Mock__factory(signer); - const pluginSetupMockContract = await PluginSetupMock.deploy(); - - return pluginSetupMockContract; + return await hre.wrapper.deploy('PluginUUPSUpgradeableSetupV1Mock'); } export async function deployNewPluginRepo( maintainer: SignerWithAddress ): Promise { - const PluginRepo = new PluginRepo__factory(maintainer); - const newPluginRepo = await deployWithProxy(PluginRepo); + const newPluginRepo = await hre.wrapper.deploy(ARTIFACT_SOURCES.PLUGIN_REPO, { + withProxy: true, + }); + await newPluginRepo.initialize(maintainer.address); return newPluginRepo; @@ -37,11 +36,9 @@ export async function deployPluginRepoFactory( pluginRepoRegistry: PluginRepoRegistry ): Promise { // PluginRepoFactory - const PluginRepoFactory = new PluginRepoFactory__factory(signers[0]); - - const pluginRepoFactory = await PluginRepoFactory.deploy( - pluginRepoRegistry.address - ); + const pluginRepoFactory = await hre.wrapper.deploy('PluginRepoFactory', { + args: [pluginRepoRegistry.address], + }); return pluginRepoFactory; } @@ -51,10 +48,9 @@ export async function deployPluginRepoRegistry( ensSubdomainRegistrar: any, signer: SignerWithAddress ): Promise { - const PluginRepoRegistry = new PluginRepoRegistry__factory(signer); - - let pluginRepoRegistry = await deployWithProxy( - PluginRepoRegistry + let pluginRepoRegistry = await hre.wrapper.deploy( + ARTIFACT_SOURCES.PLUGIN_REPO_REGISTRY, + {withProxy: true} ); await pluginRepoRegistry.initialize( diff --git a/packages/contracts/test/test-utils/skip-functions.ts b/packages/contracts/test/test-utils/skip-functions.ts new file mode 100644 index 000000000..60f92b595 --- /dev/null +++ b/packages/contracts/test/test-utils/skip-functions.ts @@ -0,0 +1,76 @@ +import hre from 'hardhat'; +import {ZK_SYNC_NETWORKS} from '../../utils/zkSync'; + +// ANSI escape codes for colored terminal output +const YELLOW = '\x1b[33m'; // Yellow color for SKIPPED +const BLUE = '\x1b[34m'; // Blue color for message +const RESET = '\x1b[0m'; // Reset to default terminal color + +const logSkipped = (testName: string, reason?: string) => + console.log( + `${YELLOW}SKIPPING TEST ${ + reason ? '(' + reason + ')' : '' + }${RESET}: ${BLUE}${testName}${RESET}` + ); + +const logSkippedSuite = (testName: string, reason?: string) => + console.log( + `${YELLOW}SKIPPING TEST SUITE ${ + reason ? '(' + reason + ')' : '' + }${RESET}: ${BLUE}${testName}${RESET}` + ); + +/** + * Creates a conditional test function that skips based on the provided condition. + * @param condition - The condition upon which to skip the test. + * @returns A function to define a test, which will skip based on the condition. + */ +export function skipTestIf(condition: boolean, reason?: string) { + return ( + testName: string, + testFunc: ((args: any) => void) | ((args: any) => Promise) + ) => { + if (condition) { + logSkipped(testName, reason); + return it.skip(testName, testFunc); + } else { + return it(testName, testFunc); + } + }; +} + +/** + * Creates a conditional test suite that skips based on the provided condition. + * @param condition - The condition upon which to skip the test. + * @returns A function to define a test, which will skip based on the condition. + */ +export function skipDescribeIf(condition: boolean, reason?: string) { + return (testName: string, testFunc: (() => void) | (() => Promise)) => { + if (condition) { + logSkippedSuite(testName, reason); + return describe.skip(testName, testFunc); + } else { + return describe(testName, testFunc); + } + }; +} + +export function skipTestIfNetworks(networksToSkip: string[], reason?: string) { + return skipTestIf(networksToSkip.includes(hre.network.name), reason); +} + +export function skipDescribeIfNetworks( + networksToSkip: string[], + reason?: string +) { + return skipDescribeIf(networksToSkip.includes(hre.network.name), reason); +} + +export const skipTestSuiteIfNetworkIsZkSync = skipDescribeIfNetworks( + ZK_SYNC_NETWORKS, + 'ZkSync network' +); +export const skipTestIfNetworkIsZkSync = skipTestIfNetworks( + ZK_SYNC_NETWORKS, + 'ZkSync network' +); diff --git a/packages/contracts/test/test-utils/uups-upgradeable.ts b/packages/contracts/test/test-utils/uups-upgradeable.ts index 0d027ea83..fe830d6d2 100644 --- a/packages/contracts/test/test-utils/uups-upgradeable.ts +++ b/packages/contracts/test/test-utils/uups-upgradeable.ts @@ -1,36 +1,40 @@ -import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {Contract, ContractFactory, errors} from 'ethers'; -import {upgrades} from 'hardhat'; +import {errors} from 'ethers'; +import hre from 'hardhat'; import {DAO} from '../../typechain'; import {readImplementationValueFromSlot} from '../../utils/storage'; +import {Contract} from 'zksync-ethers'; + +type options = { + args?: Record; + initArgs?: Record; + initializer?: string | undefined; +}; // Deploys and upgrades a contract that is managed by a DAO export async function ozUpgradeCheckManagedContract( - deployer: SignerWithAddress, - upgrader: SignerWithAddress, + deployer: number, + upgrader: number, managingDao: DAO, - initArgs: any, - initializerName: string, - from: ContractFactory, - to: ContractFactory, + {args = {}, initArgs = {}, initializer = undefined}: options, + from: string, + to: string, upgradePermissionId: string ): Promise<{ proxy: Contract; fromImplementation: string; toImplementation: string; }> { - // Deploy the proxy - let proxy = await upgrades.deployProxy( - from.connect(deployer), - Object.values(initArgs), - { - kind: 'uups', - initializer: initializerName, - unsafeAllow: ['constructor'], - constructorArgs: [], - } - ); + const deployerSigner = (await hre.ethers.getSigners())[deployer]; + const upgraderSigner = (await hre.ethers.getSigners())[upgrader]; + + let proxy = await hre.wrapper.deployProxy(deployer, from, { + args: Object.values(args), + initArgs: Object.values(initArgs), + proxySettings: { + initializer: initializer, + }, + }); const fromImplementation = await readImplementationValueFromSlot( proxy.address @@ -38,28 +42,26 @@ export async function ozUpgradeCheckManagedContract( // Check that upgrade permission is required await expect( - upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { - unsafeAllow: ['constructor'], - constructorArgs: [], + hre.wrapper.upgradeProxy(upgrader, proxy.address, to, { + args: Object.values(args), }) ) .to.be.revertedWithCustomError(proxy, 'DaoUnauthorized') .withArgs( managingDao.address, proxy.address, - upgrader.address, + upgraderSigner.address, upgradePermissionId ); // Grant the upgrade permission await managingDao - .connect(deployer) - .grant(proxy.address, upgrader.address, upgradePermissionId); + .connect(deployerSigner) + .grant(proxy.address, upgraderSigner.address, upgradePermissionId); // Upgrade the proxy - await upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { - unsafeAllow: ['constructor'], - constructorArgs: [], + await hre.wrapper.upgradeProxy(upgrader, proxy.address, to, { + args: Object.values(args), }); const toImplementation = await readImplementationValueFromSlot(proxy.address); @@ -69,52 +71,49 @@ export async function ozUpgradeCheckManagedContract( // Deploys and upgrades a contract that has its own permission manager export async function ozUpgradeCheckManagingContract( - deployer: SignerWithAddress, - upgrader: SignerWithAddress, - initArgs: any, - initializerName: string, - from: ContractFactory, - to: ContractFactory, + deployer: number, + upgrader: number, + {args = {}, initArgs = {}, initializer = undefined}: options, + from: string, + to: string, upgradePermissionId: string ): Promise<{ proxy: Contract; fromImplementation: string; toImplementation: string; }> { + const deployerSigner = (await hre.ethers.getSigners())[deployer]; + const upgraderSigner = (await hre.ethers.getSigners())[upgrader]; + // Deploy the proxy - let proxy = await upgrades.deployProxy( - from.connect(deployer), - Object.values(initArgs), - { - kind: 'uups', - initializer: initializerName, - unsafeAllow: ['constructor'], - constructorArgs: [], - } - ); + let proxy = await hre.wrapper.deployProxy(deployer, from, { + args: Object.values(args), + initArgs: Object.values(initArgs), + proxySettings: { + initializer: initializer, + }, + }); const fromImplementation = await readImplementationValueFromSlot( proxy.address ); + // Check that upgrade permission is required await expect( - upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { - unsafeAllow: ['constructor'], - constructorArgs: [], + hre.wrapper.upgradeProxy(upgrader, proxy.address, to, { + args: Object.values(args), }) ) .to.be.revertedWithCustomError(proxy, 'Unauthorized') - .withArgs(proxy.address, upgrader.address, upgradePermissionId); + .withArgs(proxy.address, upgraderSigner.address, upgradePermissionId); // Grant the upgrade permission await proxy - .connect(deployer) - .grant(proxy.address, upgrader.address, upgradePermissionId); + .connect(deployerSigner) + .grant(proxy.address, upgraderSigner.address, upgradePermissionId); - // Upgrade the proxy - await upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { - unsafeAllow: ['constructor'], - constructorArgs: [], + await hre.wrapper.upgradeProxy(upgrader, proxy.address, to, { + args: Object.values(args), }); const toImplementation = await readImplementationValueFromSlot(proxy.address); diff --git a/packages/contracts/test/test-utils/voting.ts b/packages/contracts/test/test-utils/voting.ts index 44c2a5b83..492fe6865 100644 --- a/packages/contracts/test/test-utils/voting.ts +++ b/packages/contracts/test/test-utils/voting.ts @@ -1,4 +1,4 @@ -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {expect} from 'chai'; import {BigNumber, Contract} from 'ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -48,7 +48,9 @@ export async function advanceTimeTo(timestamp: number) { } export async function advanceIntoVoteTime(startDate: number, endDate: number) { - await advanceTimeTo(startDate); + await setTimeForNextBlock(startDate); + await ethers.provider.send('evm_mine', []); + expect(await getTime()).to.be.greaterThanOrEqual(startDate); expect(await getTime()).to.be.lessThan(endDate); } diff --git a/packages/contracts/test/test-utils/wrapper/hardhat.ts b/packages/contracts/test/test-utils/wrapper/hardhat.ts new file mode 100644 index 000000000..003db0be3 --- /dev/null +++ b/packages/contracts/test/test-utils/wrapper/hardhat.ts @@ -0,0 +1,102 @@ +import hre from 'hardhat'; + +import {BigNumberish, Contract, providers} from 'ethers'; +import {utils} from 'ethers'; + +import {DeployOptions, NetworkDeployment} from '.'; + +export class HardhatClass implements NetworkDeployment { + provider: providers.BaseProvider; + constructor(_provider: providers.BaseProvider) { + this.provider = _provider; + } + + async deploy(artifactName: string, args: any[] = []) { + const {ethers} = hre; + const signers = await ethers.getSigners(); + const artifact = await hre.artifacts.readArtifact(artifactName); + let contract = await new ethers.ContractFactory( + artifact.abi, + artifact.bytecode, + signers[0] + ).deploy(...args); + + return {artifact, contract}; + } + + async encodeFunctionData( + artifactName: string, + functionName: string, + args: any[] + ): Promise { + const {ethers} = hre; + const signers = await ethers.getSigners(); + const artifact = await hre.artifacts.readArtifact(artifactName); + const contract = new ethers.ContractFactory( + artifact.abi, + artifact.bytecode, + signers[0] + ); + + const fragment = contract.interface.getFunction(functionName); + return contract.interface.encodeFunctionData(fragment, args); + } + + getCreateAddress(sender: string, nonce: BigNumberish): string { + return utils.getContractAddress({from: sender, nonce: nonce}); + } + + async getNonce( + sender: string, + type?: 'Deployment' | 'Transaction' + ): Promise { + return this.provider.getTransactionCount(sender); + } + + async deployProxy( + deployer: number, + artifactName: string, + options: DeployOptions + ): Promise { + const {ethers} = hre; + const signer = (await ethers.getSigners())[deployer]; + + const artifact = await hre.artifacts.readArtifact(artifactName); + const contract = new ethers.ContractFactory( + artifact.abi, + artifact.bytecode, + signer + ); + + // Currently, it doesn't use type and always deployes with uups + return hre.upgrades.deployProxy(contract, options.initArgs, { + kind: options.proxySettings?.type, + initializer: options.proxySettings?.initializer ?? false, + unsafeAllow: ['constructor'], + constructorArgs: options.args, + }); + } + + async upgradeProxy( + upgrader: number, + proxyAddress: string, + newArtifactName: string, + options: DeployOptions + ): Promise { + const {ethers} = hre; + const signer = (await ethers.getSigners())[upgrader]; + + const artifact = await hre.artifacts.readArtifact(newArtifactName); + + let contract = new ethers.ContractFactory( + artifact.abi, + artifact.bytecode, + signer + ); + + return hre.upgrades.upgradeProxy(proxyAddress, contract, { + unsafeAllow: ['constructor'], + constructorArgs: options.args, + }); + } +} diff --git a/packages/contracts/test/test-utils/wrapper/index.ts b/packages/contracts/test/test-utils/wrapper/index.ts new file mode 100644 index 000000000..52f4fc44a --- /dev/null +++ b/packages/contracts/test/test-utils/wrapper/index.ts @@ -0,0 +1,201 @@ +import hre, {ethers} from 'hardhat'; +import {findEvent} from '../../../utils/event'; +import {ProxyCreatedEvent} from '../../../typechain/ProxyFactory'; +import {BigNumberish, Contract, Wallet} from 'ethers'; +import {providers} from 'ethers'; + +import {HardhatClass} from './hardhat'; +import {ZkSync} from './zksync'; + +// TODO: generate paths programatically. +export const ARTIFACT_SOURCES = { + DAO: 'src/core/dao/DAO.sol:DAO', + DAO_V1_0_0: '@aragon/osx-v1.0.1/core/dao/DAO.sol:DAO', + PLUGIN_REPO: 'src/framework/plugin/repo/PluginRepo.sol:PluginRepo', + PLUGIN_REPO_V1_0_0: + '@aragon/osx-v1.0.1/framework/plugin/repo/PluginRepo.sol:PluginRepo', + DAO_REGISTRY: 'src/framework/dao/DAORegistry.sol:DAORegistry', + DAO_REGISTRY_V1_0_0: + '@aragon/osx-v1.0.1/framework/dao/DAORegistry.sol:DAORegistry', + PLUGIN_REPO_REGISTRY: + 'src/framework/plugin/repo/PluginRepoRegistry.sol:PluginRepoRegistry', + PLUGIN_REPO_REGISTRY_V1_0_0: + '@aragon/osx-v1.0.1/framework/plugin/repo/PluginRepoRegistry.sol:PluginRepoRegistry', + ENS_SUBDOMAIN_REGISTRAR: + 'src/framework/utils/ens/ENSSubdomainRegistrar.sol:ENSSubdomainRegistrar', + ENS_SUBDOMAIN_REGISTRAR_V1_0_0: + '@aragon/osx-v1.0.1/framework/utils/ens/ENSSubdomainRegistrar.sol:ENSSubdomainRegistrar', + MERKLE_DISTRIBUTOR: + 'src/plugins/token/MerkleDistributor.sol:MerkleDistributor', + MERKLE_DISTRIBUTOR_V1_0_0: + '@aragon/osx-v1.0.1/plugins/token/MerkleDistributor.sol:MerkleDistributor', + MERKLE_MINTER: 'src/plugins/token/MerkleMinter.sol:MerkleMinter', + MERKLE_MINTER_V1_0_0: + '@aragon/osx-v1.0.1/plugins/token/MerkleMinter.sol:MerkleMinter', + ADDRESSLIST_VOTING: + 'src/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol:AddresslistVoting', + ADDRESSLIST_VOTING_V1_0_0: + '@aragon/osx-v1.0.1/plugins/governance/majority-voting/addresslist/AddresslistVoting.sol:AddresslistVoting', + TOKEN_VOTING: + 'src/plugins/governance/majority-voting/token/TokenVoting.sol:TokenVoting', + TOKEN_VOTING_V1_0_0: + '@aragon/osx-v1.0.1/plugins/governance/majority-voting/token/TokenVoting.sol:TokenVoting', + MULTISIG: 'src/plugins/governance/multisig/Multisig.sol:Multisig', + MULTISIG_V1_0_0: + '@aragon/osx-v1.0.1/plugins/governance/multisig/Multisig.sol:Multisig', +}; + +export type DeployOptions = { + initArgs?: any[]; // initialize function arguments in case `withProxy` is set to true. + args?: any[]; // constructor arguments + withProxy?: boolean; + proxySettings?: { + type?: 'uups' | 'transparent' | 'beacon' | undefined; + initializer?: string; + }; +}; + +export interface NetworkDeployment { + deploy(artifactName: string, args: any[]): any; + getCreateAddress(sender: string, nonce: BigNumberish): string; + getNonce( + sender: string, + type?: 'Deployment' | 'Transaction' + ): Promise; + encodeFunctionData( + artifactName: string, + functionName: string, + args: any[] + ): Promise; + deployProxy( + deployer: number, + artifactName: string, + options: DeployOptions + ): Promise; + upgradeProxy( + upgrader: number, + proxyAddress: string, + newArtifactName: string, + options: DeployOptions + ): Promise; +} + +export class Wrapper { + network: NetworkDeployment; + + constructor(_network: NetworkDeployment) { + this.network = _network; + } + + // Creates an according wrapper class depending on the network. + // Note that on zksync network, node only has 10 rich addresses whereas + // on hardhat, it's 20. Tests are heavily using the numbers in the Signers + // object from 10 to 20. So We make 10 custom addresses rich-funded to + // allow tests use the same approach on zksync as on hardhat. + static async create(networkName: string, provider: providers.BaseProvider) { + if (networkName == 'zkLocalTestnet' || networkName == 'zkSyncLocal') { + const signers = await ethers.getSigners(); + const allSigners = signers.map(signer => signer.address); + + for (let i = 10; i < 20; i++) { + await signers[0].sendTransaction({ + to: allSigners[i], + value: ethers.utils.parseEther('0.5'), + }); + } + + // @ts-ignore TODO:GIORGI + return new Wrapper(new ZkSync(provider)); + } + + return new Wrapper(new HardhatClass(provider)); + } + + async deploy(artifactName: string, options?: DeployOptions) { + const constructorArgs = options?.args ?? []; + const isProxy = options?.withProxy ?? false; + const initializer = options?.proxySettings?.initializer ?? undefined; + + let {artifact, contract} = await this.network.deploy( + artifactName, + constructorArgs + ); + if (isProxy) { + const {contract: proxyFactoryContract} = await this.network.deploy( + 'ProxyFactory', + [contract.address] + ); + + // Currently, always deploys with UUPS + let data = '0x'; + if (initializer) { + data = await this.network.encodeFunctionData( + artifactName, + initializer, + options?.initArgs ?? [] + ); + } + + const tx = await proxyFactoryContract.deployUUPSProxy(data); + + const event = await findEvent(tx, 'ProxyCreated'); + + contract = new hre.ethers.Contract( + event.args.proxy, + artifact.abi, + (await hre.ethers.getSigners())[0] + ); + } + + return contract; + } + + getCreateAddress(sender: string, nonce: BigNumberish): string { + return this.network.getCreateAddress(sender, nonce); + } + + async getNonce( + sender: string, + type?: 'Deployment' | 'Transaction' + ): Promise { + return this.network.getNonce(sender, type ?? 'Deployment'); + } + + async deployProxy( + deployer: number, + artifactName: string, + options?: DeployOptions + ) { + const _options: DeployOptions = { + args: options?.args ?? [], + initArgs: options?.initArgs ?? [], + proxySettings: { + type: options?.proxySettings?.type ?? 'uups', + initializer: options?.proxySettings?.initializer ?? undefined, + }, + }; + + return this.network.deployProxy(deployer, artifactName, _options); + } + + async upgradeProxy( + upgrader: number, + proxyAddress: string, + newArtifactName: string, + options?: DeployOptions + ) { + const _options: DeployOptions = { + args: options?.args ?? [], + initArgs: options?.initArgs ?? [], + proxySettings: { + initializer: options?.proxySettings?.initializer ?? undefined, + }, + }; + return this.network.upgradeProxy( + upgrader, + proxyAddress, + newArtifactName, + _options + ); + } +} diff --git a/packages/contracts/test/test-utils/wrapper/zksync.ts b/packages/contracts/test/test-utils/wrapper/zksync.ts new file mode 100644 index 000000000..d90767f95 --- /dev/null +++ b/packages/contracts/test/test-utils/wrapper/zksync.ts @@ -0,0 +1,106 @@ +import hre from 'hardhat'; +import {BigNumber, BigNumberish, Contract} from 'ethers'; +import {DeployOptions, NetworkDeployment} from '.'; +import {Provider} from 'zksync-ethers'; +import {utils, ContractFactory} from 'zksync-ethers'; +import {getTime} from '../voting'; + +export class ZkSync implements NetworkDeployment { + provider: Provider; + constructor(_provider: Provider) { + this.provider = _provider; + } + + async deploy(artifactName: string, args: any[] = []) { + const {deployer} = hre; + const artifact = await deployer.loadArtifact(artifactName); + const contract = await deployer.deploy(artifact, args); + + return {artifact, contract}; + } + + async encodeFunctionData( + artifactName: string, + functionName: string, + args: any[] + ): Promise { + const {deployer} = hre; + const artifact = await deployer.loadArtifact(artifactName); + const contract = new ContractFactory( + artifact.abi, + artifact.bytecode, + await deployer.getWallet() + ); + + const fragment = contract.interface.getFunction(functionName); + return contract.interface.encodeFunctionData(fragment, args); + } + + getCreateAddress(sender: string, nonce: BigNumberish): string { + return utils.createAddress(sender, nonce); + } + + async getNonce( + sender: string, + type: 'Deployment' | 'Transaction' = 'Deployment' + ): Promise { + if (type == 'Deployment') { + const {ethers} = hre; + const NONCE_HOLDER_ADDRESS = '0x0000000000000000000000000000000000008003'; + const abi = [ + 'function getDeploymentNonce(address) public view returns(uint256)', + ]; + let signers = await ethers.getSigners(); + let contract = new ethers.Contract(NONCE_HOLDER_ADDRESS, abi, signers[0]); + const nonce = await contract.getDeploymentNonce(sender); + return BigNumber.from(nonce).toNumber(); + } + + return this.provider.getTransactionCount(sender); + } + + // currently, type is not used and always deploys with UUPS + async deployProxy( + deployer: number, + artifactName: string, + options: DeployOptions + ): Promise { + const wallets = await hre.zksyncEthers.getWallets(); + const artifact = await hre.deployer.loadArtifact(artifactName); + + return hre.zkUpgrades.deployProxy( + wallets[deployer], + artifact, + options.initArgs, + { + kind: options.proxySettings?.type, + unsafeAllow: ['constructor'], + constructorArgs: options.args, + initializer: options.proxySettings?.initializer, + }, + true + ); + } + + async upgradeProxy( + upgrader: number, + proxyAddress: string, + newArtifactName: string, + options: DeployOptions + ): Promise { + const wallets = await hre.zksyncEthers.getWallets(); + const newArtifact = await hre.deployer.loadArtifact(newArtifactName); + + return hre.zkUpgrades.upgradeProxy( + wallets[upgrader], + proxyAddress, + newArtifact, + { + unsafeAllow: ['constructor'], + constructorArgs: options.args, + // TODO: pass initiailizer and initArgs + }, + true + ); + } +} diff --git a/packages/contracts/test/token/erc20/governance-erc20.ts b/packages/contracts/test/token/erc20/governance-erc20.ts index 7b6de4406..18e32ef9c 100644 --- a/packages/contracts/test/token/erc20/governance-erc20.ts +++ b/packages/contracts/test/token/erc20/governance-erc20.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { @@ -62,7 +62,9 @@ describe('GovernanceERC20', function () { mintSettings, ]; - token = await GovernanceERC20.deploy(...defaultInitData); + token = await hre.wrapper.deploy('GovernanceERC20', { + args: defaultInitData, + }); }); describe('initialize:', async () => { @@ -78,7 +80,10 @@ describe('GovernanceERC20', function () { }); it('sets the managing DAO ', async () => { - token = await GovernanceERC20.deploy(...defaultInitData); + token = await hre.wrapper.deploy('GovernanceERC20', { + args: defaultInitData, + }); + expect(await token.dao()).to.eq(dao.address); }); @@ -86,12 +91,14 @@ describe('GovernanceERC20', function () { const receivers = [signers[0].address]; const amounts = [123, 456]; await expect( - GovernanceERC20.deploy( - dao.address, - governanceERC20Name, - governanceERC20Symbol, - {receivers: receivers, amounts: amounts} - ) + hre.wrapper.deploy('GovernanceERC20', { + args: [ + dao.address, + governanceERC20Name, + governanceERC20Symbol, + {receivers: receivers, amounts: amounts}, + ], + }) ) .to.be.revertedWithCustomError(token, 'MintSettingsArrayLengthMismatch') .withArgs(receivers.length, amounts.length); @@ -228,9 +235,8 @@ describe('GovernanceERC20', function () { describe('afterTokenTransfer', async () => { beforeEach(async () => { - token = await GovernanceERC20.deploy(dao.address, 'name', 'symbol', { - receivers: [], - amounts: [], + token = await hre.wrapper.deploy('GovernanceERC20', { + args: [dao.address, 'name', 'symbol', {receivers: [], amounts: []}], }); await dao.grant(token.address, signers[0].address, MINT_PERMISSION_ID); diff --git a/packages/contracts/test/token/erc20/governance-wrapped-erc20.ts b/packages/contracts/test/token/erc20/governance-wrapped-erc20.ts index 7bf3691a0..7aa4da9ef 100644 --- a/packages/contracts/test/token/erc20/governance-wrapped-erc20.ts +++ b/packages/contracts/test/token/erc20/governance-wrapped-erc20.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { @@ -61,12 +61,16 @@ describe('GovernanceWrappedERC20', function () { beforeEach(async function () { defaultExistingERC20InitData = [existingErc20Name, existingErc20Symbol, 0]; - erc20 = await TestERC20.deploy(...defaultExistingERC20InitData); + erc20 = await hre.wrapper.deploy('TestERC20', { + args: defaultExistingERC20InitData, + }); - const promises = defaultBalances.map(balance => - erc20.setBalance(balance.account, balance.amount) - ); - await Promise.all(promises); + for (let i = 0; i < defaultBalances.length; i++) { + await erc20.setBalance( + defaultBalances[i].account, + defaultBalances[i].amount + ); + } defaultGovernanceWrappedERC20InitData = [ erc20.address, @@ -74,22 +78,23 @@ describe('GovernanceWrappedERC20', function () { governanceWrappedERC20Symbol, ]; - governanceToken = await GovernanceWrappedERC20.deploy( - ...defaultGovernanceWrappedERC20InitData - ); + governanceToken = await hre.wrapper.deploy('GovernanceWrappedERC20', { + args: defaultGovernanceWrappedERC20InitData, + }); }); describe('initialize:', async () => { it('reverts if trying to re-initialize', async () => { await expect( + // @ts-ignore governanceToken.initialize(...defaultGovernanceWrappedERC20InitData) ).to.be.revertedWith(OZ_ERRORS.ALREADY_INITIALIZED); }); it('sets the wrapped token name and symbol', async () => { - governanceToken = await GovernanceWrappedERC20.deploy( - ...defaultGovernanceWrappedERC20InitData - ); + governanceToken = await hre.wrapper.deploy('GovernanceWrappedERC20', { + args: defaultGovernanceWrappedERC20InitData, + }); expect(await governanceToken.name()).to.eq(governanceWrappedERC20Name); expect(await governanceToken.symbol()).to.eq( @@ -248,11 +253,12 @@ describe('GovernanceWrappedERC20', function () { describe('delegate', async () => { beforeEach(async function () { // approve and deposit for all token holders - let promises = defaultBalances.map(balance => - erc20.approve(balance.account, balance.amount) - ); - - await Promise.all(promises); + for (let i = 0; i < defaultBalances.length; i++) { + await erc20.approve( + defaultBalances[i].account, + defaultBalances[i].amount + ); + } }); it('delegates voting power to another account', async () => { @@ -347,11 +353,9 @@ describe('GovernanceWrappedERC20', function () { let token: GovernanceWrappedERC20; beforeEach(async () => { - token = await GovernanceWrappedERC20.deploy( - erc20.address, - 'name', - 'symbol' - ); + token = await hre.wrapper.deploy('GovernanceWrappedERC20', { + args: [erc20.address, 'name', 'symbol'], + }); await erc20.setBalance(signers[0].address, 200); await erc20.setBalance(signers[1].address, 200); diff --git a/packages/contracts/test/upgrade/dao.ts b/packages/contracts/test/upgrade/dao.ts index 5a8ef942d..724b740c9 100644 --- a/packages/contracts/test/upgrade/dao.ts +++ b/packages/contracts/test/upgrade/dao.ts @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {ethers} from 'hardhat'; +import hre, {ethers} from 'hardhat'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import { @@ -10,12 +10,12 @@ import { import {DAO, DAO__factory, ProtocolVersion__factory} from '../../typechain'; import {daoExampleURI, ZERO_BYTES32} from '../test-utils/dao'; -import {deployWithProxy} from '../test-utils/proxy'; import {UPGRADE_PERMISSIONS} from '../test-utils/permissions'; import {findEventTopicLog} from '../../utils/event'; import {readImplementationValueFromSlot} from '../../utils/storage'; import {getInterfaceID} from '../test-utils/interfaces'; import {UpgradedEvent} from '../../typechain/DAO'; +import {ARTIFACT_SOURCES} from '../test-utils/wrapper'; let signers: SignerWithAddress[]; let DAO_old: DAO_V1_0_0__factory; @@ -43,13 +43,15 @@ describe('DAO Upgrade', function () { DAO_Current = new DAO__factory(signers[0]); // Deploy the v1.3.0 implementation - daoCurrentImplementaion = await DAO_Current.deploy(); + daoCurrentImplementaion = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO); }); context(`Re-entrancy`, function () { context(`v1.0.0 to v1.3.0`, function () { beforeEach(async function () { - daoV100Proxy = await deployWithProxy(DAO_old); + daoV100Proxy = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO_V1_0_0, { + withProxy: true, + }); await daoV100Proxy.initialize( DUMMY_METADATA, signers[0].address, @@ -261,7 +263,9 @@ describe('DAO Upgrade', function () { context(`Protocol Version`, function () { beforeEach(async function () { // prepare v1.0.0 - daoV100Proxy = await deployWithProxy(DAO_old); + daoV100Proxy = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO_V1_0_0, { + withProxy: true, + }); await daoV100Proxy.initialize( DUMMY_METADATA, signers[0].address, @@ -279,7 +283,9 @@ describe('DAO Upgrade', function () { it('fails to call protocolVersion on versions prior to v1.3.0 and succeeds from v1.3.0 onwards', async () => { // deploy the different versions - const daoCurrentProxy = await deployWithProxy(DAO_Current); + const daoCurrentProxy = await hre.wrapper.deploy(ARTIFACT_SOURCES.DAO, { + withProxy: true, + }); await daoCurrentProxy.initialize( DUMMY_METADATA, signers[0].address, diff --git a/packages/contracts/types/hardhat.d.ts b/packages/contracts/types/hardhat.d.ts index c2d36e069..c08ff104e 100644 --- a/packages/contracts/types/hardhat.d.ts +++ b/packages/contracts/types/hardhat.d.ts @@ -4,6 +4,7 @@ import { AragonVerifyEntry, TestingFork, } from '../utils/types'; +import {Wrapper} from '../test/test-utils/wrapper'; declare module 'hardhat/types' { interface HardhatRuntimeEnvironment { @@ -18,5 +19,6 @@ declare module 'hardhat/types' { description: string; // Description to be included in proposal metadata }[]; testingFork: TestingFork; + wrapper: Wrapper; } } diff --git a/packages/contracts/utils/etherscan.ts b/packages/contracts/utils/etherscan.ts index d146bc4d2..62c04abf8 100644 --- a/packages/contracts/utils/etherscan.ts +++ b/packages/contracts/utils/etherscan.ts @@ -19,7 +19,11 @@ export const verifyContract = async ( 'utf8' ); const networksJSON = JSON.parse(networks.toString()); - if (!Object.keys(networksJSON).includes(currentNetwork)) { + + if ( + !Object.keys(networksJSON).includes(currentNetwork) && + !HRE.network.config.zksync + ) { throw Error( `Current network ${currentNetwork} not supported. Please change to one of the next networks: ${Object.keys( networksJSON @@ -69,7 +73,7 @@ export const runTaskWithRetry = async ( } else { cleanup(); console.error( - 'Errors after all the retries, check the logs for more information.' + `Errors after all the retries for contract ${params.contract} at ${params.address}. Please verify manually.` ); } } catch (error: any) { diff --git a/packages/contracts/utils/zkSync.ts b/packages/contracts/utils/zkSync.ts new file mode 100644 index 000000000..c5c66ee54 --- /dev/null +++ b/packages/contracts/utils/zkSync.ts @@ -0,0 +1 @@ +export const ZK_SYNC_NETWORKS = ['zkMainnet', 'zkLocalTestnet', 'zkTestnet']; diff --git a/packages/subgraph/manifest/data/zksync-era-sepolia.json b/packages/subgraph/manifest/data/zksync-era-sepolia.json new file mode 100644 index 000000000..e5ea91e08 --- /dev/null +++ b/packages/subgraph/manifest/data/zksync-era-sepolia.json @@ -0,0 +1,23 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "zksync-era-sepolia", + "dataSources": { + "DAORegistry": { + "name": "DAORegistry", + "address": "0x9305Ecd8D3837F7AC4f425dA578a0D11352D317A", + "startBlock": 2441204 + }, + "PluginRepoRegistry": { + "name": "PluginRepoRegistry", + "address": "0x22eE909B94c5FAeee67B7e187ca81E5A291A9EC0", + "startBlock": 2441207 + }, + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0xe2Ef39f1be2269644cBfa9b70003A143bF1fdf4d", + "startBlock": 2441214 + } + ] + } +} \ No newline at end of file diff --git a/packages/subgraph/manifest/data/zksync-era.json b/packages/subgraph/manifest/data/zksync-era.json new file mode 100644 index 000000000..aedd784e8 --- /dev/null +++ b/packages/subgraph/manifest/data/zksync-era.json @@ -0,0 +1,23 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "zksync-era", + "dataSources": { + "DAORegistry": { + "name": "DAORegistry", + "address": "0xE7351bA0DDCc52249F27353893BcDBC74229e99d", + "startBlock": 37460768 + }, + "PluginRepoRegistry": { + "name": "PluginRepoRegistry", + "address": "0xEa26fC4028D9293f453804b40F097F11974FdB79", + "startBlock": 37460776 + }, + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0x8E3e98ECF5CdBF2bEcCD91d3BA580D472df5A0cB", + "startBlock": 37460792 + } + ] + } +} diff --git a/packages/subgraph/manifest/subgraph.placeholder.yaml b/packages/subgraph/manifest/subgraph.placeholder.yaml index 6268cd121..5cc839584 100644 --- a/packages/subgraph/manifest/subgraph.placeholder.yaml +++ b/packages/subgraph/manifest/subgraph.placeholder.yaml @@ -196,6 +196,8 @@ templates: abis: - name: ERC20 file: $ARAGON_OSX_MODULE/artifacts/@openzeppelin/contracts/token/ERC20/ERC20.sol/ERC20.json + - name: GovernanceERC20 + file: $ARAGON_OSX_MODULE/artifacts/src/token/ERC20/governance/GovernanceERC20.sol/GovernanceERC20.json - name: GovernanceWrappedERC20 file: $ARAGON_OSX_MODULE/artifacts/src/token/ERC20/governance/GovernanceWrappedERC20.sol/GovernanceWrappedERC20.json - name: TokenVoting diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 5594dc5c9..cd0840851 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -888,14 +888,23 @@ type MultisigPlugin implements IPlugin @entity { } type MultisigApprover @entity { + """ + Current Approver of the multisig. If the approver is removed from the multisig, + the entity is not deleted and instead the isActive flag is set to false. + """ id: ID! # plugin_address + member_address - address: String # address as string to facilitate filtering by address on the UI - proposals: [MultisigProposalApprover!]! @derivedFrom(field: "approver") + address: Bytes! + approvals: [MultisigProposalApproval!]! @derivedFrom(field: "approver") plugin: MultisigPlugin! + isActive: Boolean! } -type MultisigProposalApprover @entity(immutable: true) { - "ApproverProposal for Many-to-Many" +type MultisigProposalApproval @entity(immutable: true) { + """ + An approval of an specific multisig proposal + This entity works as a join table between MultisigProposal and MultisigApprover + and also stores the timestamp of the approval + """ id: ID! # approver + proposal approver: MultisigApprover! proposal: MultisigProposal! @@ -918,12 +927,12 @@ type MultisigProposal implements IProposal @entity { creationBlockNumber: BigInt! snapshotBlock: BigInt! minApprovals: Int! - approvals: Int + approvalCount: Int approvalReached: Boolean! isSignaling: Boolean! executed: Boolean! executionDate: BigInt executionBlockNumber: BigInt executionTxHash: Bytes - approvers: [MultisigProposalApprover!]! @derivedFrom(field: "proposal") + approvals: [MultisigProposalApproval!]! @derivedFrom(field: "proposal") } diff --git a/packages/subgraph/src/packages/multisig/multisig.ts b/packages/subgraph/src/packages/multisig/multisig.ts index 04ca24484..877e5375d 100644 --- a/packages/subgraph/src/packages/multisig/multisig.ts +++ b/packages/subgraph/src/packages/multisig/multisig.ts @@ -3,7 +3,7 @@ import { MultisigPlugin, MultisigProposal, MultisigApprover, - MultisigProposalApprover, + MultisigProposalApproval, } from '../../../generated/schema'; import { ProposalCreated, @@ -20,7 +20,7 @@ import { generatePluginEntityId, generateProposalEntityId, } from '@aragon/osx-commons-subgraph'; -import {dataSource, store} from '@graphprotocol/graph-ts'; +import {dataSource} from '@graphprotocol/graph-ts'; export function handleProposalCreated(event: ProposalCreated): void { let context = dataSource.context(); @@ -60,7 +60,7 @@ export function _handleProposalCreated( if (!proposal.reverted) { proposalEntity.executed = proposal.value.value0; - proposalEntity.approvals = proposal.value.value1; + proposalEntity.approvalCount = proposal.value.value1; // ProposalParameters let parameters = proposal.value.value2; @@ -102,24 +102,22 @@ export function _handleProposalCreated( export function handleApproved(event: Approved): void { let memberAddress = event.params.approver; let pluginAddress = event.address; - let memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); + let approverEntityId = generateMemberEntityId(pluginAddress, memberAddress); let pluginProposalId = event.params.proposalId; let proposalEntityId = generateProposalEntityId( event.address, pluginProposalId ); let approverProposalId = generateVoterEntityId( - memberEntityId, + approverEntityId, proposalEntityId ); - let approverProposalEntity = - MultisigProposalApprover.load(approverProposalId); - if (!approverProposalEntity) { - approverProposalEntity = new MultisigProposalApprover(approverProposalId); - approverProposalEntity.approver = memberEntityId; - approverProposalEntity.proposal = proposalEntityId; - } + // No need for checking if approval exists + // a proposal cannot be approved twice by the same approver + let approverProposalEntity = new MultisigProposalApproval(approverProposalId); + approverProposalEntity.approver = approverEntityId; + approverProposalEntity.proposal = proposalEntityId; approverProposalEntity.createdAt = event.block.timestamp; approverProposalEntity.save(); @@ -131,7 +129,7 @@ export function handleApproved(event: Approved): void { if (!proposal.reverted) { let approvals = proposal.value.value1; - proposalEntity.approvals = approvals; + proposalEntity.approvalCount = approvals; // calculate if proposal is executable let minApprovalsStruct = proposal.value.value2; @@ -171,15 +169,17 @@ export function handleMembersAdded(event: MembersAdded): void { for (let index = 0; index < members.length; index++) { const memberAddress = members[index]; const pluginEntityId = generatePluginEntityId(event.address); - const memberEntityId = [pluginEntityId, memberAddress.toHexString()].join( - '_' + const approverEntityId = generateMemberEntityId( + event.address, + memberAddress ); - let approverEntity = MultisigApprover.load(memberEntityId); + let approverEntity = MultisigApprover.load(approverEntityId); if (!approverEntity) { - approverEntity = new MultisigApprover(memberEntityId); - approverEntity.address = memberAddress.toHexString(); + approverEntity = new MultisigApprover(approverEntityId); + approverEntity.address = memberAddress; approverEntity.plugin = pluginEntityId; + approverEntity.isActive = true; approverEntity.save(); } } @@ -189,14 +189,15 @@ export function handleMembersRemoved(event: MembersRemoved): void { const members = event.params.members; for (let index = 0; index < members.length; index++) { const memberAddress = members[index]; - const pluginEntityId = generatePluginEntityId(event.address); - const memberEntityId = [pluginEntityId, memberAddress.toHexString()].join( - '_' + const approverEntityId = generateMemberEntityId( + event.address, + memberAddress ); - const approverEntity = MultisigApprover.load(memberEntityId); + const approverEntity = MultisigApprover.load(approverEntityId); if (approverEntity) { - store.remove('MultisigApprover', memberEntityId); + approverEntity.isActive = false; + approverEntity.save(); } } } diff --git a/packages/subgraph/src/packages/token/governance-erc20.ts b/packages/subgraph/src/packages/token/governance-erc20.ts index 575c07c4e..64f9757cc 100644 --- a/packages/subgraph/src/packages/token/governance-erc20.ts +++ b/packages/subgraph/src/packages/token/governance-erc20.ts @@ -2,7 +2,7 @@ import {TokenVotingMember} from '../../../generated/schema'; import {GovernanceERC20 as GovernanceERC20Contract} from '../../../generated/templates/GovernanceERC20/GovernanceERC20'; import { DelegateChanged, - DelegateVotesChanged, + DelegateVotesChanged } from '../../../generated/templates/GovernanceERC20/GovernanceERC20'; import {Transfer} from '../../../generated/templates/TokenVoting/ERC20'; import {generateMemberEntityId} from '../../utils/ids'; @@ -10,11 +10,11 @@ import { TokenVotingMemberResult, getDelegateeId, getERC20Balance, - getVotingPower, + getVotingPower } from './utils'; import {Address, BigInt, dataSource, store} from '@graphprotocol/graph-ts'; -function getOrCreateMember( +export function getOrCreateMember( user: Address, pluginId: string, tokenAddress: Address diff --git a/packages/subgraph/src/packages/token/token-voting.ts b/packages/subgraph/src/packages/token/token-voting.ts index 57c9189cc..93d9e826a 100644 --- a/packages/subgraph/src/packages/token/token-voting.ts +++ b/packages/subgraph/src/packages/token/token-voting.ts @@ -3,7 +3,7 @@ import { TokenVotingPlugin, TokenVotingProposal, TokenVotingVoter, - TokenVotingVote, + TokenVotingVote } from '../../../generated/schema'; import {GovernanceERC20} from '../../../generated/templates'; import { @@ -12,7 +12,7 @@ import { ProposalExecuted, VotingSettingsUpdated, MembershipContractAnnounced, - TokenVoting, + TokenVoting } from '../../../generated/templates/TokenVoting/TokenVoting'; import {RATIO_BASE, VOTER_OPTIONS, VOTING_MODES} from '../../utils/constants'; import {generateMemberEntityId, generateVoteEntityId} from '../../utils/ids'; @@ -20,9 +20,17 @@ import {identifyAndFetchOrCreateERC20TokenEntity} from '../../utils/tokens/erc20 import { generateActionEntityId, generatePluginEntityId, - generateProposalEntityId, + generateProposalEntityId } from '@aragon/osx-commons-subgraph'; -import {BigInt, dataSource, DataSourceContext} from '@graphprotocol/graph-ts'; +import { + Address, + BigInt, + ByteArray, + dataSource, + DataSourceContext, + log +} from '@graphprotocol/graph-ts'; +import {getOrCreateMember} from './governance-erc20'; export function handleProposalCreated(event: ProposalCreated): void { let context = dataSource.context(); @@ -278,5 +286,31 @@ export function handleMembershipContractAnnounced( let context = new DataSourceContext(); context.setString('pluginId', pluginEntityId); GovernanceERC20.createWithContext(event.params.definingContract, context); + + //////////////// Grant DAO /////////////// + // Special treatment for the passed governanceErc20 token (0x7702e57ef6887f47343c045c0f7c7ad7b709d358), + // Which has been deployed prioer to its DAO & Plugin creation. + const isTargetToken = + token == Address.fromString('0x7702e57ef6887f47343c045c0f7c7ad7b709d358'); + if (isTargetToken) { + log.warning('grant token found {}', [ + '0x7702e57ef6887f47343c045c0f7c7ad7b709d358' + ]); + + const userOne = getOrCreateMember( + Address.fromString('0x09178794c8a3ae720712cc9640474ca33891d6e6'), + pluginEntityId, + token + ); + userOne.entity.save(); + + const userTwo = getOrCreateMember( + Address.fromString('0x0724d72eb61e508d81ca701881f2248f092953bf'), + pluginEntityId, + token + ); + userTwo.entity.save(); + } + //////////////// Grant DAO /////////////// } } diff --git a/packages/subgraph/src/packages/token/utils.ts b/packages/subgraph/src/packages/token/utils.ts index d20033fe2..cf5cb550a 100644 --- a/packages/subgraph/src/packages/token/utils.ts +++ b/packages/subgraph/src/packages/token/utils.ts @@ -15,11 +15,14 @@ export function getDelegation( tokenAddress: Address ): string | null { let contract = GovernanceERC20Contract.bind(tokenAddress); - let delegate = contract.delegates(user); + let delegate = contract.try_delegates(user); - return delegate === Address.fromString(ADDRESS_ZERO) - ? null - : delegate.toHexString(); + if (!delegate.reverted) { + return delegate.value === Address.fromString(ADDRESS_ZERO) + ? null + : delegate.value.toHexString(); + } + return null; } export function getDelegateeId( @@ -36,10 +39,16 @@ export function getDelegateeId( : null; } -export function getVotingPower(user: Address, tokenAddress: Address): BigInt { +export function getVotingPower( + user: Address, + tokenAddress: Address +): BigInt | null { let contract = GovernanceERC20Contract.bind(tokenAddress); - let votingPower = contract.getVotes(user); - return votingPower; + let votingPower = contract.try_getVotes(user); + if (!votingPower.reverted) { + return votingPower.value; + } + return null; } /** diff --git a/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts b/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts index 3f54a88c2..318440c30 100644 --- a/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts +++ b/packages/subgraph/tests/addresslist-voting/addresslist-voting.test.ts @@ -595,7 +595,6 @@ test('Run AddresslistVoting (handleMembersAdded) mappings with mock event', () = 'id', memberEntityId ); - const voter = AddresslistVotingVoter.load(memberEntityId); assert.fieldEquals( 'AddresslistVotingVoter', memberEntityId, diff --git a/packages/subgraph/tests/multisig/multisig.test.ts b/packages/subgraph/tests/multisig/multisig.test.ts index f60ff4bb7..33346b924 100644 --- a/packages/subgraph/tests/multisig/multisig.test.ts +++ b/packages/subgraph/tests/multisig/multisig.test.ts @@ -156,7 +156,12 @@ test('Run Multisig (handleProposalCreated) mappings with mock event', () => { SNAPSHOT_BLOCK ); assert.fieldEquals('MultisigProposal', proposalEntityId, 'minApprovals', ONE); - assert.fieldEquals('MultisigProposal', proposalEntityId, 'approvals', ONE); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'approvalCount', + ONE + ); assert.fieldEquals('MultisigProposal', proposalEntityId, 'executed', 'false'); assert.fieldEquals( 'MultisigProposal', @@ -221,32 +226,32 @@ test('Run Multisig (handleApproved) mappings with mock event', () => { const voterEntityId = generateVoterEntityId(memberEntityId, proposal.id); // check proposalVoter assert.fieldEquals( - 'MultisigProposalApprover', + 'MultisigProposalApproval', voterEntityId, 'id', voterEntityId ); assert.fieldEquals( - 'MultisigProposalApprover', + 'MultisigProposalApproval', + voterEntityId, + 'createdAt', + event.block.timestamp.toString() + ); + assert.fieldEquals( + 'MultisigProposalApproval', voterEntityId, 'approver', memberEntityId ); assert.fieldEquals( - 'MultisigProposalApprover', + 'MultisigProposalApproval', voterEntityId, 'proposal', proposal.id ); - assert.fieldEquals( - 'MultisigProposalApprover', - voterEntityId, - 'createdAt', - event.block.timestamp.toString() - ); // check proposal - assert.fieldEquals('MultisigProposal', proposal.id, 'approvals', ONE); + assert.fieldEquals('MultisigProposal', proposal.id, 'approvalCount', ONE); assert.fieldEquals( 'MultisigProposal', proposal.id, @@ -284,7 +289,7 @@ test('Run Multisig (handleApproved) mappings with mock event', () => { handleApproved(event2); // Check - assert.fieldEquals('MultisigProposal', proposal.id, 'approvals', TWO); + assert.fieldEquals('MultisigProposal', proposal.id, 'approvalCount', TWO); assert.fieldEquals( 'MultisigProposal', proposal.id, @@ -358,10 +363,10 @@ test('Run Multisig (handleMembersAdded) mappings with mock event', () => { handleMembersAdded(event); // checks - let memberId = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - userArray[0].toHexString(); + let memberId = generateMemberEntityId( + Address.fromString(CONTRACT_ADDRESS), + userArray[0] + ); assert.fieldEquals('MultisigApprover', memberId, 'id', memberId); assert.fieldEquals( @@ -376,6 +381,7 @@ test('Run Multisig (handleMembersAdded) mappings with mock event', () => { 'plugin', Address.fromString(CONTRACT_ADDRESS).toHexString() ); + assert.fieldEquals('MultisigApprover', memberId, 'isActive', 'true'); clearStore(); }); @@ -386,25 +392,23 @@ test('Run Multisig (handleMembersRemoved) mappings with mock event', () => { Address.fromString(ADDRESS_ONE), Address.fromString(ADDRESS_TWO), ]; - + let pluginAddress = Address.fromString(CONTRACT_ADDRESS); + // create approvers for (let index = 0; index < memberAddresses.length; index++) { - const user = memberAddresses[index].toHexString(); - const pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); - let memberId = pluginId + '_' + user; - let userEntity = new MultisigApprover(memberId); - userEntity.plugin = Address.fromString(CONTRACT_ADDRESS).toHexString(); - userEntity.save(); + let memberId = generateMemberEntityId( + pluginAddress, + memberAddresses[index] + ); + let approverEntity = new MultisigApprover(memberId); + approverEntity.plugin = pluginAddress.toHexString(); + approverEntity.address = memberAddresses[index]; + approverEntity.isActive = true; + approverEntity.save(); } // checks - let memberId1 = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - memberAddresses[0].toHexString(); - let memberId2 = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - memberAddresses[1].toHexString(); + let memberId1 = generateMemberEntityId(pluginAddress, memberAddresses[0]); + let memberId2 = generateMemberEntityId(pluginAddress, memberAddresses[1]); assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); assert.fieldEquals('MultisigApprover', memberId2, 'id', memberId2); @@ -420,8 +424,9 @@ test('Run Multisig (handleMembersRemoved) mappings with mock event', () => { // checks assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); - assert.notInStore('MultisigApprover', memberId2); - + assert.fieldEquals('MultisigApprover', memberId1, 'isActive', 'true'); + assert.fieldEquals('MultisigApprover', memberId2, 'id', memberId2); + assert.fieldEquals('MultisigApprover', memberId2, 'isActive', 'false'); clearStore(); }); diff --git a/packages/subgraph/tests/token/governance-erc20.test.ts b/packages/subgraph/tests/token/governance-erc20.test.ts index 750273221..c76634fdb 100644 --- a/packages/subgraph/tests/token/governance-erc20.test.ts +++ b/packages/subgraph/tests/token/governance-erc20.test.ts @@ -17,7 +17,6 @@ import {getBalanceOf} from '../dao/utils'; import {ExtendedTokenVotingMember} from '../helpers/extended-schema'; import { createNewDelegateChangedEvent, - createNewERC20TransferEvent, createNewERC20TransferEventWithAddress, createTokenVotingMember, getDelegatee, @@ -27,7 +26,7 @@ import { generateEntityIdFromAddress, generatePluginEntityId, } from '@aragon/osx-commons-subgraph'; -import {Address, BigInt, DataSourceContext, log} from '@graphprotocol/graph-ts'; +import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; import { assert, afterEach, @@ -610,8 +609,6 @@ describe('Governance ERC20', () => { test("It should initialize with the user's existing voting power and delegation, if she has any", () => { // constants const STARTING_BALANCE = '10'; - const TRANSFER = '3'; - const REMAINING = '7'; // mocked calls getBalanceOf( @@ -639,16 +636,6 @@ describe('Governance ERC20', () => { Address.fromString(fromAddressHexString) ); - const memberEntityIdTo = generateMemberEntityId( - pluginAddressSecond, - Address.fromString(toAddressHexString) - ); - - const memberEntityIdToSecondPlugin = generateMemberEntityId( - pluginAddressSecond, - Address.fromString(toAddressHexString) - ); - // delegate to self const delegateChangedEvent = createNewDelegateChangedEvent( fromAddressHexString, diff --git a/yarn.lock b/yarn.lock index 00fda09d7..5269b161d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,65 +42,6 @@ dependencies: "@babel/highlight" "^7.16.7" -"@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== - dependencies: - "@babel/highlight" "^7.24.2" - picocolors "^1.0.0" - -"@babel/generator@7.17.7": - version "7.17.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" - integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== - dependencies: - "@babel/types" "^7.17.0" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.23.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" - integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== - dependencies: - "@babel/types" "^7.24.0" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.23.4": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== - "@babel/helper-validator-identifier@^7.15.7": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" @@ -111,11 +52,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" @@ -134,21 +70,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/highlight@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" - integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/parser@^7.20.5", "@babel/parser@^7.23.0", "@babel/parser@^7.24.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" - integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== - "@babel/runtime@^7.4.4": version "7.18.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.0.tgz#6d77142a19cb6088f0af662af1ada37a604d34ae" @@ -156,47 +77,10 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.22.15": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - -"@babel/traverse@7.23.2": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" - integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - to-fast-properties "^2.0.0" - -"@babel/types@^7.17.0", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" - integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== - dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" +"@balena/dockerignore@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" + integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" @@ -363,21 +247,6 @@ "@ethersproject/properties" "^5.0.3" "@ethersproject/strings" "^5.0.4" -"@ethersproject/abi@5.5.0", "@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.1.2", "@ethersproject/abi@^5.4.0", "@ethersproject/abi@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613" - integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w== - dependencies: - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/hash" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/abi@5.6.0", "@ethersproject/abi@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.0.tgz#ea07cbc1eec2374d32485679c12408005895e9f3" @@ -408,7 +277,7 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.0" -"@ethersproject/abi@^5.0.9": +"@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.0.9", "@ethersproject/abi@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== @@ -423,18 +292,20 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/abstract-provider@5.5.1", "@ethersproject/abstract-provider@^5.5.0": - version "5.5.1" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5" - integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg== +"@ethersproject/abi@^5.0.0-beta.146", "@ethersproject/abi@^5.1.2": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613" + integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w== dependencies: + "@ethersproject/address" "^5.5.0" "@ethersproject/bignumber" "^5.5.0" "@ethersproject/bytes" "^5.5.0" + "@ethersproject/constants" "^5.5.0" + "@ethersproject/hash" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" "@ethersproject/logger" "^5.5.0" - "@ethersproject/networks" "^5.5.0" "@ethersproject/properties" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/web" "^5.5.0" + "@ethersproject/strings" "^5.5.0" "@ethersproject/abstract-provider@5.6.0", "@ethersproject/abstract-provider@^5.6.0": version "5.6.0" @@ -449,7 +320,7 @@ "@ethersproject/transactions" "^5.6.0" "@ethersproject/web" "^5.6.0" -"@ethersproject/abstract-provider@^5.7.0": +"@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== @@ -462,16 +333,18 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" -"@ethersproject/abstract-signer@5.5.0", "@ethersproject/abstract-signer@^5.4.1", "@ethersproject/abstract-signer@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d" - integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA== +"@ethersproject/abstract-provider@^5.5.0": + version "5.5.1" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5" + integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg== dependencies: - "@ethersproject/abstract-provider" "^5.5.0" "@ethersproject/bignumber" "^5.5.0" "@ethersproject/bytes" "^5.5.0" "@ethersproject/logger" "^5.5.0" + "@ethersproject/networks" "^5.5.0" "@ethersproject/properties" "^5.5.0" + "@ethersproject/transactions" "^5.5.0" + "@ethersproject/web" "^5.5.0" "@ethersproject/abstract-signer@5.6.0", "@ethersproject/abstract-signer@^5.6.0": version "5.6.0" @@ -495,7 +368,7 @@ "@ethersproject/logger" "^5.6.0" "@ethersproject/properties" "^5.6.0" -"@ethersproject/abstract-signer@^5.7.0": +"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== @@ -506,16 +379,16 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/address@5.5.0", "@ethersproject/address@^5.0.4", "@ethersproject/address@^5.4.0", "@ethersproject/address@^5.5.0": +"@ethersproject/abstract-signer@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f" - integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw== + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d" + integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA== dependencies: + "@ethersproject/abstract-provider" "^5.5.0" "@ethersproject/bignumber" "^5.5.0" "@ethersproject/bytes" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" "@ethersproject/logger" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" + "@ethersproject/properties" "^5.5.0" "@ethersproject/address@5.6.0", "@ethersproject/address@^5.6.0": version "5.6.0" @@ -528,7 +401,7 @@ "@ethersproject/logger" "^5.6.0" "@ethersproject/rlp" "^5.6.0" -"@ethersproject/address@^5.0.2", "@ethersproject/address@^5.7.0": +"@ethersproject/address@5.7.0", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== @@ -539,12 +412,16 @@ "@ethersproject/logger" "^5.7.0" "@ethersproject/rlp" "^5.7.0" -"@ethersproject/base64@5.5.0", "@ethersproject/base64@^5.5.0": +"@ethersproject/address@^5.0.4", "@ethersproject/address@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090" - integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA== + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f" + integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw== dependencies: + "@ethersproject/bignumber" "^5.5.0" "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" + "@ethersproject/logger" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" "@ethersproject/base64@5.6.0", "@ethersproject/base64@^5.6.0": version "5.6.0" @@ -553,20 +430,19 @@ dependencies: "@ethersproject/bytes" "^5.6.0" -"@ethersproject/base64@^5.7.0": +"@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== dependencies: "@ethersproject/bytes" "^5.7.0" -"@ethersproject/basex@5.5.0", "@ethersproject/basex@^5.5.0": +"@ethersproject/base64@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.5.0.tgz#e40a53ae6d6b09ab4d977bd037010d4bed21b4d3" - integrity sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ== + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090" + integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA== dependencies: "@ethersproject/bytes" "^5.5.0" - "@ethersproject/properties" "^5.5.0" "@ethersproject/basex@5.6.0", "@ethersproject/basex@^5.6.0": version "5.6.0" @@ -576,14 +452,13 @@ "@ethersproject/bytes" "^5.6.0" "@ethersproject/properties" "^5.6.0" -"@ethersproject/bignumber@5.5.0", "@ethersproject/bignumber@^5.0.7", "@ethersproject/bignumber@^5.4.1", "@ethersproject/bignumber@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527" - integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg== +"@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - bn.js "^4.11.9" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" "@ethersproject/bignumber@5.6.0", "@ethersproject/bignumber@^5.6.0": version "5.6.0" @@ -603,7 +478,7 @@ "@ethersproject/logger" "^5.6.0" bn.js "^4.11.9" -"@ethersproject/bignumber@^5.7.0": +"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== @@ -612,12 +487,14 @@ "@ethersproject/logger" "^5.7.0" bn.js "^5.2.1" -"@ethersproject/bytes@5.5.0", "@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.4.0", "@ethersproject/bytes@^5.5.0": +"@ethersproject/bignumber@^5.0.7", "@ethersproject/bignumber@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c" - integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog== + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527" + integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg== dependencies: + "@ethersproject/bytes" "^5.5.0" "@ethersproject/logger" "^5.5.0" + bn.js "^4.11.9" "@ethersproject/bytes@5.6.1", "@ethersproject/bytes@^5.6.0": version "5.6.1" @@ -626,19 +503,19 @@ dependencies: "@ethersproject/logger" "^5.6.0" -"@ethersproject/bytes@^5.7.0": +"@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/constants@5.5.0", "@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.4.0", "@ethersproject/constants@^5.5.0": +"@ethersproject/bytes@^5.0.4", "@ethersproject/bytes@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e" - integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ== + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c" + integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog== dependencies: - "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/logger" "^5.5.0" "@ethersproject/constants@5.6.0", "@ethersproject/constants@^5.6.0": version "5.6.0" @@ -647,28 +524,19 @@ dependencies: "@ethersproject/bignumber" "^5.6.0" -"@ethersproject/constants@^5.7.0": +"@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== dependencies: "@ethersproject/bignumber" "^5.7.0" -"@ethersproject/contracts@5.5.0", "@ethersproject/contracts@^5.4.1": +"@ethersproject/constants@^5.0.4", "@ethersproject/constants@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.5.0.tgz#b735260d4bd61283a670a82d5275e2a38892c197" - integrity sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg== + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e" + integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ== dependencies: - "@ethersproject/abi" "^5.5.0" - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" "@ethersproject/contracts@5.6.0": version "5.6.0" @@ -702,19 +570,21 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/transactions" "^5.6.0" -"@ethersproject/hash@5.5.0", "@ethersproject/hash@^5.0.4", "@ethersproject/hash@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9" - integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg== +"@ethersproject/contracts@5.7.0", "@ethersproject/contracts@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== dependencies: - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/strings" "^5.5.0" + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" "@ethersproject/hash@5.6.0", "@ethersproject/hash@^5.6.0": version "5.6.0" @@ -730,7 +600,7 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.0" -"@ethersproject/hash@^5.7.0": +"@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== @@ -745,23 +615,19 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/hdnode@5.5.0", "@ethersproject/hdnode@^5.5.0": +"@ethersproject/hash@^5.0.4", "@ethersproject/hash@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.5.0.tgz#4a04e28f41c546f7c978528ea1575206a200ddf6" - integrity sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q== + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9" + integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg== dependencies: "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/basex" "^5.5.0" + "@ethersproject/address" "^5.5.0" "@ethersproject/bignumber" "^5.5.0" "@ethersproject/bytes" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" "@ethersproject/logger" "^5.5.0" - "@ethersproject/pbkdf2" "^5.5.0" "@ethersproject/properties" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/signing-key" "^5.5.0" "@ethersproject/strings" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/wordlists" "^5.5.0" "@ethersproject/hdnode@5.6.0", "@ethersproject/hdnode@^5.6.0": version "5.6.0" @@ -799,24 +665,23 @@ "@ethersproject/transactions" "^5.6.0" "@ethersproject/wordlists" "^5.6.0" -"@ethersproject/json-wallets@5.5.0", "@ethersproject/json-wallets@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz#dd522d4297e15bccc8e1427d247ec8376b60e325" - integrity sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ== +"@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== dependencies: - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/hdnode" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/pbkdf2" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/random" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - aes-js "3.0.0" - scrypt-js "3.0.1" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" "@ethersproject/json-wallets@5.6.0", "@ethersproject/json-wallets@^5.6.0": version "5.6.0" @@ -837,13 +702,24 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@5.5.0", "@ethersproject/keccak256@^5.0.3", "@ethersproject/keccak256@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492" - integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg== +"@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== dependencies: - "@ethersproject/bytes" "^5.5.0" - js-sha3 "0.8.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" "@ethersproject/keccak256@5.6.0", "@ethersproject/keccak256@^5.6.0": version "5.6.0" @@ -853,7 +729,7 @@ "@ethersproject/bytes" "^5.6.0" js-sha3 "0.8.0" -"@ethersproject/keccak256@^5.7.0": +"@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== @@ -861,27 +737,28 @@ "@ethersproject/bytes" "^5.7.0" js-sha3 "0.8.0" -"@ethersproject/logger@5.5.0", "@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.5.0": +"@ethersproject/keccak256@^5.0.3", "@ethersproject/keccak256@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d" - integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg== + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492" + integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg== + dependencies: + "@ethersproject/bytes" "^5.5.0" + js-sha3 "0.8.0" "@ethersproject/logger@5.6.0", "@ethersproject/logger@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" integrity sha512-BiBWllUROH9w+P21RzoxJKzqoqpkyM1pRnEKG69bulE9TSQD8SAIvTQqIMZmmCO8pUNkgLP1wndX1gKghSpBmg== -"@ethersproject/logger@^5.7.0": +"@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/networks@5.5.0", "@ethersproject/networks@^5.5.0": +"@ethersproject/logger@^5.0.5", "@ethersproject/logger@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.0.tgz#babec47cab892c51f8dd652ce7f2e3e14283981a" - integrity sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA== - dependencies: - "@ethersproject/logger" "^5.5.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d" + integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg== "@ethersproject/networks@5.6.1", "@ethersproject/networks@^5.6.0": version "5.6.1" @@ -897,20 +774,19 @@ dependencies: "@ethersproject/logger" "^5.6.0" -"@ethersproject/networks@^5.7.0": +"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/pbkdf2@5.5.0", "@ethersproject/pbkdf2@^5.5.0": +"@ethersproject/networks@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz#e25032cdf02f31505d47afbf9c3e000d95c4a050" - integrity sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg== + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.0.tgz#babec47cab892c51f8dd652ce7f2e3e14283981a" + integrity sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA== dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" + "@ethersproject/logger" "^5.5.0" "@ethersproject/pbkdf2@5.6.0", "@ethersproject/pbkdf2@^5.6.0": version "5.6.0" @@ -920,12 +796,13 @@ "@ethersproject/bytes" "^5.6.0" "@ethersproject/sha2" "^5.6.0" -"@ethersproject/properties@5.5.0", "@ethersproject/properties@^5.0.3", "@ethersproject/properties@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995" - integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA== +"@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== dependencies: - "@ethersproject/logger" "^5.5.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" "@ethersproject/properties@5.6.0", "@ethersproject/properties@^5.6.0": version "5.6.0" @@ -934,37 +811,19 @@ dependencies: "@ethersproject/logger" "^5.6.0" -"@ethersproject/properties@^5.7.0": +"@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.5.0": +"@ethersproject/properties@^5.0.3", "@ethersproject/properties@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.0.tgz#bc2876a8fe5e0053ed9828b1f3767ae46e43758b" - integrity sha512-xqMbDnS/FPy+J/9mBLKddzyLLAQFjrVff5g00efqxPzcAwXiR+SiCGVy6eJ5iAIirBOATjx7QLhDNPGV+AEQsw== + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995" + integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA== dependencies: - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/basex" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/hash" "^5.5.0" "@ethersproject/logger" "^5.5.0" - "@ethersproject/networks" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/random" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/web" "^5.5.0" - bech32 "1.1.4" - ws "7.4.6" "@ethersproject/providers@5.6.2": version "5.6.2" @@ -1017,39 +876,32 @@ bech32 "1.1.4" ws "7.4.6" -"@ethersproject/providers@^5.4.4": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.2.tgz#131ccf52dc17afd0ab69ed444b8c0e3a27297d99" - integrity sha512-hkbx7x/MKcRjyrO4StKXCzCpWer6s97xnm34xkfPiarhtEUVAN4TBBpamM+z66WcTt7H5B53YwbRj1n7i8pZoQ== +"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== dependencies: - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/basex" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/hash" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/networks" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/random" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/strings" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/web" "^5.5.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" bech32 "1.1.4" ws "7.4.6" -"@ethersproject/random@5.5.0", "@ethersproject/random@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.0.tgz#305ed9e033ca537735365ac12eed88580b0f81f9" - integrity sha512-egGYZwZ/YIFKMHcoBUo8t3a8Hb/TKYX8BCBoLjudVCZh892welR3jOxgOmb48xznc9bTcMm7Tpwc1gHC1PFNFQ== - dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/random@5.6.0", "@ethersproject/random@^5.6.0": version "5.6.0" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.6.0.tgz#1505d1ab6a250e0ee92f436850fa3314b2cb5ae6" @@ -1058,13 +910,13 @@ "@ethersproject/bytes" "^5.6.0" "@ethersproject/logger" "^5.6.0" -"@ethersproject/rlp@5.5.0", "@ethersproject/rlp@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0" - integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA== +"@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" "@ethersproject/rlp@5.6.0", "@ethersproject/rlp@^5.6.0": version "5.6.0" @@ -1074,7 +926,7 @@ "@ethersproject/bytes" "^5.6.0" "@ethersproject/logger" "^5.6.0" -"@ethersproject/rlp@^5.7.0": +"@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== @@ -1082,14 +934,13 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/sha2@5.5.0", "@ethersproject/sha2@^5.5.0": +"@ethersproject/rlp@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.5.0.tgz#a40a054c61f98fd9eee99af2c3cc6ff57ec24db7" - integrity sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA== + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0" + integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA== dependencies: "@ethersproject/bytes" "^5.5.0" "@ethersproject/logger" "^5.5.0" - hash.js "1.1.7" "@ethersproject/sha2@5.6.0", "@ethersproject/sha2@^5.6.0": version "5.6.0" @@ -1100,16 +951,13 @@ "@ethersproject/logger" "^5.6.0" hash.js "1.1.7" -"@ethersproject/signing-key@5.5.0", "@ethersproject/signing-key@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0" - integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng== +"@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - bn.js "^4.11.9" - elliptic "6.5.4" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" hash.js "1.1.7" "@ethersproject/signing-key@5.6.0", "@ethersproject/signing-key@^5.6.0": @@ -1136,7 +984,7 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/signing-key@^5.7.0": +"@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== @@ -1148,17 +996,17 @@ elliptic "6.5.4" hash.js "1.1.7" -"@ethersproject/solidity@5.5.0", "@ethersproject/solidity@^5.4.0": +"@ethersproject/signing-key@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.5.0.tgz#2662eb3e5da471b85a20531e420054278362f93f" - integrity sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw== + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0" + integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng== dependencies: - "@ethersproject/bignumber" "^5.5.0" "@ethersproject/bytes" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" "@ethersproject/logger" "^5.5.0" - "@ethersproject/sha2" "^5.5.0" - "@ethersproject/strings" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + bn.js "^4.11.9" + elliptic "6.5.4" + hash.js "1.1.7" "@ethersproject/solidity@5.6.0": version "5.6.0" @@ -1172,14 +1020,17 @@ "@ethersproject/sha2" "^5.6.0" "@ethersproject/strings" "^5.6.0" -"@ethersproject/strings@5.5.0", "@ethersproject/strings@^5.0.4", "@ethersproject/strings@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549" - integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ== +"@ethersproject/solidity@5.7.0", "@ethersproject/solidity@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== dependencies: - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/constants" "^5.5.0" - "@ethersproject/logger" "^5.5.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" "@ethersproject/strings@5.6.0", "@ethersproject/strings@^5.6.0": version "5.6.0" @@ -1190,7 +1041,7 @@ "@ethersproject/constants" "^5.6.0" "@ethersproject/logger" "^5.6.0" -"@ethersproject/strings@^5.7.0": +"@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== @@ -1199,20 +1050,14 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.5.0", "@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.4.0", "@ethersproject/transactions@^5.5.0": +"@ethersproject/strings@^5.0.4", "@ethersproject/strings@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908" - integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA== + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549" + integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ== dependencies: - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" "@ethersproject/bytes" "^5.5.0" "@ethersproject/constants" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/rlp" "^5.5.0" - "@ethersproject/signing-key" "^5.5.0" "@ethersproject/transactions@5.6.0", "@ethersproject/transactions@^5.6.0": version "5.6.0" @@ -1244,7 +1089,7 @@ "@ethersproject/rlp" "^5.6.0" "@ethersproject/signing-key" "^5.6.0" -"@ethersproject/transactions@^5.7.0": +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== @@ -1259,14 +1104,20 @@ "@ethersproject/rlp" "^5.7.0" "@ethersproject/signing-key" "^5.7.0" -"@ethersproject/units@5.5.0": +"@ethersproject/transactions@^5.0.0-beta.135", "@ethersproject/transactions@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.5.0.tgz#104d02db5b5dc42cc672cc4587bafb87a95ee45e" - integrity sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag== + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908" + integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA== dependencies: + "@ethersproject/address" "^5.5.0" "@ethersproject/bignumber" "^5.5.0" + "@ethersproject/bytes" "^5.5.0" "@ethersproject/constants" "^5.5.0" + "@ethersproject/keccak256" "^5.5.0" "@ethersproject/logger" "^5.5.0" + "@ethersproject/properties" "^5.5.0" + "@ethersproject/rlp" "^5.5.0" + "@ethersproject/signing-key" "^5.5.0" "@ethersproject/units@5.6.0": version "5.6.0" @@ -1277,26 +1128,14 @@ "@ethersproject/constants" "^5.6.0" "@ethersproject/logger" "^5.6.0" -"@ethersproject/wallet@5.5.0", "@ethersproject/wallet@^5.4.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.5.0.tgz#322a10527a440ece593980dca6182f17d54eae75" - integrity sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q== +"@ethersproject/units@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== dependencies: - "@ethersproject/abstract-provider" "^5.5.0" - "@ethersproject/abstract-signer" "^5.5.0" - "@ethersproject/address" "^5.5.0" - "@ethersproject/bignumber" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/hash" "^5.5.0" - "@ethersproject/hdnode" "^5.5.0" - "@ethersproject/json-wallets" "^5.5.0" - "@ethersproject/keccak256" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/random" "^5.5.0" - "@ethersproject/signing-key" "^5.5.0" - "@ethersproject/transactions" "^5.5.0" - "@ethersproject/wordlists" "^5.5.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" "@ethersproject/wallet@5.6.0": version "5.6.0" @@ -1340,16 +1179,26 @@ "@ethersproject/transactions" "^5.6.0" "@ethersproject/wordlists" "^5.6.0" -"@ethersproject/web@5.5.0", "@ethersproject/web@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.0.tgz#0e5bb21a2b58fb4960a705bfc6522a6acf461e28" - integrity sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA== +"@ethersproject/wallet@5.7.0", "@ethersproject/wallet@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== dependencies: - "@ethersproject/base64" "^5.5.0" - "@ethersproject/bytes" "^5.5.0" - "@ethersproject/logger" "^5.5.0" - "@ethersproject/properties" "^5.5.0" - "@ethersproject/strings" "^5.5.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" "@ethersproject/web@5.6.0", "@ethersproject/web@^5.6.0": version "5.6.0" @@ -1362,7 +1211,7 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.0" -"@ethersproject/web@^5.7.0": +"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== @@ -1373,13 +1222,13 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@ethersproject/wordlists@5.5.0", "@ethersproject/wordlists@^5.5.0": +"@ethersproject/web@^5.5.0": version "5.5.0" - resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.5.0.tgz#aac74963aa43e643638e5172353d931b347d584f" - integrity sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q== + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.0.tgz#0e5bb21a2b58fb4960a705bfc6522a6acf461e28" + integrity sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA== dependencies: + "@ethersproject/base64" "^5.5.0" "@ethersproject/bytes" "^5.5.0" - "@ethersproject/hash" "^5.5.0" "@ethersproject/logger" "^5.5.0" "@ethersproject/properties" "^5.5.0" "@ethersproject/strings" "^5.5.0" @@ -1395,6 +1244,17 @@ "@ethersproject/properties" "^5.6.0" "@ethersproject/strings" "^5.6.0" +"@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@float-capital/float-subgraph-uncrashable@^0.0.0-alpha.4": version "0.0.0-internal-testing.5" resolved "https://registry.yarnpkg.com/@float-capital/float-subgraph-uncrashable/-/float-subgraph-uncrashable-0.0.0-internal-testing.5.tgz#060f98440f6e410812766c5b040952d2d02e2b73" @@ -1505,31 +1365,24 @@ dependencies: multiformats "^9.5.4" -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" "@jridgewell/resolve-uri@^3.0.3": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -1542,19 +1395,120 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== +"@matterlabs/hardhat-zksync-deploy@0.8": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-deploy/-/hardhat-zksync-deploy-0.8.0.tgz#29279e57030affca71de31887bd4f4104ffb4921" + integrity sha512-OkQLt90A6cZnFt/XQptqDvaJl3Q99kKWH6QXjoI19rw9At7gT1+olYjQc9G1oKm+N5gQ66ASXSVIWNGFLszk9g== + dependencies: + "@matterlabs/hardhat-zksync-solc" "^1.0.5" + chai "^4.3.6" + chalk "4.1.2" + fs-extra "^11.2.0" + glob "^10.3.10" + lodash "^4.17.21" + sinon "^17.0.1" + sinon-chai "^3.7.0" + ts-morph "^21.0.1" + +"@matterlabs/hardhat-zksync-deploy@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-deploy/-/hardhat-zksync-deploy-0.9.0.tgz#026815303df792af50d722e4a59b5aa26baa5ed7" + integrity sha512-F9qPa7+Etq9/zAWEhsJ+oHCJy+B+yXxt8Tv1wgJsw5yc/race7VIdrdWW6xUS5YNpwhsiuM2cxYBcrE+FSU/TA== + dependencies: + "@matterlabs/hardhat-zksync-solc" "^1.0.5" + chai "^4.3.6" + chalk "4.1.2" + fs-extra "^11.2.0" + glob "^10.3.10" + lodash "^4.17.21" + sinon "^17.0.1" + sinon-chai "^3.7.0" + ts-morph "^21.0.1" + +"@matterlabs/hardhat-zksync-ethers@0.0.1-beta.2": + version "0.0.1-beta.2" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-ethers/-/hardhat-zksync-ethers-0.0.1-beta.2.tgz#72c58559e6b5550777dd290f9bfa4f117ac67697" + integrity sha512-j5G2uQu/6feEGdFeQ6upbIoUGTRxBJRvFGySbVlC8UxYIdNFMVUUI3Vb2TYlH2EvlJN89Uue908fvGruTrEIHA== + dependencies: + chalk "5.3.0" + +"@matterlabs/hardhat-zksync-node@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-node/-/hardhat-zksync-node-0.1.0.tgz#de9b6b277727457ee981208030f33d06537db5d7" + integrity sha512-P3QZkcajplkQZg4Mj7soAlvH7JZObn853GtsD6NRCcjnwn3Id2yV1B5Iokg/BACRAwXCSLHRTn4nc2B/xkyqfg== + dependencies: + "@matterlabs/hardhat-zksync-solc" "^1.1.4" + axios "^1.4.0" + chai "^4.3.6" + chalk "4.1.2" + fs-extra "^11.1.1" + proxyquire "^2.1.3" + sinon "^17.0.1" + sinon-chai "^3.7.0" + undici "^5.14.0" + +"@matterlabs/hardhat-zksync-solc@^1.0.5", "@matterlabs/hardhat-zksync-solc@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-1.1.4.tgz#04a2fad6fb6b6944c64ad969080ee65b9af3f617" + integrity sha512-4/usbogh9neewR2/v8Dn2OzqVblZMUuT/iH2MyPZgPRZYQlL4SlZtMvokU9UQjZT6iSoaKCbbdWESHDHSzfUjA== + dependencies: + "@nomiclabs/hardhat-docker" "^2.0.0" + chai "^4.3.6" + chalk "4.1.2" + debug "^4.3.4" + dockerode "^4.0.2" + fs-extra "^11.1.1" + proper-lockfile "^4.1.2" + semver "^7.5.1" + sinon "^17.0.1" + sinon-chai "^3.7.0" + undici "^5.14.0" + +"@matterlabs/hardhat-zksync-upgradable@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-upgradable/-/hardhat-zksync-upgradable-0.4.0.tgz#402c80bb7a3eb2de7284d02e0b10d16d0b1885af" + integrity sha512-ufZfwJUylyjxPsdf5WtUo5kBtTjF+rxprY1AOudzxumzL3JPkPsHQL3ostrlpTvseB5t3j+AMHcNPfioEN1yRg== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@matterlabs/hardhat-zksync-deploy" "^0.9.0" + "@matterlabs/hardhat-zksync-solc" "^1.1.4" + "@openzeppelin/upgrades-core" "1.27.0" + chalk "4.1.2" + compare-versions "^6.0.0" + dockerode "^3.3.4" + ethereumjs-util "^7.1.5" + ethers "~5.7.2" + fs-extra "^11.1.1" + hardhat "^2.14.0" + proper-lockfile "^4.1.1" + solidity-ast "npm:solidity-ast@0.4.45" + zksync-ethers "^5.0.0" + +"@matterlabs/hardhat-zksync-verify@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-verify/-/hardhat-zksync-verify-0.6.0.tgz#8923a124d7d2a84fea9a037b654a733bec07836e" + integrity sha512-LndlCZLgAd7r2zB/VJCuLOhVNpcWFbk3UOqv5sVOtO+XdV0A74tiLaxlyGNILm9lsbP+cPnGArDq9KI0yV4FEw== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@ethersproject/address" "5.7.0" + "@matterlabs/hardhat-zksync-solc" "^1.1.4" + "@nomicfoundation/hardhat-verify" "^2.0.0" + "@openzeppelin/contracts" "^4.9.2" + axios "^1.6.2" + cbor "^8.1.0" + chai "^4.3.6" + chalk "4.1.2" + debug "^4.1.1" + hardhat "^2.14.0" + sinon "^17.0.1" + sinon-chai "^3.7.0" + zksync-ethers "^5.0.0" + "@metamask/eth-sig-util@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088" @@ -1602,6 +1556,54 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nomicfoundation/edr-darwin-arm64@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.7.tgz#c204edc79643624dbd431b489b254778817d8244" + integrity sha512-6tK9Lv/lSfyBvpEQ4nsTfgxyDT1y1Uv/x8Wa+aB+E8qGo3ToexQ1BMVjxJk6PChXCDOWxB3B4KhqaZFjdhl3Ow== + +"@nomicfoundation/edr-darwin-x64@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.7.tgz#c3b394445084270cc5250d6c1869b0574e7ef810" + integrity sha512-1RrQ/1JPwxrYO69e0tglFv5H+ggour5Ii3bb727+yBpBShrxtOTQ7fZyfxA5h62LCN+0Z9wYOPeQ7XFcVurMaQ== + +"@nomicfoundation/edr-linux-arm64-gnu@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.7.tgz#6d65545a44d1323bb7ab08c3306947165d2071de" + integrity sha512-ds/CKlBoVXIihjhflhgPn13EdKWed6r5bgvMs/YwRqT5wldQAQJZWAfA2+nYm0Yi2gMGh1RUpBcfkyl4pq7G+g== + +"@nomicfoundation/edr-linux-arm64-musl@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.7.tgz#5368534bceac1a8c18b1be6b908caca5d39b0c03" + integrity sha512-e29udiRaPujhLkM3+R6ju7QISrcyOqpcaxb2FsDWBkuD7H8uU9JPZEyyUIpEp5uIY0Jh1eEJPKZKIXQmQAEAuw== + +"@nomicfoundation/edr-linux-x64-gnu@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.7.tgz#42349bf5941dbb54a5719942924c6e4e8cde348e" + integrity sha512-/xkjmTyv+bbJ4akBCW0qzFKxPOV4AqLOmqurov+s9umHb16oOv72osSa3SdzJED2gHDaKmpMITT4crxbar4Axg== + +"@nomicfoundation/edr-linux-x64-musl@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.7.tgz#e6babe11c9a8012f1284e6e48c3551861f2a7cd4" + integrity sha512-QwBP9xlmsbf/ldZDGLcE4QiAb8Zt46E/+WLpxHBATFhGa7MrpJh6Zse+h2VlrT/SYLPbh2cpHgSmoSlqVxWG9g== + +"@nomicfoundation/edr-win32-x64-msvc@0.3.7": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.7.tgz#1504b98f305f03be153b0220a546985660de9dc6" + integrity sha512-j/80DEnkxrF2ewdbk/gQ2EOPvgF0XSsg8D0o4+6cKhUVAW6XwtWKzIphNL6dyD2YaWEPgIrNvqiJK/aln0ww4Q== + +"@nomicfoundation/edr@^0.3.5": + version "0.3.7" + resolved "https://registry.yarnpkg.com/@nomicfoundation/edr/-/edr-0.3.7.tgz#9c75edf1fcf601617905b2c89acf103f4786d017" + integrity sha512-v2JFWnFKRsnOa6PDUrD+sr8amcdhxnG/YbL7LzmgRGU1odWEyOF4/EwNeUajQr4ZNKVWrYnJ6XjydXtUge5OBQ== + optionalDependencies: + "@nomicfoundation/edr-darwin-arm64" "0.3.7" + "@nomicfoundation/edr-darwin-x64" "0.3.7" + "@nomicfoundation/edr-linux-arm64-gnu" "0.3.7" + "@nomicfoundation/edr-linux-arm64-musl" "0.3.7" + "@nomicfoundation/edr-linux-x64-gnu" "0.3.7" + "@nomicfoundation/edr-linux-x64-musl" "0.3.7" + "@nomicfoundation/edr-win32-x64-msvc" "0.3.7" + "@nomicfoundation/ethereumjs-block@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz#fdd5c045e7baa5169abeed0e1202bf94e4481c49" @@ -1632,6 +1634,13 @@ lru-cache "^5.1.1" memory-level "^1.0.0" +"@nomicfoundation/ethereumjs-common@4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz#9901f513af2d4802da87c66d6f255b510bef5acb" + integrity sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg== + dependencies: + "@nomicfoundation/ethereumjs-util" "9.0.4" + "@nomicfoundation/ethereumjs-common@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz#f6bcc7753994555e49ab3aa517fc8bcf89c280b9" @@ -1666,6 +1675,11 @@ mcl-wasm "^0.7.1" rustbn.js "~0.2.0" +"@nomicfoundation/ethereumjs-rlp@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz#66c95256fc3c909f6fb18f6a586475fc9762fa30" + integrity sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw== + "@nomicfoundation/ethereumjs-rlp@^4.0.0", "@nomicfoundation/ethereumjs-rlp@^4.0.0-beta.2": version "4.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz#d9a9c5f0f10310c8849b6525101de455a53e771d" @@ -1694,6 +1708,16 @@ ethereum-cryptography "0.1.3" readable-stream "^3.6.0" +"@nomicfoundation/ethereumjs-tx@5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz#b0ceb58c98cc34367d40a30d255d6315b2f456da" + integrity sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw== + dependencies: + "@nomicfoundation/ethereumjs-common" "4.0.4" + "@nomicfoundation/ethereumjs-rlp" "5.0.4" + "@nomicfoundation/ethereumjs-util" "9.0.4" + ethereum-cryptography "0.1.3" + "@nomicfoundation/ethereumjs-tx@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz#59dc7452b0862b30342966f7052ab9a1f7802f52" @@ -1704,6 +1728,14 @@ "@nomicfoundation/ethereumjs-util" "^8.0.0" ethereum-cryptography "0.1.3" +"@nomicfoundation/ethereumjs-util@9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz#84c5274e82018b154244c877b76bc049a4ed7b38" + integrity sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q== + dependencies: + "@nomicfoundation/ethereumjs-rlp" "5.0.4" + ethereum-cryptography "0.1.3" + "@nomicfoundation/ethereumjs-util@^8.0.0", "@nomicfoundation/ethereumjs-util@^8.0.0-rc.3": version "8.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz#deb2b15d2c308a731e82977aefc4e61ca0ece6c5" @@ -1712,7 +1744,7 @@ "@nomicfoundation/ethereumjs-rlp" "^4.0.0-beta.2" ethereum-cryptography "0.1.3" -"@nomicfoundation/ethereumjs-vm@^6.0.0", "@nomicfoundation/ethereumjs-vm@^6.0.0-rc.3": +"@nomicfoundation/ethereumjs-vm@^6.0.0-rc.3": version "6.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.0.0.tgz#2bb50d332bf41790b01a3767ffec3987585d1de6" integrity sha512-JMPxvPQ3fzD063Sg3Tp+UdwUkVxMoo1uML6KSzFhMH3hoQi/LMuXBoEHAoW83/vyNS9BxEe6jm6LmT5xdeEJ6w== @@ -1761,6 +1793,21 @@ table "^6.8.0" undici "^5.14.0" +"@nomicfoundation/hardhat-verify@^2.0.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.6.tgz#02623c431244c92a852c524008239fc616e1c658" + integrity sha512-oKUI5fl8QC8jysE2LUBHE6rObzEmccJcc4b43Ov7LFMlCBZJE27qoqGIsg/++wX7L8Jdga+bkejPxl8NvsecpQ== + dependencies: + "@ethersproject/abi" "^5.1.2" + "@ethersproject/address" "^5.0.2" + cbor "^8.1.0" + chalk "^2.4.2" + debug "^4.1.1" + lodash.clonedeep "^4.5.0" + semver "^6.3.0" + table "^6.8.0" + undici "^5.14.0" + "@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.0.tgz#83a7367342bd053a76d04bbcf4f373fef07cf760" @@ -1827,6 +1874,15 @@ "@nomicfoundation/solidity-analyzer-win32-ia32-msvc" "0.1.0" "@nomicfoundation/solidity-analyzer-win32-x64-msvc" "0.1.0" +"@nomiclabs/hardhat-docker@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-docker/-/hardhat-docker-2.0.2.tgz#ae964be17951275a55859ff7358e9e7c77448846" + integrity sha512-XgGEpRT3wlA1VslyB57zyAHV+oll8KnV1TjwnxxC1tpAL04/lbdwpdO5KxInVN8irMSepqFpsiSkqlcnvbE7Ng== + dependencies: + dockerode "^2.5.8" + fs-extra "^7.0.1" + node-fetch "^2.6.0" + "@nomiclabs/hardhat-ethers@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.1.tgz#8057b43566a0e41abeb8142064a3c0d3f23dca86" @@ -1901,15 +1957,15 @@ dependencies: "@openzeppelin/contracts" "^4.2.0" -"@openzeppelin/contracts-upgradeable@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.1.tgz#363f7dd08f25f8f77e16d374350c3d6b43340a7a" - integrity sha512-1wTv+20lNiC0R07jyIAbHU7TNHKRwGiTGRfiNnA8jOWjKT98g5OgLpYWOi40Vgpk8SPLA9EvfJAbAeIyVn+7Bw== +"@openzeppelin/contracts-upgradeable@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.5.tgz#572b5da102fc9be1d73f34968e0ca56765969812" + integrity sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg== -"@openzeppelin/contracts@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.1.tgz#709cfc4bbb3ca9f4460d60101f15dac6b7a2d5e4" - integrity sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ== +"@openzeppelin/contracts@4.9.5": + version "4.9.5" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.5.tgz#1eed23d4844c861a1835b5d33507c1017fa98de8" + integrity sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg== "@openzeppelin/contracts@^4.1.0": version "4.6.0" @@ -1921,6 +1977,11 @@ resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.5.0.tgz#3fd75d57de172b3743cdfc1206883f56430409cc" integrity sha512-fdkzKPYMjrRiPK6K4y64e6GzULR7R7RwxSigHS8DDp7aWDeoReqsQI+cxHV1UuhAqX69L1lAaWDxenfP+xiqzA== +"@openzeppelin/contracts@^4.9.2": + version "4.9.6" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.6.tgz#2a880a24eb19b4f8b25adc2a5095f2aa27f39677" + integrity sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA== + "@openzeppelin/hardhat-upgrades@^1.23.1": version "1.23.1" resolved "https://registry.yarnpkg.com/@openzeppelin/hardhat-upgrades/-/hardhat-upgrades-1.23.1.tgz#45b47f7083493dae7339782aeeafd68606f76488" @@ -1931,6 +1992,20 @@ debug "^4.1.1" proper-lockfile "^4.1.1" +"@openzeppelin/upgrades-core@1.27.0": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.27.0.tgz#43f05c3e45b21bdc583488aa42297fbb0d065d17" + integrity sha512-FBIuFPKiRNMhW09HS8jkmV5DueGfxO2wp/kmCa0m0SMDyX4ROumgy/4Ao0/yH8/JZZPDiH1q3EnTRn+B7TGYgg== + dependencies: + cbor "^8.0.0" + chalk "^4.1.0" + compare-versions "^5.0.0" + debug "^4.1.1" + ethereumjs-util "^7.0.3" + minimist "^1.2.7" + proper-lockfile "^4.1.1" + solidity-ast "^0.4.15" + "@openzeppelin/upgrades-core@^1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.25.0.tgz#4f540e2043b98f8b59a4e8a988b9aeb5b5f23a35" @@ -1944,6 +2019,20 @@ proper-lockfile "^4.1.1" solidity-ast "^0.4.15" +"@openzeppelin/upgrades-core@^1.33.1": + version "1.33.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/upgrades-core/-/upgrades-core-1.33.1.tgz#2e129ce1ab7bd07d07e98822ca8bb8de1d3b008e" + integrity sha512-YRxIRhTY1b+j7+NUUu8Uuem5ugxKexEMVd8dBRWNgWeoN1gS1OCrhgUg0ytL+54vzQ+SGWZDfNnzjVuI1Cj1Zw== + dependencies: + cbor "^9.0.0" + chalk "^4.1.0" + compare-versions "^6.0.0" + debug "^4.1.1" + ethereumjs-util "^7.0.3" + minimist "^1.2.7" + proper-lockfile "^4.1.1" + solidity-ast "^0.4.51" + "@peculiar/asn1-schema@^2.3.6": version "2.3.6" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz#3dd3c2ade7f702a9a94dfb395c192f5fa5d6b922" @@ -1971,6 +2060,11 @@ tslib "^2.5.0" webcrypto-core "^1.7.7" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -2148,6 +2242,41 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== + dependencies: + type-detect "4.0.8" + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^11.2.2": + version "11.2.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" + integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@sinonjs/samsam@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" + integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew== + dependencies: + "@sinonjs/commons" "^2.0.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" + integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== + "@solidity-parser/parser@^0.12.0": version "0.12.2" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.12.2.tgz#1afad367cb29a2ed8cdd4a3a62701c2821fb578f" @@ -2167,11 +2296,6 @@ dependencies: antlr4ts "^0.5.0-alpha.4" -"@solidity-parser/parser@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.17.0.tgz#52a2fcc97ff609f72011014e4c5b485ec52243ef" - integrity sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw== - "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -2179,18 +2303,6 @@ dependencies: defer-to-connect "^1.0.1" -"@trivago/prettier-plugin-sort-imports@^4.2.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz#725f411646b3942193a37041c84e0b2116339789" - integrity sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ== - dependencies: - "@babel/generator" "7.17.7" - "@babel/parser" "^7.20.5" - "@babel/traverse" "7.23.2" - "@babel/types" "7.17.0" - javascript-natural-sort "0.7.1" - lodash "^4.17.21" - "@truffle/abi-utils@^0.2.13": version "0.2.13" resolved "https://registry.yarnpkg.com/@truffle/abi-utils/-/abi-utils-0.2.13.tgz#63b7f5e5b61a86e563b2ea0c93a39b094086d205" @@ -2287,6 +2399,16 @@ mkdirp "^1.0.4" path-browserify "^1.0.1" +"@ts-morph/common@~0.22.0": + version "0.22.0" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.22.0.tgz#8951d451622a26472fbc3a227d6c3a90e687a683" + integrity sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw== + dependencies: + fast-glob "^3.3.2" + minimatch "^9.0.3" + mkdirp "^3.0.1" + path-browserify "^1.0.1" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -2824,6 +2946,13 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + ansi-colors@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" @@ -2866,6 +2995,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -2880,6 +3014,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.3.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" @@ -2969,6 +3108,14 @@ array-back@^4.0.1: 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.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.5" + is-array-buffer "^3.0.4" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -2990,6 +3137,18 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.findlast@^1.2.2: + 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.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" + array.prototype.flat@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" @@ -2999,6 +3158,20 @@ array.prototype.flat@^1.2.5: define-properties "^1.1.3" es-abstract "^1.19.0" +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.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: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -3014,7 +3187,7 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" -asn1@~0.2.3: +asn1@^0.2.6, asn1@~0.2.3: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== @@ -3116,6 +3289,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" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -3133,6 +3313,15 @@ axios@^0.21.1, axios@^0.21.4: dependencies: follow-redirects "^1.14.0" +axios@^1.4.0, axios@^1.6.2: + 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" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -3150,7 +3339,7 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -bcrypt-pbkdf@^1.0.0: +bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= @@ -3240,6 +3429,15 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + bl@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/bl/-/bl-5.0.0.tgz#6928804a41e9da9034868e1c50ca88f21f57aea2" @@ -3307,6 +3505,20 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boxen@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3480,6 +3692,11 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" +buildcheck@~0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238" + integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A== + busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -3518,6 +3735,17 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +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" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -3565,7 +3793,7 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -3603,6 +3831,13 @@ cbor@^8.0.0, cbor@^8.1.0: dependencies: nofilter "^3.1.0" +cbor@^9.0.0: + 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" + cborg@^1.5.4: version "1.9.1" resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.9.1.tgz#9ea2f7b1745048e7db51e78d54e8a9a0e4f64a11" @@ -3645,6 +3880,19 @@ chai@^4.3.4: pathval "^1.1.1" type-detect "^4.0.5" +chai@^4.3.6: + version "4.4.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" + integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + chalk@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -3653,6 +3901,19 @@ chalk@3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3662,14 +3923,6 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - change-case@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.2.tgz#fd48746cce02f03f0a672577d1d3a8dc2eceb037" @@ -3709,6 +3962,13 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= +check-error@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.3.tgz#a6502e4312a7ee969f646e83bb3ddd56281bd694" + integrity sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg== + dependencies: + get-func-name "^2.0.2" + cheerio-select@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" @@ -3780,7 +4040,7 @@ chokidar@^3.4.0, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -chownr@^1.0.1, chownr@^1.1.4: +chownr@^1.0.1, chownr@^1.1.1, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== @@ -3842,6 +4102,11 @@ clean-stack@^3.0.1: dependencies: escape-string-regexp "4.0.0" +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -3937,6 +4202,11 @@ code-block-writer@^11.0.3: resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-11.0.3.tgz#9eec2993edfb79bfae845fbc093758c0a0b73b76" integrity sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw== +code-block-writer@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770" + integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w== + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -4032,6 +4302,11 @@ compare-versions@^5.0.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.1.tgz#14c6008436d994c3787aba38d4087fabe858555e" integrity sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ== +compare-versions@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-6.1.0.tgz#3f2131e3ae93577df111dba133e6db876ffe127a" + integrity sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -4135,6 +4410,14 @@ cosmiconfig@^5.0.7: js-yaml "^3.13.1" parse-json "^4.0.0" +cpu-features@~0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.9.tgz#5226b92f0f1c63122b0a3eb84cb8335a4de499fc" + integrity sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ== + dependencies: + buildcheck "~0.0.6" + nan "^2.17.0" + crc-32@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" @@ -4179,7 +4462,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@7.0.3, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4265,6 +4548,33 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +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" @@ -4291,7 +4601,7 @@ debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: dependencies: ms "2.1.2" -debug@4.3.4, debug@^4.1.0, debug@^4.3.3, debug@^4.3.4: +debug@4.3.4, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4334,7 +4644,7 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" -deep-eql@^4.0.1: +deep-eql@^4.0.1, deep-eql@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" integrity sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw== @@ -4363,6 +4673,15 @@ defer-to-connect@^1.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== +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-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -4370,6 +4689,15 @@ define-properties@^1.1.2, define-properties@^1.1.3: dependencies: object-keys "^1.0.12" +define-properties@^1.2.0, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + delay@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" @@ -4426,6 +4754,11 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -4482,7 +4815,27 @@ docker-modem@^1.0.8: readable-stream "~1.0.26-4" split-ca "^1.0.0" -dockerode@2.5.8: +docker-modem@^3.0.0: + version "3.0.8" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.8.tgz#ef62c8bdff6e8a7d12f0160988c295ea8705e77a" + integrity sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^1.11.0" + +docker-modem@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-5.0.3.tgz#50c06f11285289f58112b5c4c4d89824541c41d0" + integrity sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^1.15.0" + +dockerode@2.5.8, dockerode@^2.5.8: version "2.5.8" resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-2.5.8.tgz#1b661e36e1e4f860e25f56e0deabe9f87f1d0acc" integrity sha512-+7iOUYBeDTScmOmQqpUYQaE7F4vvIt6+gIZNHWhqAQEI887tiPFB9OvXI/HzQYqfUNvukMK+9myLW63oTJPZpw== @@ -4491,6 +4844,24 @@ dockerode@2.5.8: docker-modem "^1.0.8" tar-fs "~1.16.3" +dockerode@^3.3.4: + version "3.3.5" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629" + integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA== + dependencies: + "@balena/dockerignore" "^1.0.2" + docker-modem "^3.0.0" + tar-fs "~2.0.1" + +dockerode@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-4.0.2.tgz#dedc8529a1db3ac46d186f5912389899bc309f7d" + integrity sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w== + dependencies: + "@balena/dockerignore" "^1.0.2" + docker-modem "^5.0.3" + tar-fs "~2.0.1" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -4566,6 +4937,11 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -4623,6 +4999,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encode-utf8@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" @@ -4640,7 +5021,7 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4702,6 +5083,93 @@ es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.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.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== + 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.4" + get-symbol-description "^1.0.2" + globalthis "^1.0.3" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" + has-symbols "^1.0.3" + hasown "^2.0.2" + internal-slot "^1.0.7" + is-array-buffer "^3.0.4" + is-callable "^1.2.7" + is-data-view "^1.0.1" + is-negative-zero "^2.0.3" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.3" + is-string "^1.0.7" + 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.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.8" + 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.6" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.15" + +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.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.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== + dependencies: + hasown "^2.0.0" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -5274,7 +5742,7 @@ ethereumjs-util@^7.0.10, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.3: ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethereumjs-util@^7.0.3: +ethereumjs-util@^7.0.3, ethereumjs-util@^7.1.5: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== @@ -5347,42 +5815,6 @@ ethers@^5.0.13: "@ethersproject/web" "5.6.0" "@ethersproject/wordlists" "5.6.0" -ethers@^5.5.1: - version "5.5.1" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.1.tgz#d3259a95a42557844aa543906c537106c0406fbf" - integrity sha512-RodEvUFZI+EmFcE6bwkuJqpCYHazdzeR1nMzg+YWQSmQEsNtfl1KHGfp/FWZYl48bI/g7cgBeP2IlPthjiVngw== - dependencies: - "@ethersproject/abi" "5.5.0" - "@ethersproject/abstract-provider" "5.5.1" - "@ethersproject/abstract-signer" "5.5.0" - "@ethersproject/address" "5.5.0" - "@ethersproject/base64" "5.5.0" - "@ethersproject/basex" "5.5.0" - "@ethersproject/bignumber" "5.5.0" - "@ethersproject/bytes" "5.5.0" - "@ethersproject/constants" "5.5.0" - "@ethersproject/contracts" "5.5.0" - "@ethersproject/hash" "5.5.0" - "@ethersproject/hdnode" "5.5.0" - "@ethersproject/json-wallets" "5.5.0" - "@ethersproject/keccak256" "5.5.0" - "@ethersproject/logger" "5.5.0" - "@ethersproject/networks" "5.5.0" - "@ethersproject/pbkdf2" "5.5.0" - "@ethersproject/properties" "5.5.0" - "@ethersproject/providers" "5.5.0" - "@ethersproject/random" "5.5.0" - "@ethersproject/rlp" "5.5.0" - "@ethersproject/sha2" "5.5.0" - "@ethersproject/signing-key" "5.5.0" - "@ethersproject/solidity" "5.5.0" - "@ethersproject/strings" "5.5.0" - "@ethersproject/transactions" "5.5.0" - "@ethersproject/units" "5.5.0" - "@ethersproject/wallet" "5.5.0" - "@ethersproject/web" "5.5.0" - "@ethersproject/wordlists" "5.5.0" - ethers@^5.6.2: version "5.6.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.2.tgz#e75bac7f038c5e0fdde667dba62fc223924143a2" @@ -5419,6 +5851,42 @@ ethers@^5.6.2: "@ethersproject/web" "5.6.0" "@ethersproject/wordlists" "5.6.0" +ethers@^5.7.0, ethers@^5.7.2, ethers@~5.7.0, ethers@~5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -5610,6 +6078,17 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -5674,6 +6153,14 @@ filelist@^1.0.4: dependencies: minimatch "^5.0.1" +fill-keys@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fill-keys/-/fill-keys-1.0.2.tgz#9a8fa36f4e8ad634e3bf6b4f3c8882551452eb20" + integrity sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA== + dependencies: + is-object "~1.0.1" + merge-descriptors "~1.0.0" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -5795,11 +6282,31 @@ follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== +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" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -5887,6 +6394,15 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^11.1.1, fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -5961,11 +6477,31 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" @@ -5981,6 +6517,11 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-func-name@^2.0.1, get-func-name@^2.0.2: + version "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.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -5990,6 +6531,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, 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" @@ -6037,6 +6589,15 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +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.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -6112,6 +6673,17 @@ glob@9.3.5: minipass "^4.2.4" path-scurry "^1.6.1" +glob@^10.3.10: + version "10.3.12" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b" + integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.3.6" + minimatch "^9.0.1" + minipass "^7.0.4" + path-scurry "^1.10.2" + glob@^5.0.15: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -6147,7 +6719,7 @@ global@~4.4.0: min-document "^2.19.0" process "^0.11.10" -globals@^11.1.0, globals@^11.7.0: +globals@^11.7.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== @@ -6159,6 +6731,14 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" +globalthis@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^10.0.1: version "10.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" @@ -6233,6 +6813,13 @@ gluegun@5.1.2: which "2.0.2" yargs-parser "^21.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + got@9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -6325,33 +6912,35 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -hardhat-deploy@^0.9.26: - version "0.9.26" - resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.9.26.tgz#84b85dbf67f317c8eca8b7cfeab27c051be6f023" - integrity sha512-hQsftZrmPkel8NrcVqHsJaXMv9xCVIaJdzn8TFa8Ow+nuvkt2NvIjsC1aN02SAL2nTI1Na3H1vfubWHSTbN2gA== - dependencies: - "@ethersproject/abi" "^5.4.0" - "@ethersproject/abstract-signer" "^5.4.1" - "@ethersproject/address" "^5.4.0" - "@ethersproject/bignumber" "^5.4.1" - "@ethersproject/bytes" "^5.4.0" - "@ethersproject/constants" "^5.4.0" - "@ethersproject/contracts" "^5.4.1" - "@ethersproject/providers" "^5.4.4" - "@ethersproject/solidity" "^5.4.0" - "@ethersproject/transactions" "^5.4.0" - "@ethersproject/wallet" "^5.4.0" +hardhat-deploy@0.12.4: + version "0.12.4" + resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.12.4.tgz#5ebef37f1004f52a74987213b0465ad7c9433fb2" + integrity sha512-bYO8DIyeGxZWlhnMoCBon9HNZb6ji0jQn7ngP1t5UmGhC8rQYhji7B73qETMOFhzt5ECZPr+U52duj3nubsqdQ== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/solidity" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" "@types/qs" "^6.9.7" axios "^0.21.1" chalk "^4.1.2" chokidar "^3.5.2" debug "^4.3.2" enquirer "^2.3.6" + ethers "^5.7.0" form-data "^4.0.0" fs-extra "^10.0.0" match-all "^1.2.6" murmur-128 "^0.2.1" qs "^6.9.4" + zksync-ethers "^5.0.0" hardhat-gas-reporter@^1.0.4: version "1.0.4" @@ -6361,31 +6950,25 @@ hardhat-gas-reporter@^1.0.4: eth-gas-reporter "^0.2.20" sha1 "^1.1.1" -hardhat@^2.12.7: - version "2.12.7" - resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.12.7.tgz#d8de2dc32e9a2956d53cf26ef4cd5857e57a3138" - integrity sha512-voWoN6zn5d8BOEaczSyK/1PyfdeOeI3SbGCFb36yCHTJUt6OIqLb+ZDX30VhA1UsYKzLqG7UnWl3fKJUuANc6A== +hardhat@^2.12.4, hardhat@^2.14.0: + version "2.22.3" + resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.3.tgz#50605daca6b29862397e446c42ec14c89430bec3" + integrity sha512-k8JV2ECWNchD6ahkg2BR5wKVxY0OiKot7fuxiIpRK0frRqyOljcR2vKwgWSLw6YIeDcNNA4xybj7Og7NSxr2hA== dependencies: "@ethersproject/abi" "^5.1.2" "@metamask/eth-sig-util" "^4.0.0" - "@nomicfoundation/ethereumjs-block" "^4.0.0" - "@nomicfoundation/ethereumjs-blockchain" "^6.0.0" - "@nomicfoundation/ethereumjs-common" "^3.0.0" - "@nomicfoundation/ethereumjs-evm" "^1.0.0" - "@nomicfoundation/ethereumjs-rlp" "^4.0.0" - "@nomicfoundation/ethereumjs-statemanager" "^1.0.0" - "@nomicfoundation/ethereumjs-trie" "^5.0.0" - "@nomicfoundation/ethereumjs-tx" "^4.0.0" - "@nomicfoundation/ethereumjs-util" "^8.0.0" - "@nomicfoundation/ethereumjs-vm" "^6.0.0" + "@nomicfoundation/edr" "^0.3.5" + "@nomicfoundation/ethereumjs-common" "4.0.4" + "@nomicfoundation/ethereumjs-tx" "5.0.4" + "@nomicfoundation/ethereumjs-util" "9.0.4" "@nomicfoundation/solidity-analyzer" "^0.1.0" "@sentry/node" "^5.18.1" "@types/bn.js" "^5.1.0" "@types/lru-cache" "^5.1.0" - abort-controller "^3.0.0" adm-zip "^0.4.16" aggregate-error "^3.0.0" ansi-escapes "^4.3.0" + boxen "^5.1.2" chalk "^2.4.2" chokidar "^3.4.0" ci-info "^2.0.0" @@ -6405,7 +6988,6 @@ hardhat@^2.12.7: mnemonist "^0.38.0" mocha "^10.0.0" p-map "^4.0.0" - qs "^6.7.0" raw-body "^2.4.1" resolve "1.17.0" semver "^6.3.0" @@ -6422,6 +7004,11 @@ has-bigints@^1.0.1: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== +has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" @@ -6437,6 +7024,18 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-property-descriptors@^1.0.0, 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, 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-symbol-support-x@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" @@ -6447,6 +7046,11 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" @@ -6461,6 +7065,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" + has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -6498,6 +7109,13 @@ hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: inherits "^2.0.3" minimalistic-assert "^1.0.1" +hasown@^2.0.0, 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" @@ -6804,6 +7422,15 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +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: + es-errors "^1.3.0" + hasown "^2.0.0" + side-channel "^1.0.4" + interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" @@ -7019,6 +7646,14 @@ is-arguments@^1.0.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +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.1" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -7051,11 +7686,23 @@ is-buffer@^2.0.5, is-buffer@~2.0.3: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +is-callable@^1.1.3, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + is-core-module@^2.2.0, is-core-module@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" @@ -7070,6 +7717,13 @@ is-core-module@^2.8.1: dependencies: has "^1.0.3" +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" @@ -7162,6 +7816,11 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== +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.6" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" @@ -7174,7 +7833,7 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-object@^1.0.1: +is-object@^1.0.1, is-object@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== @@ -7207,6 +7866,13 @@ is-shared-array-buffer@^1.0.1: resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== +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.7" + is-stream@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -7231,6 +7897,13 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" +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, is-typed-array@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" @@ -7271,6 +7944,13 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.0.0" +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -7283,6 +7963,11 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -7415,6 +8100,15 @@ it-to-stream@^1.0.0: p-fifo "^1.0.0" readable-stream "^3.6.0" +jackspeak@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.6.1, jake@^10.8.5: version "10.8.7" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" @@ -7425,11 +8119,6 @@ jake@^10.6.1, jake@^10.8.5: filelist "^1.0.4" minimatch "^3.1.2" -javascript-natural-sort@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" - integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== - jayson@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.0.0.tgz#145a0ced46f900934c9b307e1332bcb0c7dbdb17" @@ -7491,11 +8180,6 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -7586,6 +8270,11 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +just-extend@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-6.2.0.tgz#b816abfb3d67ee860482e7401564672558163947" + integrity sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw== + keccak@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/keccak/-/keccak-2.1.0.tgz#734ea53f2edcfd0f42cdb8d5f4c358fef052752b" @@ -7722,6 +8411,11 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" @@ -7849,6 +8543,13 @@ loupe@^2.3.1: dependencies: get-func-name "^2.0.0" +loupe@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.7.tgz#6e69b7d4db7d3ab436328013d37d1c8c3540c697" + integrity sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA== + dependencies: + get-func-name "^2.0.1" + lower-case-first@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" @@ -7871,6 +8572,11 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lru-cache@^10.2.0: + version "10.2.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" + integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7962,6 +8668,11 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= +merge-descriptors@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + merge-options@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" @@ -8084,11 +8795,23 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.1, minimatch@^9.0.3: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -8114,6 +8837,11 @@ minipass@^4.2.4: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== +minipass@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -8129,6 +8857,11 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp-promise@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" @@ -8148,6 +8881,11 @@ mkdirp@0.5.5, mkdirp@0.5.x, mkdirp@^0.5.1, mkdirp@^0.5.5: dependencies: minimist "^1.2.5" +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + mnemonist@^0.38.0: version "0.38.5" resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade" @@ -8252,6 +8990,11 @@ module-error@^1.0.1, module-error@^1.0.2: resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== +module-not-found-error@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" + integrity sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8365,6 +9108,11 @@ nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +nan@^2.17.0, nan@^2.18.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" + integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== + nano-base32@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/nano-base32/-/nano-base32-1.0.1.tgz#ba548c879efcfb90da1c4d9e097db4a46c9255ef" @@ -8430,6 +9178,17 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +nise@^5.1.5: + version "5.1.9" + resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.9.tgz#0cb73b5e4499d738231a473cd89bd8afbb618139" + integrity sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/text-encoding" "^0.7.2" + just-extend "^6.2.0" + path-to-regexp "^6.2.1" + no-case@^2.2.0, no-case@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" @@ -8457,7 +9216,7 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@^2.6.8: +node-fetch@^2.6.0, node-fetch@^2.6.8: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -8552,6 +9311,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -8582,6 +9346,16 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" +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== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + object.getownpropertydescriptors@^2.0.3: version "2.1.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" @@ -8948,6 +9722,14 @@ path-parse@^1.0.6, path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7" + integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry@^1.6.1: version "1.10.1" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" @@ -8961,6 +9743,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" + integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -8996,11 +9783,6 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -9045,6 +9827,11 @@ pluralize@^8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +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.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9081,15 +9868,6 @@ prettier-plugin-solidity@^1.1.1: semver "^7.3.8" solidity-comments-extractor "^0.0.7" -prettier-plugin-solidity@^1.1.3: - version "1.3.1" - resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz#59944d3155b249f7f234dee29f433524b9a4abcf" - integrity sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA== - dependencies: - "@solidity-parser/parser" "^0.17.0" - semver "^7.5.4" - solidity-comments-extractor "^0.0.8" - prettier@1.19.1, prettier@^1.14.3: version "1.19.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" @@ -9110,11 +9888,6 @@ prettier@^2.4.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc" integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg== -prettier@^2.8.8: - version "2.8.8" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" - integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== - printj@~1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" @@ -9142,7 +9915,7 @@ promise@^8.0.0: dependencies: asap "~2.0.6" -proper-lockfile@^4.1.1: +proper-lockfile@^4.1.1, proper-lockfile@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== @@ -9178,6 +9951,20 @@ proxy-addr@~2.0.5: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +proxyquire@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" + integrity sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg== + dependencies: + fill-keys "^1.0.2" + module-not-found-error "^1.0.1" + resolve "^1.11.1" + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -9248,7 +10035,7 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.4.0, qs@^6.7.0, qs@^6.9.4: +qs@^6.4.0, qs@^6.9.4: version "6.10.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== @@ -9358,6 +10145,15 @@ readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1, readable-stream@^3.5.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -9429,6 +10225,16 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +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.6" + define-properties "^1.2.1" + es-errors "^1.3.0" + set-function-name "^2.0.1" + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -9550,6 +10356,15 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.20.0: is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@^1.11.1: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^1.17.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" @@ -9697,6 +10512,16 @@ rxjs@^7.2.0: dependencies: tslib "^2.1.0" +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.7" + get-intrinsic "^1.2.4" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -9707,6 +10532,15 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +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.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", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -9803,7 +10637,7 @@ semver@^7.3.8: dependencies: lru-cache "^6.0.0" -semver@^7.5.4: +semver@^7.5.1: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -9870,6 +10704,28 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +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.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.2" + setimmediate@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" @@ -9965,6 +10821,11 @@ signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -9979,6 +10840,23 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" +sinon-chai@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783" + integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g== + +sinon@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-17.0.1.tgz#26b8ef719261bf8df43f925924cccc96748e407a" + integrity sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g== + dependencies: + "@sinonjs/commons" "^3.0.0" + "@sinonjs/fake-timers" "^11.2.2" + "@sinonjs/samsam" "^8.0.0" + diff "^5.1.0" + nise "^5.1.5" + supports-color "^7.2.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -10067,16 +10945,23 @@ solidity-ast@^0.4.38: resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.46.tgz#d0745172dced937741d07464043564e35b147c59" integrity sha512-MlPZQfPhjWXqh7YxWcBGDXaPZIfMYCOHYoLEhGDWulNwEPIQQZuB7mA9eP17CU0jY/bGR4avCEUVVpvHtT2gbA== +solidity-ast@^0.4.51: + 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" + +"solidity-ast@npm:solidity-ast@0.4.45": + version "0.4.45" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.45.tgz#37c1c17bd79123106fc69d94b4a8e9237ae8c625" + integrity sha512-N6uqfaDulVZqjpjru+KvMLjV89M3hesyr/1/t8nkjohRagFSDmDxZvb9viKV98pdwpMzs61Nt2JAApgh0fkL0g== + solidity-comments-extractor@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== -solidity-comments-extractor@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz#f6e148ab0c49f30c1abcbecb8b8df01ed8e879f8" - integrity sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g== - solidity-coverage@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/solidity-coverage/-/solidity-coverage-0.8.2.tgz#bc39604ab7ce0a3fa7767b126b44191830c07813" @@ -10119,11 +11004,6 @@ source-map-support@^0.5.13, source-map-support@^0.5.17, source-map-support@^0.5. buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -10167,7 +11047,7 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== -split-ca@^1.0.0: +split-ca@^1.0.0, split-ca@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= @@ -10177,6 +11057,17 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= +ssh2@^1.11.0, ssh2@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b" + integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw== + dependencies: + asn1 "^0.2.6" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "~0.0.9" + nan "^2.18.0" + sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -10231,6 +11122,15 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -10257,7 +11157,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10266,6 +11166,25 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2 is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +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.7" + define-properties "^1.2.1" + es-abstract "^1.23.0" + es-object-atoms "^1.0.0" + string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -10274,6 +11193,15 @@ string.prototype.trimend@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" +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.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string.prototype.trimstart@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" @@ -10282,6 +11210,15 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" +string.prototype.trimstart@^1.0.8: + 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.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -10301,6 +11238,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -10329,6 +11273,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -10396,7 +11347,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -10509,6 +11460,16 @@ tar-fs@~1.16.3: pump "^1.0.0" tar-stream "^1.1.2" +tar-fs@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + tar-stream@^1.1.2: version "1.6.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" @@ -10522,6 +11483,17 @@ tar-stream@^1.1.2: to-buffer "^1.1.1" xtend "^4.0.0" +tar-stream@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^4.0.2: version "4.4.19" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" @@ -10643,11 +11615,6 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - to-readable-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" @@ -10706,6 +11673,14 @@ ts-morph@^17.0.1: "@ts-morph/common" "~0.18.0" code-block-writer "^11.0.3" +ts-morph@^21.0.1: + version "21.0.1" + resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-21.0.1.tgz#712302a0f6e9dbf1aa8d9cf33a4386c4b18c2006" + integrity sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg== + dependencies: + "@ts-morph/common" "~0.22.0" + code-block-writer "^12.0.0" + ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -10814,7 +11789,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5, type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== @@ -10884,6 +11859,50 @@ typechain@^8.0.0: ts-command-line-args "^2.2.0" ts-essentials "^7.0.1" +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.7" + es-errors "^1.3.0" + is-typed-array "^1.1.13" + +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.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +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.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + +typed-array-length@^1.0.6: + 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.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-proto "^1.0.3" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -10955,6 +11974,16 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + underscore@^1.8.3: version "1.13.3" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.3.tgz#54bc95f7648c5557897e5e968d0f76bc062c34ee" @@ -11704,6 +12733,17 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +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-typed-array@^1.1.2: version "1.1.7" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" @@ -11772,6 +12812,15 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -11798,6 +12847,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -12019,3 +13077,10 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zksync-ethers@^5.0, zksync-ethers@^5.0.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.7.0.tgz#edf465eb564ed60d6a1cc3de5978b8bd8481c230" + integrity sha512-X99c5APICTlRzyXXjfwkEjRzOPp3Jwo62+z2DVGaZbe+b9Apbizcd2UGV4NGomoAR2GXPbeiSqi1cf3Hbo3cQw== + dependencies: + ethers "~5.7.0"