diff --git a/.github/workflows/contracts-tests.yml b/.github/workflows/contracts-tests.yml index 99e18102..d9922911 100644 --- a/.github/workflows/contracts-tests.yml +++ b/.github/workflows/contracts-tests.yml @@ -37,6 +37,11 @@ jobs: env: INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + - name: 'Build the typechain' + run: 'yarn typechain' + env: + INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + - name: 'Test the contracts and generate the coverage report' run: 'yarn coverage >> $GITHUB_STEP_SUMMARY' env: diff --git a/.github/workflows/pull_request_labeler.yml b/.github/workflows/pull_request_labeler.yml index c54c074f..fa73969e 100644 --- a/.github/workflows/pull_request_labeler.yml +++ b/.github/workflows/pull_request_labeler.yml @@ -6,6 +6,9 @@ jobs: triage: runs-on: ubuntu-latest steps: + - name: 'Check out the repo' + uses: 'actions/checkout@v3' + - uses: actions/labeler@v4 with: sync-labels: true diff --git a/contracts/CHANGELOG.md b/contracts/CHANGELOG.md index 186d7748..859189ca 100644 --- a/contracts/CHANGELOG.md +++ b/contracts/CHANGELOG.md @@ -5,15 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## v0.0.1 +## v1.4.0 ### Added - Copied files from [aragon/osx commit e7ba46](https://github.com/aragon/osx/tree/e7ba46026db96931d3e4a585e8f30c585906e1fc) - - interfaces `IMajorityVoting`, `IMembership`, `IProposal`, `IERC20MintableUpgradeable`, `IGovernanceWrappedERC20` - - abstract contracts `MajorityVotingBase`, `Addresslist`,`Proposal`, `ProposalUpgradeable`, `GovernanceERC20`, `GovernanceWrappedERC20`, - - contracts `PlaceholderSetup`, - - libraries `VersionComparisonLib` - - free functions `BitMap`, `Ratio`, and `UncheckedMath` - - test helpers `TestERC1155`, `TestERC20`, `TestERC721`, `TestGovernanceERC20`, `AddresslistMock`, `MajorityVotingMock`, `RatioTest`, `VersionComparisonLibTest` + - interfaces `IDAO`, `IPermissionCondition`, `IPlugin`, `IMembership`, `IProposal`, `IPluginSetup`, `IProtocolVersion`, + - abstract contracts `DaoAuthorizable`, `DaoAuthorizableUpgradeable`, `Plugin`, `PluginCloneable`, `PluginUUPSUpgradeable`, `PermissionCondition`, `PermissionConditionUpgradeable`, `Addresslist`, `Proposal`, `ProposalUpgradeable`, `PluginSetup` + - contracts `CloneFactory` + - libraries `PermissionLib`, `VersionComparisonLib` + - free functions `auth`, `Proxy`, `BitMap`, `Ratio`, `UncheckedMath` diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index a7078264..d2bd8443 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -167,6 +167,7 @@ const config: HardhatUserConfig = { src: './contracts', coinmarketcap: process.env.COINMARKETCAP_API_KEY, }, + networks, paths: { artifacts: './artifacts', diff --git a/contracts/package.json b/contracts/package.json index bf68c297..de2ffde8 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,23 +1,26 @@ { - "name": "@aragon/osx-commons", + "name": "@aragon/osx-commons-contracts", "license": "AGPL-3.0-or-later", "description": "The Aragon OSx contracts package containing common utilities", - "version": "0.0.1", + "version": "1.4.0-alpha.2", "author": { "name": "aragon", "url": "https://github.com/aragon" }, "devDependencies": { + "@aragon/osx-commons-sdk": "0.0.1-alpha.3", "@ethersproject/abi": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", "@nomicfoundation/hardhat-network-helpers": "^1.0.8", "@nomicfoundation/hardhat-toolbox": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.2.3", "@nomiclabs/hardhat-etherscan": "^3.1.7", + "@openzeppelin/hardhat-upgrades": "^1.28.0", "@typechain/ethers-v5": "^10.1.1", "@typechain/hardhat": "^6.1.4", "@types/chai": "^4.3.4", @@ -30,7 +33,6 @@ "hardhat": "^2.13.1", "hardhat-deploy": "^0.11.26", "hardhat-gas-reporter": "^1.0.9", - "ipfs-http-client": "^51.0.0", "mocha": "^10.1.0", "rimraf": "^5.0.5", "solidity-coverage": "^0.8.2", @@ -41,14 +43,11 @@ "typescript": "5.0.4" }, "dependencies": { - "@aragon/osx": "aragon/osx#develop", - "@aragon/osx-ethers": "1.3.0", "@openzeppelin/contracts": "4.9.5", - "@openzeppelin/contracts-upgradeable": "4.9.5", - "@openzeppelin/hardhat-upgrades": "^1.28.0" + "@openzeppelin/contracts-upgradeable": "4.9.5" }, "files": [ - "/src" + "src/{dao,permission,plugin,utils}/**/*.sol" ], "keywords": [ "blockchain", @@ -71,7 +70,6 @@ "lint": "yarn lint:sol && yarn lint:ts", "lint:sol": "cd .. && yarn run lint:contracts:sol", "lint:ts": "cd .. && yarn run lint:contracts:ts", - "postinstall": "DOTENV_CONFIG_PATH=../.env.example yarn typechain", "test": "hardhat test", "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain", "clean": "rimraf ./artifacts ./cache ./coverage ./types ./coverage.json && yarn typechain", diff --git a/contracts/src/dao/IDAO.sol b/contracts/src/dao/IDAO.sol new file mode 100644 index 00000000..ac1329e2 --- /dev/null +++ b/contracts/src/dao/IDAO.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +/// @title IDAO +/// @author Aragon Association - 2022-2023 +/// @notice The interface required for DAOs within the Aragon App DAO framework. +/// @custom:security-contact sirt@aragon.org +interface IDAO { + /// @notice The action struct to be consumed by the DAO's `execute` function resulting in an external call. + /// @param to The address to call. + /// @param value The native token value to be sent with the call. + /// @param data The bytes-encoded function selector and calldata for the call. + struct Action { + address to; + uint256 value; + bytes data; + } + + /// @notice Checks if an address has permission on a contract via a permission identifier and considers if `ANY_ADDRESS` was used in the granting process. + /// @param _where The address of the contract. + /// @param _who The address of a EOA or contract to give the permissions. + /// @param _permissionId The permission identifier. + /// @param _data The optional data passed to the `PermissionCondition` registered. + /// @return Returns true if the address has permission, false if not. + function hasPermission( + address _where, + address _who, + bytes32 _permissionId, + bytes memory _data + ) external view returns (bool); + + /// @notice Updates the DAO metadata (e.g., an IPFS hash). + /// @param _metadata The IPFS hash of the new metadata object. + function setMetadata(bytes calldata _metadata) external; + + /// @notice Emitted when the DAO metadata is updated. + /// @param metadata The IPFS hash of the new metadata object. + event MetadataSet(bytes metadata); + + /// @notice Executes a list of actions. If a zero allow-failure map is provided, a failing action reverts the entire execution. If a non-zero allow-failure map is provided, allowed actions can fail without the entire call being reverted. + /// @param _callId The ID of the call. The definition of the value of `callId` is up to the calling contract and can be used, e.g., as a nonce. + /// @param _actions The array of actions. + /// @param _allowFailureMap A bitmap allowing execution to succeed, even if individual actions might revert. If the bit at index `i` is 1, the execution succeeds even if the `i`th action reverts. A failure map value of 0 requires every action to not revert. + /// @return The array of results obtained from the executed actions in `bytes`. + /// @return The resulting failure map containing the actions have actually failed. + function execute( + bytes32 _callId, + Action[] memory _actions, + uint256 _allowFailureMap + ) external returns (bytes[] memory, uint256); + + /// @notice Emitted when a proposal is executed. + /// @param actor The address of the caller. + /// @param callId The ID of the call. + /// @param actions The array of actions executed. + /// @param allowFailureMap The allow failure map encoding which actions are allowed to fail. + /// @param failureMap The failure map encoding which actions have failed. + /// @param execResults The array with the results of the executed actions. + /// @dev The value of `callId` is defined by the component/contract calling the execute function. A `Plugin` implementation can use it, for example, as a nonce. + event Executed( + address indexed actor, + bytes32 callId, + Action[] actions, + uint256 allowFailureMap, + uint256 failureMap, + bytes[] execResults + ); + + /// @notice Emitted when a standard callback is registered. + /// @param interfaceId The ID of the interface. + /// @param callbackSelector The selector of the callback function. + /// @param magicNumber The magic number to be registered for the callback function selector. + event StandardCallbackRegistered( + bytes4 interfaceId, + bytes4 callbackSelector, + bytes4 magicNumber + ); + + /// @notice Deposits (native) tokens to the DAO contract with a reference string. + /// @param _token The address of the token or address(0) in case of the native token. + /// @param _amount The amount of tokens to deposit. + /// @param _reference The reference describing the deposit reason. + function deposit(address _token, uint256 _amount, string calldata _reference) external payable; + + /// @notice Emitted when a token deposit has been made to the DAO. + /// @param sender The address of the sender. + /// @param token The address of the deposited token. + /// @param amount The amount of tokens deposited. + /// @param _reference The reference describing the deposit reason. + event Deposited( + address indexed sender, + address indexed token, + uint256 amount, + string _reference + ); + + /// @notice Emitted when a native token deposit has been made to the DAO. + /// @dev This event is intended to be emitted in the `receive` function and is therefore bound by the gas limitations for `send`/`transfer` calls introduced by [ERC-2929](https://eips.ethereum.org/EIPS/eip-2929). + /// @param sender The address of the sender. + /// @param amount The amount of native tokens deposited. + event NativeTokenDeposited(address sender, uint256 amount); + + /// @notice Setter for the trusted forwarder verifying the meta transaction. + /// @param _trustedForwarder The trusted forwarder address. + function setTrustedForwarder(address _trustedForwarder) external; + + /// @notice Getter for the trusted forwarder verifying the meta transaction. + /// @return The trusted forwarder address. + function getTrustedForwarder() external view returns (address); + + /// @notice Emitted when a new TrustedForwarder is set on the DAO. + /// @param forwarder the new forwarder address. + event TrustedForwarderSet(address forwarder); + + /// @notice Checks whether a signature is valid for a provided hash according to [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271). + /// @param _hash The hash of the data to be signed. + /// @param _signature The signature byte array associated with `_hash`. + /// @return Returns the `bytes4` magic value `0x1626ba7e` if the signature is valid and `0xffffffff` if not. + function isValidSignature(bytes32 _hash, bytes memory _signature) external returns (bytes4); + + /// @notice Registers an ERC standard having a callback by registering its [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID and callback function signature. + /// @param _interfaceId The ID of the interface. + /// @param _callbackSelector The selector of the callback function. + /// @param _magicNumber The magic number to be registered for the function signature. + function registerStandardCallback( + bytes4 _interfaceId, + bytes4 _callbackSelector, + bytes4 _magicNumber + ) external; + + /// @notice Removed function being left here to not corrupt the IDAO interface ID. Any call will revert. + /// @dev Introduced in v1.0.0. Removed in v1.4.0. + function setSignatureValidator(address) external; +} diff --git a/contracts/src/governance/majority-voting/IMajorityVoting.sol b/contracts/src/governance/majority-voting/IMajorityVoting.sol deleted file mode 100644 index 678870af..00000000 --- a/contracts/src/governance/majority-voting/IMajorityVoting.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -/// @title IMajorityVoting -/// @author Aragon Association - 2022-2023 -/// @notice The interface of majority voting plugin. -/// @custom:security-contact sirt@aragon.org -interface IMajorityVoting { - /// @notice Vote options that a voter can chose from. - /// @param None The default option state of a voter indicating the absence from the vote. This option neither influences support nor participation. - /// @param Abstain This option does not influence the support but counts towards participation. - /// @param Yes This option increases the support and counts towards participation. - /// @param No This option decreases the support and counts towards participation. - enum VoteOption { - None, - Abstain, - Yes, - No - } - - /// @notice Emitted when a vote is cast by a voter. - /// @param proposalId The ID of the proposal. - /// @param voter The voter casting the vote. - /// @param voteOption The casted vote option. - /// @param votingPower The voting power behind this vote. - event VoteCast( - uint256 indexed proposalId, - address indexed voter, - VoteOption voteOption, - uint256 votingPower - ); - - /// @notice Returns the support threshold parameter stored in the voting settings. - /// @return The support threshold parameter. - function supportThreshold() external view returns (uint32); - - /// @notice Returns the minimum participation parameter stored in the voting settings. - /// @return The minimum participation parameter. - function minParticipation() external view returns (uint32); - - /// @notice Checks if the support value defined as $$\texttt{support} = \frac{N_\text{yes}}{N_\text{yes}+N_\text{no}}$$ for a proposal vote is greater than the support threshold. - /// @param _proposalId The ID of the proposal. - /// @return Returns `true` if the support is greater than the support threshold and `false` otherwise. - function isSupportThresholdReached(uint256 _proposalId) external view returns (bool); - - /// @notice Checks if the worst-case support value defined as $$\texttt{worstCaseSupport} = \frac{N_\text{yes}}{ N_\text{total}-N_\text{abstain}}$$ for a proposal vote is greater than the support threshold. - /// @param _proposalId The ID of the proposal. - /// @return Returns `true` if the worst-case support is greater than the support threshold and `false` otherwise. - function isSupportThresholdReachedEarly(uint256 _proposalId) external view returns (bool); - - /// @notice Checks if the participation value defined as $$\texttt{participation} = \frac{N_\text{yes}+N_\text{no}+N_\text{abstain}}{N_\text{total}}$$ for a proposal vote is greater or equal than the minimum participation value. - /// @param _proposalId The ID of the proposal. - /// @return Returns `true` if the participation is greater than the minimum participation and `false` otherwise. - function isMinParticipationReached(uint256 _proposalId) external view returns (bool); - - /// @notice Checks if an account can participate on a proposal vote. This can be because the vote - /// - has not started, - /// - has ended, - /// - was executed, or - /// - the voter doesn't have voting powers. - /// @param _proposalId The proposal Id. - /// @param _account The account address to be checked. - /// @param _voteOption Whether the voter abstains, supports or opposes the proposal. - /// @return Returns true if the account is allowed to vote. - /// @dev The function assumes the queried proposal exists. - function canVote( - uint256 _proposalId, - address _account, - VoteOption _voteOption - ) external view returns (bool); - - /// @notice Checks if a proposal can be executed. - /// @param _proposalId The ID of the proposal to be checked. - /// @return True if the proposal can be executed, false otherwise. - function canExecute(uint256 _proposalId) external view returns (bool); - - /// @notice Votes for a vote option and, optionally, executes the proposal. - /// @dev `_voteOption`, 1 -> abstain, 2 -> yes, 3 -> no - /// @param _proposalId The ID of the proposal. - /// @param _voteOption The chosen vote option. - /// @param _tryEarlyExecution If `true`, early execution is tried after the vote cast. The call does not revert if early execution is not possible. - function vote(uint256 _proposalId, VoteOption _voteOption, bool _tryEarlyExecution) external; - - /// @notice Executes a proposal. - /// @param _proposalId The ID of the proposal to be executed. - function execute(uint256 _proposalId) external; - - /// @notice Returns whether the account has voted for the proposal. Note, that this does not check if the account has voting power. - /// @param _proposalId The ID of the proposal. - /// @param _account The account address to be checked. - /// @return The vote option cast by a voter for a certain proposal. - function getVoteOption( - uint256 _proposalId, - address _account - ) external view returns (VoteOption); -} diff --git a/contracts/src/governance/majority-voting/MajorityVotingBase.sol b/contracts/src/governance/majority-voting/MajorityVotingBase.sol deleted file mode 100644 index 22126016..00000000 --- a/contracts/src/governance/majority-voting/MajorityVotingBase.sol +++ /dev/null @@ -1,605 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {SafeCastUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; - -import {IDAO} from "@aragon/osx/packages/contracts/src/core/dao/IDAO.sol"; -import {PluginUUPSUpgradeable} from "@aragon/osx/packages/contracts/src/core/plugin/PluginUUPSUpgradeable.sol"; - -import {ProposalUpgradeable} from "../proposal//ProposalUpgradeable.sol"; -import {RATIO_BASE, RatioOutOfBounds} from "../../utils/math/Ratio.sol"; -import {IMajorityVoting} from "./IMajorityVoting.sol"; - -/// @title MajorityVotingBase -/// @author Aragon Association - 2022-2023 -/// @notice The abstract implementation of majority voting plugins. -/// -/// ### Parameterization -/// -/// We define two parameters -/// -/// $$\texttt{support} = \frac{N_\text{yes}}{N_\text{yes} + N_\text{no}} \in [0,1]$$ -/// -/// and -/// -/// $$\texttt{participation} = \frac{N_\text{yes} + N_\text{no} + N_\text{abstain}}{N_\text{total}} \in [0,1],$$ -/// -/// where $N_\text{yes}$, $N_\text{no}$, and $N_\text{abstain}$ are the yes, no, and abstain votes that have been cast and $N_\text{total}$ is the total voting power available at proposal creation time. -/// -/// #### Limit Values: Support Threshold & Minimum Participation -/// -/// Two limit values are associated with these parameters and decide if proposal execution is possible: $\texttt{supportThreshold} \in [0,1]$ and $\texttt{minParticipation} \in [0,1]$. -/// -/// For threshold values, $>$ comparison is used. This **does not** include the threshold value. E.g., for $\texttt{supportThreshold} = 50\%$, the criterion is fulfilled if there is at least one more yes than no votes ($N_\text{yes} = N_\text{no} + 1$). -/// For minimum values, $\ge{}$ comparison is used. This **does** include the minimum participation value. E.g., for $\texttt{minParticipation} = 40\%$ and $N_\text{total} = 10$, the criterion is fulfilled if 4 out of 10 votes were casted. -/// -/// Majority voting implies that the support threshold is set with -/// -/// $$\texttt{supportThreshold} \ge 50\% .$$ -/// -/// However, this is not enforced by the contract code and developers can make unsafe parameters and only the frontend will warn about bad parameter settings. -/// -/// ### Execution Criteria -/// -/// After the vote is closed, two criteria decide if the proposal passes. -/// -/// #### The Support Criterion -/// -/// For a proposal to pass, the required ratio of yes and no votes must be met: -/// -/// $$(1- \texttt{supportThreshold}) \cdot N_\text{yes} > \texttt{supportThreshold} \cdot N_\text{no}.$$ -/// -/// Note, that the inequality yields the simple majority voting condition for $\texttt{supportThreshold}=\frac{1}{2}$. -/// -/// #### The Participation Criterion -/// -/// For a proposal to pass, the minimum voting power must have been cast: -/// -/// $$N_\text{yes} + N_\text{no} + N_\text{abstain} \ge \texttt{minVotingPower},$$ -/// -/// where $\texttt{minVotingPower} = \texttt{minParticipation} \cdot N_\text{total}$. -/// -/// ### Vote Replacement Execution -/// -/// The contract allows votes to be replaced. Voters can vote multiple times and only the latest vote option is tallied. -/// -/// ### Early Execution -/// -/// This contract allows a proposal to be executed early, iff the vote outcome cannot change anymore by more people voting. Accordingly, vote replacement and early execution are /// mutually exclusive options. -/// The outcome cannot change anymore iff the support threshold is met even if all remaining votes are no votes. We call this number the worst-case number of no votes and define it as -/// -/// $$N_\text{no, worst-case} = N_\text{no, worst-case} + \texttt{remainingVotes}$$ -/// -/// where -/// -/// $$\texttt{remainingVotes} = N_\text{total}-\underbrace{(N_\text{yes}+N_\text{no}+N_\text{abstain})}_{\text{turnout}}.$$ -/// -/// We can use this quantity to calculate the worst-case support that would be obtained if all remaining votes are casted with no: -/// -/// $$\begin{align*} -/// \texttt{worstCaseSupport} -/// &= \frac{N_\text{yes}}{N_\text{yes} + (N_\text{no, worst-case})} \\[3mm] -/// &= \frac{N_\text{yes}}{N_\text{yes} + (N_\text{no} + \texttt{remainingVotes})} \\[3mm] -/// &= \frac{N_\text{yes}}{N_\text{yes} + N_\text{no} + N_\text{total} - (N_\text{yes} + N_\text{no} + N_\text{abstain})} \\[3mm] -/// &= \frac{N_\text{yes}}{N_\text{total} - N_\text{abstain}} -/// \end{align*}$$ -/// -/// In analogy, we can modify [the support criterion](#the-support-criterion) from above to allow for early execution: -/// -/// $$\begin{align*} -/// (1 - \texttt{supportThreshold}) \cdot N_\text{yes} -/// &> \texttt{supportThreshold} \cdot N_\text{no, worst-case} \\[3mm] -/// &> \texttt{supportThreshold} \cdot (N_\text{no} + \texttt{remainingVotes}) \\[3mm] -/// &> \texttt{supportThreshold} \cdot (N_\text{no} + N_\text{total}-(N_\text{yes}+N_\text{no}+N_\text{abstain})) \\[3mm] -/// &> \texttt{supportThreshold} \cdot (N_\text{total} - N_\text{yes} - N_\text{abstain}) -/// \end{align*} $$ -/// -/// Accordingly, early execution is possible when the vote is open, the modified support criterion, and the particicpation criterion are met. -/// @dev This contract implements the `IMajorityVoting` interface. -/// @custom:security-contact sirt@aragon.org -abstract contract MajorityVotingBase is - IMajorityVoting, - Initializable, - ERC165Upgradeable, - PluginUUPSUpgradeable, - ProposalUpgradeable -{ - using SafeCastUpgradeable for uint256; - - /// @notice The different voting modes available. - /// @param Standard In standard mode, early execution and vote replacement are disabled. - /// @param EarlyExecution In early execution mode, a proposal can be executed early before the end date if the vote outcome cannot mathematically change by more voters voting. - /// @param VoteReplacement In vote replacement mode, voters can change their vote multiple times and only the latest vote option is tallied. - enum VotingMode { - Standard, - EarlyExecution, - VoteReplacement - } - - /// @notice A container for the majority voting settings that will be applied as parameters on proposal creation. - /// @param votingMode A parameter to select the vote mode. In standard mode (0), early execution and vote replacement are disabled. In early execution mode (1), a proposal can be executed early before the end date if the vote outcome cannot mathematically change by more voters voting. In vote replacement mode (2), voters can change their vote multiple times and only the latest vote option is tallied. - /// @param supportThreshold The support threshold value. Its value has to be in the interval [0, 10^6] defined by `RATIO_BASE = 10**6`. - /// @param minParticipation The minimum participation value. Its value has to be in the interval [0, 10^6] defined by `RATIO_BASE = 10**6`. - /// @param minDuration The minimum duration of the proposal vote in seconds. - /// @param minProposerVotingPower The minimum voting power required to create a proposal. - struct VotingSettings { - VotingMode votingMode; - uint32 supportThreshold; - uint32 minParticipation; - uint64 minDuration; - uint256 minProposerVotingPower; - } - - /// @notice A container for proposal-related information. - /// @param executed Whether the proposal is executed or not. - /// @param parameters The proposal parameters at the time of the proposal creation. - /// @param tally The vote tally of the proposal. - /// @param voters The votes casted by the voters. - /// @param actions The actions to be executed when the proposal passes. - /// @param allowFailureMap A bitmap allowing the proposal to succeed, even if individual actions might revert. If the bit at index `i` is 1, the proposal succeeds even if the `i`th action reverts. A failure map value of 0 requires every action to not revert. - struct Proposal { - bool executed; - ProposalParameters parameters; - Tally tally; - mapping(address => IMajorityVoting.VoteOption) voters; - IDAO.Action[] actions; - uint256 allowFailureMap; - } - - /// @notice A container for the proposal parameters at the time of proposal creation. - /// @param votingMode A parameter to select the vote mode. - /// @param supportThreshold The support threshold value. The value has to be in the interval [0, 10^6] defined by `RATIO_BASE = 10**6`. - /// @param startDate The start date of the proposal vote. - /// @param endDate The end date of the proposal vote. - /// @param snapshotBlock The number of the block prior to the proposal creation. - /// @param minVotingPower The minimum voting power needed. - struct ProposalParameters { - VotingMode votingMode; - uint32 supportThreshold; - uint64 startDate; - uint64 endDate; - uint64 snapshotBlock; - uint256 minVotingPower; - } - - /// @notice A container for the proposal vote tally. - /// @param abstain The number of abstain votes casted. - /// @param yes The number of yes votes casted. - /// @param no The number of no votes casted. - struct Tally { - uint256 abstain; - uint256 yes; - uint256 no; - } - - /// @notice The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract. - bytes4 internal constant MAJORITY_VOTING_BASE_INTERFACE_ID = - this.minDuration.selector ^ - this.minProposerVotingPower.selector ^ - this.votingMode.selector ^ - this.totalVotingPower.selector ^ - this.getProposal.selector ^ - this.updateVotingSettings.selector ^ - this.createProposal.selector; - - /// @notice The ID of the permission required to call the `updateVotingSettings` function. - bytes32 public constant UPDATE_VOTING_SETTINGS_PERMISSION_ID = - keccak256("UPDATE_VOTING_SETTINGS_PERMISSION"); - - /// @notice A mapping between proposal IDs and proposal information. - // solhint-disable-next-line named-parameters-mapping - mapping(uint256 => Proposal) internal proposals; - - /// @notice The struct storing the voting settings. - VotingSettings private votingSettings; - - /// @notice Thrown if a date is out of bounds. - /// @param limit The limit value. - /// @param actual The actual value. - error DateOutOfBounds(uint64 limit, uint64 actual); - - /// @notice Thrown if the minimal duration value is out of bounds (less than one hour or greater than 1 year). - /// @param limit The limit value. - /// @param actual The actual value. - error MinDurationOutOfBounds(uint64 limit, uint64 actual); - - /// @notice Thrown when a sender is not allowed to create a proposal. - /// @param sender The sender address. - error ProposalCreationForbidden(address sender); - - /// @notice Thrown if an account is not allowed to cast a vote. This can be because the vote - /// - has not started, - /// - has ended, - /// - was executed, or - /// - the account doesn't have voting powers. - /// @param proposalId The ID of the proposal. - /// @param account The address of the _account. - /// @param voteOption The chosen vote option. - error VoteCastForbidden(uint256 proposalId, address account, VoteOption voteOption); - - /// @notice Thrown if the proposal execution is forbidden. - /// @param proposalId The ID of the proposal. - error ProposalExecutionForbidden(uint256 proposalId); - - /// @notice Emitted when the voting settings are updated. - /// @param votingMode A parameter to select the vote mode. - /// @param supportThreshold The support threshold value. - /// @param minParticipation The minimum participation value. - /// @param minDuration The minimum duration of the proposal vote in seconds. - /// @param minProposerVotingPower The minimum voting power required to create a proposal. - event VotingSettingsUpdated( - VotingMode votingMode, - uint32 supportThreshold, - uint32 minParticipation, - uint64 minDuration, - uint256 minProposerVotingPower - ); - - /// @notice Initializes the component to be used by inheriting contracts. - /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). - /// @param _dao The IDAO interface of the associated DAO. - /// @param _votingSettings The voting settings. - // solhint-disable-next-line func-name-mixedcase - function __MajorityVotingBase_init( - IDAO _dao, - VotingSettings calldata _votingSettings - ) internal onlyInitializing { - __PluginUUPSUpgradeable_init(_dao); - _updateVotingSettings(_votingSettings); - } - - /// @notice Checks if this or the parent contract supports an interface by its ID. - /// @param _interfaceId The ID of the interface. - /// @return Returns `true` if the interface is supported. - function supportsInterface( - bytes4 _interfaceId - ) - public - view - virtual - override(ERC165Upgradeable, PluginUUPSUpgradeable, ProposalUpgradeable) - returns (bool) - { - return - _interfaceId == MAJORITY_VOTING_BASE_INTERFACE_ID || - _interfaceId == type(IMajorityVoting).interfaceId || - super.supportsInterface(_interfaceId); - } - - /// @inheritdoc IMajorityVoting - function vote( - uint256 _proposalId, - VoteOption _voteOption, - bool _tryEarlyExecution - ) public virtual { - address account = _msgSender(); - - if (!_canVote(_proposalId, account, _voteOption)) { - revert VoteCastForbidden({ - proposalId: _proposalId, - account: account, - voteOption: _voteOption - }); - } - _vote(_proposalId, _voteOption, account, _tryEarlyExecution); - } - - /// @inheritdoc IMajorityVoting - function execute(uint256 _proposalId) public virtual { - if (!_canExecute(_proposalId)) { - revert ProposalExecutionForbidden(_proposalId); - } - _execute(_proposalId); - } - - /// @inheritdoc IMajorityVoting - function getVoteOption( - uint256 _proposalId, - address _voter - ) public view virtual returns (VoteOption) { - return proposals[_proposalId].voters[_voter]; - } - - /// @inheritdoc IMajorityVoting - function canVote( - uint256 _proposalId, - address _voter, - VoteOption _voteOption - ) public view virtual returns (bool) { - return _canVote(_proposalId, _voter, _voteOption); - } - - /// @inheritdoc IMajorityVoting - function canExecute(uint256 _proposalId) public view virtual returns (bool) { - return _canExecute(_proposalId); - } - - /// @inheritdoc IMajorityVoting - function isSupportThresholdReached(uint256 _proposalId) public view virtual returns (bool) { - Proposal storage proposal_ = proposals[_proposalId]; - - // The code below implements the formula of the support criterion explained in the top of this file. - // `(1 - supportThreshold) * N_yes > supportThreshold * N_no` - return - (RATIO_BASE - proposal_.parameters.supportThreshold) * proposal_.tally.yes > - proposal_.parameters.supportThreshold * proposal_.tally.no; - } - - /// @inheritdoc IMajorityVoting - function isSupportThresholdReachedEarly( - uint256 _proposalId - ) public view virtual returns (bool) { - Proposal storage proposal_ = proposals[_proposalId]; - - uint256 noVotesWorstCase = totalVotingPower(proposal_.parameters.snapshotBlock) - - proposal_.tally.yes - - proposal_.tally.abstain; - - // The code below implements the formula of the early execution support criterion explained in the top of this file. - // `(1 - supportThreshold) * N_yes > supportThreshold * N_no,worst-case` - return - (RATIO_BASE - proposal_.parameters.supportThreshold) * proposal_.tally.yes > - proposal_.parameters.supportThreshold * noVotesWorstCase; - } - - /// @inheritdoc IMajorityVoting - function isMinParticipationReached(uint256 _proposalId) public view virtual returns (bool) { - Proposal storage proposal_ = proposals[_proposalId]; - - // The code below implements the formula of the participation criterion explained in the top of this file. - // `N_yes + N_no + N_abstain >= minVotingPower = minParticipation * N_total` - return - proposal_.tally.yes + proposal_.tally.no + proposal_.tally.abstain >= - proposal_.parameters.minVotingPower; - } - - /// @inheritdoc IMajorityVoting - function supportThreshold() public view virtual returns (uint32) { - return votingSettings.supportThreshold; - } - - /// @inheritdoc IMajorityVoting - function minParticipation() public view virtual returns (uint32) { - return votingSettings.minParticipation; - } - - /// @notice Returns the minimum duration parameter stored in the voting settings. - /// @return The minimum duration parameter. - function minDuration() public view virtual returns (uint64) { - return votingSettings.minDuration; - } - - /// @notice Returns the minimum voting power required to create a proposal stored in the voting settings. - /// @return The minimum voting power required to create a proposal. - function minProposerVotingPower() public view virtual returns (uint256) { - return votingSettings.minProposerVotingPower; - } - - /// @notice Returns the vote mode stored in the voting settings. - /// @return The vote mode parameter. - function votingMode() public view virtual returns (VotingMode) { - return votingSettings.votingMode; - } - - /// @notice Returns the total voting power checkpointed for a specific block number. - /// @param _blockNumber The block number. - /// @return The total voting power. - function totalVotingPower(uint256 _blockNumber) public view virtual returns (uint256); - - /// @notice Returns all information for a proposal vote by its ID. - /// @param _proposalId The ID of the proposal. - /// @return open Whether the proposal is open or not. - /// @return executed Whether the proposal is executed or not. - /// @return parameters The parameters of the proposal vote. - /// @return tally The current tally of the proposal vote. - /// @return actions The actions to be executed in the associated DAO after the proposal has passed. - /// @return allowFailureMap The bit map representations of which actions are allowed to revert so tx still succeeds. - function getProposal( - uint256 _proposalId - ) - public - view - virtual - returns ( - bool open, - bool executed, - ProposalParameters memory parameters, - Tally memory tally, - IDAO.Action[] memory actions, - uint256 allowFailureMap - ) - { - Proposal storage proposal_ = proposals[_proposalId]; - - open = _isProposalOpen(proposal_); - executed = proposal_.executed; - parameters = proposal_.parameters; - tally = proposal_.tally; - actions = proposal_.actions; - allowFailureMap = proposal_.allowFailureMap; - } - - /// @notice Updates the voting settings. - /// @param _votingSettings The new voting settings. - function updateVotingSettings( - VotingSettings calldata _votingSettings - ) external virtual auth(UPDATE_VOTING_SETTINGS_PERMISSION_ID) { - _updateVotingSettings(_votingSettings); - } - - /// @notice Creates a new majority voting proposal. - /// @param _metadata The metadata of the proposal. - /// @param _actions The actions that will be executed after the proposal passes. - /// @param _allowFailureMap Allows proposal to succeed even if an action reverts. Uses bitmap representation. If the bit at index `x` is 1, the tx succeeds even if the action at `x` failed. Passing 0 will be treated as atomic execution. - /// @param _startDate The start date of the proposal vote. If 0, the current timestamp is used and the vote starts immediately. - /// @param _endDate The end date of the proposal vote. If 0, `_startDate + minDuration` is used. - /// @param _voteOption The chosen vote option to be casted on proposal creation. - /// @param _tryEarlyExecution If `true`, early execution is tried after the vote cast. The call does not revert if early execution is not possible. - /// @return proposalId The ID of the proposal. - function createProposal( - bytes calldata _metadata, - IDAO.Action[] calldata _actions, - uint256 _allowFailureMap, - uint64 _startDate, - uint64 _endDate, - VoteOption _voteOption, - bool _tryEarlyExecution - ) external virtual returns (uint256 proposalId); - - /// @notice Internal function to cast a vote. It assumes the queried vote exists. - /// @param _proposalId The ID of the proposal. - /// @param _voteOption The chosen vote option to be casted on the proposal vote. - /// @param _tryEarlyExecution If `true`, early execution is tried after the vote cast. The call does not revert if early execution is not possible. - function _vote( - uint256 _proposalId, - VoteOption _voteOption, - address _voter, - bool _tryEarlyExecution - ) internal virtual; - - /// @notice Internal function to execute a vote. It assumes the queried proposal exists. - /// @param _proposalId The ID of the proposal. - function _execute(uint256 _proposalId) internal virtual { - proposals[_proposalId].executed = true; - - _executeProposal( - dao(), - _proposalId, - proposals[_proposalId].actions, - proposals[_proposalId].allowFailureMap - ); - } - - /// @notice Internal function to check if a voter can vote. It assumes the queried proposal exists. - /// @param _proposalId The ID of the proposal. - /// @param _voter The address of the voter to check. - /// @param _voteOption Whether the voter abstains, supports or opposes the proposal. - /// @return Returns `true` if the given voter can vote on a certain proposal and `false` otherwise. - function _canVote( - uint256 _proposalId, - address _voter, - VoteOption _voteOption - ) internal view virtual returns (bool); - - /// @notice Internal function to check if a proposal can be executed. It assumes the queried proposal exists. - /// @param _proposalId The ID of the proposal. - /// @return True if the proposal can be executed, false otherwise. - /// @dev Threshold and minimal values are compared with `>` and `>=` comparators, respectively. - function _canExecute(uint256 _proposalId) internal view virtual returns (bool) { - Proposal storage proposal_ = proposals[_proposalId]; - - // Verify that the vote has not been executed already. - if (proposal_.executed) { - return false; - } - - if (_isProposalOpen(proposal_)) { - // Early execution - if (proposal_.parameters.votingMode != VotingMode.EarlyExecution) { - return false; - } - if (!isSupportThresholdReachedEarly(_proposalId)) { - return false; - } - } else { - // Normal execution - if (!isSupportThresholdReached(_proposalId)) { - return false; - } - } - if (!isMinParticipationReached(_proposalId)) { - return false; - } - - return true; - } - - /// @notice Internal function to check if a proposal vote is still open. - /// @param proposal_ The proposal struct. - /// @return True if the proposal vote is open, false otherwise. - function _isProposalOpen(Proposal storage proposal_) internal view virtual returns (bool) { - // solhint-disable-next-line not-rely-on-time - uint64 currentTime = block.timestamp.toUint64(); - - return - proposal_.parameters.startDate <= currentTime && - currentTime < proposal_.parameters.endDate && - !proposal_.executed; - } - - /// @notice Internal function to update the plugin-wide proposal vote settings. - /// @param _votingSettings The voting settings to be validated and updated. - function _updateVotingSettings(VotingSettings calldata _votingSettings) internal virtual { - // Require the support threshold value to be in the interval [0, 10^6-1], because `>` comparision is used in the support criterion and >100% could never be reached. - if (_votingSettings.supportThreshold > RATIO_BASE - 1) { - revert RatioOutOfBounds({ - limit: RATIO_BASE - 1, - actual: _votingSettings.supportThreshold - }); - } - - // Require the minimum participation value to be in the interval [0, 10^6], because `>=` comparision is used in the participation criterion. - if (_votingSettings.minParticipation > RATIO_BASE) { - revert RatioOutOfBounds({limit: RATIO_BASE, actual: _votingSettings.minParticipation}); - } - - if (_votingSettings.minDuration < 60 minutes) { - revert MinDurationOutOfBounds({limit: 60 minutes, actual: _votingSettings.minDuration}); - } - - if (_votingSettings.minDuration > 365 days) { - revert MinDurationOutOfBounds({limit: 365 days, actual: _votingSettings.minDuration}); - } - - votingSettings = _votingSettings; - - emit VotingSettingsUpdated({ - votingMode: _votingSettings.votingMode, - supportThreshold: _votingSettings.supportThreshold, - minParticipation: _votingSettings.minParticipation, - minDuration: _votingSettings.minDuration, - minProposerVotingPower: _votingSettings.minProposerVotingPower - }); - } - - /// @notice Validates and returns the proposal vote dates. - /// @param _start The start date of the proposal vote. If 0, the current timestamp is used and the vote starts immediately. - /// @param _end The end date of the proposal vote. If 0, `_start + minDuration` is used. - /// @return startDate The validated start date of the proposal vote. - /// @return endDate The validated end date of the proposal vote. - function _validateProposalDates( - uint64 _start, - uint64 _end - ) internal view virtual returns (uint64 startDate, uint64 endDate) { - // solhint-disable-next-line not-rely-on-time - uint64 currentTimestamp = block.timestamp.toUint64(); - - if (_start == 0) { - startDate = currentTimestamp; - } else { - startDate = _start; - - if (startDate < currentTimestamp) { - revert DateOutOfBounds({limit: currentTimestamp, actual: startDate}); - } - } - - uint64 earliestEndDate = startDate + votingSettings.minDuration; // Since `minDuration` is limited to 1 year, `startDate + minDuration` can only overflow if the `startDate` is after `type(uint64).max - minDuration`. In this case, the proposal creation will revert and another date can be picked. - - if (_end == 0) { - endDate = earliestEndDate; - } else { - endDate = _end; - - if (endDate < earliestEndDate) { - revert DateOutOfBounds({limit: earliestEndDate, actual: endDate}); - } - } - } - - /// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)). - uint256[47] private __gap; -} diff --git a/contracts/src/governance/token/erc20/GovernanceERC20.sol b/contracts/src/governance/token/erc20/GovernanceERC20.sol deleted file mode 100644 index a68dc174..00000000 --- a/contracts/src/governance/token/erc20/GovernanceERC20.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {IERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol"; -import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import {ERC20VotesUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; -import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; - -import {DaoAuthorizableUpgradeable} from "@aragon/osx/packages/contracts/src/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; -import {IDAO} from "@aragon/osx/packages/contracts/src/core/dao/IDAO.sol"; - -import {IERC20MintableUpgradeable} from "./IERC20MintableUpgradeable.sol"; - -/// @title GovernanceERC20 -/// @author Aragon Association -/// @notice An [OpenZeppelin `Votes`](https://docs.openzeppelin.com/contracts/4.x/api/governance#Votes) compatible [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token that can be used for voting and is managed by a DAO. -/// @custom:security-contact sirt@aragon.org -contract GovernanceERC20 is - IERC20MintableUpgradeable, - Initializable, - ERC165Upgradeable, - ERC20VotesUpgradeable, - DaoAuthorizableUpgradeable -{ - /// @notice The permission identifier to mint new tokens - bytes32 public constant MINT_PERMISSION_ID = keccak256("MINT_PERMISSION"); - - /// @notice The settings for the initial mint of the token. - /// @param receivers The receivers of the tokens. - /// @param amounts The amounts of tokens to be minted for each receiver. - /// @dev The lengths of `receivers` and `amounts` must match. - struct MintSettings { - address[] receivers; - uint256[] amounts; - } - - /// @notice Thrown if the number of receivers and amounts specified in the mint settings do not match. - /// @param receiversArrayLength The length of the `receivers` array. - /// @param amountsArrayLength The length of the `amounts` array. - error MintSettingsArrayLengthMismatch(uint256 receiversArrayLength, uint256 amountsArrayLength); - - /// @notice Calls the initialize function. - /// @param _dao The managing DAO. - /// @param _name The name of the [ERC-20](https://eips.ethereum.org/EIPS/eip-20) governance token. - /// @param _symbol The symbol of the [ERC-20](https://eips.ethereum.org/EIPS/eip-20) governance token. - /// @param _mintSettings The token mint settings struct containing the `receivers` and `amounts`. - constructor( - IDAO _dao, - string memory _name, - string memory _symbol, - MintSettings memory _mintSettings - ) { - initialize(_dao, _name, _symbol, _mintSettings); - } - - /// @notice Initializes the contract and mints tokens to a list of receivers. - /// @param _dao The managing DAO. - /// @param _name The name of the [ERC-20](https://eips.ethereum.org/EIPS/eip-20) governance token. - /// @param _symbol The symbol of the [ERC-20](https://eips.ethereum.org/EIPS/eip-20) governance token. - /// @param _mintSettings The token mint settings struct containing the `receivers` and `amounts`. - function initialize( - IDAO _dao, - string memory _name, - string memory _symbol, - MintSettings memory _mintSettings - ) public initializer { - // Check mint settings - if (_mintSettings.receivers.length != _mintSettings.amounts.length) { - revert MintSettingsArrayLengthMismatch({ - receiversArrayLength: _mintSettings.receivers.length, - amountsArrayLength: _mintSettings.amounts.length - }); - } - - __ERC20_init(_name, _symbol); - __ERC20Permit_init(_name); - __DaoAuthorizableUpgradeable_init(_dao); - - for (uint256 i; i < _mintSettings.receivers.length; ) { - _mint(_mintSettings.receivers[i], _mintSettings.amounts[i]); - - unchecked { - ++i; - } - } - } - - /// @notice Checks if this or the parent contract supports an interface by its ID. - /// @param _interfaceId The ID of the interface. - /// @return Returns `true` if the interface is supported. - function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - return - _interfaceId == type(IERC20Upgradeable).interfaceId || - _interfaceId == type(IERC20PermitUpgradeable).interfaceId || - _interfaceId == type(IERC20MetadataUpgradeable).interfaceId || - _interfaceId == type(IVotesUpgradeable).interfaceId || - _interfaceId == type(IERC20MintableUpgradeable).interfaceId || - super.supportsInterface(_interfaceId); - } - - /// @notice Mints tokens to an address. - /// @param to The address receiving the tokens. - /// @param amount The amount of tokens to be minted. - function mint(address to, uint256 amount) external override auth(MINT_PERMISSION_ID) { - _mint(to, amount); - } - - // https://forum.openzeppelin.com/t/self-delegation-in-erc20votes/17501/12?u=novaknole - /// @inheritdoc ERC20VotesUpgradeable - function _afterTokenTransfer(address from, address to, uint256 amount) internal override { - super._afterTokenTransfer(from, to, amount); - - // Automatically turn on delegation on mint/transfer but only for the first time. - if (to != address(0) && numCheckpoints(to) == 0 && delegates(to) == address(0)) { - _delegate(to, to); - } - } -} diff --git a/contracts/src/governance/token/erc20/GovernanceWrappedERC20.sol b/contracts/src/governance/token/erc20/GovernanceWrappedERC20.sol deleted file mode 100644 index bc4cbd0b..00000000 --- a/contracts/src/governance/token/erc20/GovernanceWrappedERC20.sol +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import {ERC20WrapperUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20WrapperUpgradeable.sol"; -import {IVotesUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/utils/IVotesUpgradeable.sol"; -import {IERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol"; -import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; -import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import {ERC20VotesUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; - -import {IGovernanceWrappedERC20} from "./IGovernanceWrappedERC20.sol"; - -/// @title GovernanceWrappedERC20 -/// @author Aragon Association -/// @notice Wraps an existing [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token by inheriting from `ERC20WrapperUpgradeable` and allows to use it for voting by inheriting from `ERC20VotesUpgradeable`. The latter is compatible with [OpenZeppelin's `Votes`](https://docs.openzeppelin.com/contracts/4.x/api/governance#Votes) interface. -/// The contract also supports meta transactions. To use an `amount` of underlying tokens for voting, the token owner has to -/// 1. call `approve` for the tokens to be used by this contract -/// 2. call `depositFor` to wrap them, which safely transfers the underlying [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens to the contract and mints wrapped [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens. -/// To get the [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens back, the owner of the wrapped tokens can call `withdrawFor`, which burns the wrapped [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens and safely transfers the underlying tokens back to the owner. -/// @dev This contract intentionally has no public mint functionality because this is the responsibility of the underlying [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token contract. -/// @custom:security-contact sirt@aragon.org -contract GovernanceWrappedERC20 is - IGovernanceWrappedERC20, - Initializable, - ERC165Upgradeable, - ERC20VotesUpgradeable, - ERC20WrapperUpgradeable -{ - /// @notice Calls the initialize function. - /// @param _token The underlying [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token. - /// @param _name The name of the wrapped token. - /// @param _symbol The symbol of the wrapped token. - constructor(IERC20Upgradeable _token, string memory _name, string memory _symbol) { - initialize(_token, _name, _symbol); - } - - /// @notice Initializes the contract. - /// @param _token The underlying [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token. - /// @param _name The name of the wrapped token. - /// @param _symbol The symbol of the wrapped token. - function initialize( - IERC20Upgradeable _token, - string memory _name, - string memory _symbol - ) public initializer { - __ERC20_init(_name, _symbol); - __ERC20Permit_init(_name); - __ERC20Wrapper_init(_token); - } - - /// @notice Checks if this or the parent contract supports an interface by its ID. - /// @param _interfaceId The ID of the interface. - /// @return Returns `true` if the interface is supported. - function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { - return - _interfaceId == type(IGovernanceWrappedERC20).interfaceId || - _interfaceId == type(IERC20Upgradeable).interfaceId || - _interfaceId == type(IERC20PermitUpgradeable).interfaceId || - _interfaceId == type(IERC20MetadataUpgradeable).interfaceId || - _interfaceId == type(IVotesUpgradeable).interfaceId || - super.supportsInterface(_interfaceId); - } - - /// @inheritdoc ERC20WrapperUpgradeable - /// @dev Uses the `decimals` of the underlying [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token. - function decimals() - public - view - override(ERC20Upgradeable, ERC20WrapperUpgradeable) - returns (uint8) - { - return ERC20WrapperUpgradeable.decimals(); - } - - /// @inheritdoc IGovernanceWrappedERC20 - function depositFor( - address account, - uint256 amount - ) public override(IGovernanceWrappedERC20, ERC20WrapperUpgradeable) returns (bool) { - return ERC20WrapperUpgradeable.depositFor(account, amount); - } - - /// @inheritdoc IGovernanceWrappedERC20 - function withdrawTo( - address account, - uint256 amount - ) public override(IGovernanceWrappedERC20, ERC20WrapperUpgradeable) returns (bool) { - return ERC20WrapperUpgradeable.withdrawTo(account, amount); - } - - // https://forum.openzeppelin.com/t/self-delegation-in-erc20votes/17501/12?u=novaknole - /// @inheritdoc ERC20VotesUpgradeable - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal override(ERC20VotesUpgradeable, ERC20Upgradeable) { - super._afterTokenTransfer(from, to, amount); - - // Automatically turn on delegation on mint/transfer but only for the first time. - if (to != address(0) && numCheckpoints(to) == 0 && delegates(to) == address(0)) { - _delegate(to, to); - } - } - - /// @inheritdoc ERC20VotesUpgradeable - function _mint( - address to, - uint256 amount - ) internal override(ERC20VotesUpgradeable, ERC20Upgradeable) { - super._mint(to, amount); - } - - /// @inheritdoc ERC20VotesUpgradeable - function _burn( - address account, - uint256 amount - ) internal override(ERC20VotesUpgradeable, ERC20Upgradeable) { - super._burn(account, amount); - } -} diff --git a/contracts/src/governance/token/erc20/IERC20MintableUpgradeable.sol b/contracts/src/governance/token/erc20/IERC20MintableUpgradeable.sol deleted file mode 100644 index f7d59ba0..00000000 --- a/contracts/src/governance/token/erc20/IERC20MintableUpgradeable.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -/// @title IERC20MintableUpgradeable -/// @notice Interface to allow minting of [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens. -/// @custom:security-contact sirt@aragon.org -interface IERC20MintableUpgradeable { - /// @notice Mints [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens for a receiving address. - /// @param _to The receiving address. - /// @param _amount The amount of tokens. - function mint(address _to, uint256 _amount) external; -} diff --git a/contracts/src/governance/token/erc20/IGovernanceWrappedERC20.sol b/contracts/src/governance/token/erc20/IGovernanceWrappedERC20.sol deleted file mode 100644 index 3f987c4d..00000000 --- a/contracts/src/governance/token/erc20/IGovernanceWrappedERC20.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -/// @title IGovernanceWrappedERC20 -/// @author Aragon Association -/// @notice An interface for the token wrapping contract wrapping existing [ERC-20](https://eips.ethereum.org/EIPS/eip-20) tokens. -/// @custom:security-contact sirt@aragon.org -interface IGovernanceWrappedERC20 { - /// @notice Deposits an amount of underlying token and mints the corresponding number of wrapped tokens for a receiving address. - /// @param account The address receiving the minted, wrapped tokens. - /// @param amount The amount of tokens to deposit. - function depositFor(address account, uint256 amount) external returns (bool); - - /// @notice Withdraws an amount of underlying tokens to a receiving address and burns the corresponding number of wrapped tokens. - /// @param account The address receiving the withdrawn, underlying tokens. - /// @param amount The amount of underlying tokens to withdraw. - function withdrawTo(address account, uint256 amount) external returns (bool); -} diff --git a/contracts/src/mocks/permission/PermissionConditionMock.sol b/contracts/src/mocks/permission/PermissionConditionMock.sol new file mode 100644 index 00000000..9ee057ae --- /dev/null +++ b/contracts/src/mocks/permission/PermissionConditionMock.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {PermissionCondition} from "../../permission/condition/PermissionCondition.sol"; + +/// @notice A mock permission condition that can be set to permit or deny every call. +/// @dev DO NOT USE IN PRODUCTION! +contract PermissionConditionMock is PermissionCondition { + bool public answer; + + constructor() { + answer = true; + } + + function setAnswer(bool _answer) external { + answer = _answer; + } + + function isGranted( + address _where, + address _who, + bytes32 _permissionId, + bytes memory _data + ) external view returns (bool) { + (_where, _who, _permissionId, _data); + return answer; + } +} diff --git a/contracts/src/mocks/plugin/PluginCloneableMock.sol b/contracts/src/mocks/plugin/PluginCloneableMock.sol new file mode 100644 index 00000000..965c894c --- /dev/null +++ b/contracts/src/mocks/plugin/PluginCloneableMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* solhint-disable one-contract-per-file */ +pragma solidity ^0.8.8; + +import {PluginCloneable} from "../../plugin/PluginCloneable.sol"; +import {IDAO} from "../../dao/IDAO.sol"; + +contract PluginCloneableMockBuild1 is PluginCloneable { + uint256 public state1; + + function initialize(IDAO _dao) external initializer { + __PluginCloneable_init(_dao); + state1 = 1; + } +} + +contract PluginCloneableMockBuild2 is PluginCloneable { + uint256 public state1; + uint256 public state2; + + function initialize(IDAO _dao) external initializer { + __PluginCloneable_init(_dao); + state1 = 1; + state2 = 2; + } +} diff --git a/contracts/src/mocks/plugin/PluginCloneableSetupMock.sol b/contracts/src/mocks/plugin/PluginCloneableSetupMock.sol new file mode 100644 index 00000000..e3d20326 --- /dev/null +++ b/contracts/src/mocks/plugin/PluginCloneableSetupMock.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* solhint-disable one-contract-per-file */ +pragma solidity ^0.8.8; + +import {PermissionLib} from "../../permission/PermissionLib.sol"; +import {IPluginSetup} from "../../plugin/setup/IPluginSetup.sol"; +import {PluginSetup} from "../../plugin/setup/PluginSetup.sol"; +import {IDAO} from "../../dao/IDAO.sol"; +import {mockPermissions, mockHelpers} from "./PluginSetupMockData.sol"; +import {PluginCloneableMockBuild1, PluginCloneableMockBuild2} from "./PluginCloneableMock.sol"; + +contract PluginCloneableSetupMockBuild1 is PluginSetup { + address internal pluginBase; + + constructor() { + pluginBase = address(new PluginCloneableMockBuild1()); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes memory + ) external override returns (address plugin, PreparedSetupData memory preparedSetupData) { + bytes memory initData = abi.encodeCall(PluginCloneableMockBuild1.initialize, (IDAO(_dao))); + plugin = createERC1967Proxy(pluginBase, initData); // TODO createClone(pluginBase, initData); is missing! See task OS-794 and OS-675. + preparedSetupData.helpers = mockHelpers(1); + preparedSetupData.permissions = mockPermissions(0, 1, PermissionLib.Operation.Grant); + } + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external pure override returns (PermissionLib.MultiTargetPermission[] memory permissions) { + (_dao, _payload); + permissions = mockPermissions(0, 1, PermissionLib.Operation.Revoke); + } + + /// @inheritdoc IPluginSetup + function implementation() external view override returns (address) { + return address(pluginBase); + } +} + +contract PluginCloneableSetupMockBuild2 is PluginSetup { + address internal pluginBase; + + constructor() { + pluginBase = address(new PluginCloneableMockBuild2()); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes memory + ) external override returns (address plugin, PreparedSetupData memory preparedSetupData) { + bytes memory initData = abi.encodeCall(PluginCloneableMockBuild2.initialize, (IDAO(_dao))); + plugin = createERC1967Proxy(pluginBase, initData); // TODO createClone(pluginBase, initData); is missing! See task OS-794 and OS-675. + preparedSetupData.helpers = mockHelpers(2); + preparedSetupData.permissions = mockPermissions(0, 2, PermissionLib.Operation.Grant); + } + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external pure override returns (PermissionLib.MultiTargetPermission[] memory permissions) { + (_dao, _payload); + permissions = mockPermissions(0, 2, PermissionLib.Operation.Revoke); + } + + /// @inheritdoc IPluginSetup + function implementation() external view override returns (address) { + return address(pluginBase); + } +} diff --git a/contracts/src/mocks/plugin/PluginMock.sol b/contracts/src/mocks/plugin/PluginMock.sol new file mode 100644 index 00000000..123a0166 --- /dev/null +++ b/contracts/src/mocks/plugin/PluginMock.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {Plugin} from "../../plugin/Plugin.sol"; +import {IDAO} from "../../dao/IDAO.sol"; + +contract PluginMockBuild1 is Plugin { + uint256 public state1; + + constructor(IDAO _dao) Plugin(_dao) { + state1 = 1; + } +} diff --git a/contracts/src/mocks/plugin/PluginSetupMock.sol b/contracts/src/mocks/plugin/PluginSetupMock.sol new file mode 100644 index 00000000..8a095814 --- /dev/null +++ b/contracts/src/mocks/plugin/PluginSetupMock.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* solhint-disable one-contract-per-file */ +pragma solidity ^0.8.8; + +import {PermissionLib} from "../../permission/PermissionLib.sol"; +import {IPluginSetup} from "../../plugin/setup/IPluginSetup.sol"; +import {PluginSetup} from "../../plugin/setup/PluginSetup.sol"; +import {IDAO} from "../../dao/IDAO.sol"; +import {mockPermissions, mockHelpers} from "./PluginSetupMockData.sol"; +import {PluginMockBuild1} from "./PluginMock.sol"; + +contract PluginSetupMockBuild1 is PluginSetup { + address internal pluginBase; + + constructor() { + pluginBase = address(new PluginMockBuild1(IDAO(address(0)))); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes memory + ) external override returns (address plugin, PreparedSetupData memory preparedSetupData) { + plugin = address(new PluginMockBuild1(IDAO(_dao))); + preparedSetupData.helpers = mockHelpers(1); + preparedSetupData.permissions = mockPermissions(0, 1, PermissionLib.Operation.Grant); + } + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external pure override returns (PermissionLib.MultiTargetPermission[] memory permissions) { + (_dao, _payload); + permissions = mockPermissions(0, 1, PermissionLib.Operation.Revoke); + } + + /// @inheritdoc IPluginSetup + function implementation() external view override returns (address) { + return address(pluginBase); + } +} diff --git a/contracts/src/mocks/plugin/PluginSetupMockData.sol b/contracts/src/mocks/plugin/PluginSetupMockData.sol new file mode 100644 index 00000000..a169c014 --- /dev/null +++ b/contracts/src/mocks/plugin/PluginSetupMockData.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {PermissionLib} from "../../permission/PermissionLib.sol"; + +address constant NO_CONDITION = address(0); + +error ConflictingValues(); + +/// @notice Creates a mock `MultiTargetPermission` array by converting a range of `uint160` values into `address` values. +/// @param rangeStart The start of the range. +/// @param rangeEnd The end of the range (that is not included). +/// @param op The permission operation type. +/// @return permissions The mock array of permissions. +function mockPermissions( + uint160 rangeStart, + uint160 rangeEnd, + PermissionLib.Operation op +) pure returns (PermissionLib.MultiTargetPermission[] memory permissions) { + if (rangeStart > rangeEnd) revert ConflictingValues(); + + permissions = new PermissionLib.MultiTargetPermission[](rangeEnd - rangeStart); + + for (uint160 i = rangeStart; i < rangeEnd; i++) { + permissions[i - rangeStart] = PermissionLib.MultiTargetPermission({ + operation: op, + where: address(i), + who: address(i), + condition: PermissionLib.NO_CONDITION, + permissionId: keccak256("MOCK_PERMISSION") + }); + } +} + +/// @notice Creates a mock array of helper addresses of specified length by converting `uint160` values starting from 0 into `address` values. +/// @param len The length of the helper array. +/// @return helpers The mock array of helper addresses. +function mockHelpers(uint160 len) pure returns (address[] memory helpers) { + helpers = new address[](len); + + for (uint160 i = 0; i < len; i++) { + helpers[i] = address(i); + } +} diff --git a/contracts/src/mocks/plugin/PluginUUPSUpgradeableMock.sol b/contracts/src/mocks/plugin/PluginUUPSUpgradeableMock.sol new file mode 100644 index 00000000..2ad0e42e --- /dev/null +++ b/contracts/src/mocks/plugin/PluginUUPSUpgradeableMock.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* solhint-disable one-contract-per-file */ +pragma solidity ^0.8.8; + +import {PluginUUPSUpgradeable} from "../../plugin/PluginUUPSUpgradeable.sol"; +import {IDAO} from "../../dao/IDAO.sol"; + +contract PluginUUPSUpgradeableMockBuild1 is PluginUUPSUpgradeable { + uint256 public state1; + + function initialize(IDAO _dao) external initializer { + __PluginUUPSUpgradeable_init(_dao); + state1 = 1; + } +} + +contract PluginUUPSUpgradeableMockBuild2 is PluginUUPSUpgradeable { + uint256 public state1; + uint256 public state2; + + function initialize(IDAO _dao) external reinitializer(2) { + __PluginUUPSUpgradeable_init(_dao); + state1 = 1; + state2 = 2; + } + + function initializeFrom(uint16 _previousBuild) external reinitializer(2) { + if (_previousBuild < 2) { + state2 = 2; + } + } +} + +contract PluginUUPSUpgradeableMockBuild3 is PluginUUPSUpgradeable { + uint256 public state1; + uint256 public state2; + uint256 public state3; + + function initialize(IDAO _dao) external reinitializer(3) { + __PluginUUPSUpgradeable_init(_dao); + state1 = 1; + state2 = 2; + state3 = 3; + } + + function initializeFrom(uint16 _previousBuild) external reinitializer(3) { + if (_previousBuild < 2) { + state2 = 2; + } + if (_previousBuild < 3) { + state2 = 3; + } + } +} diff --git a/contracts/src/mocks/plugin/PluginUUPSUpgradeableSetupMock.sol b/contracts/src/mocks/plugin/PluginUUPSUpgradeableSetupMock.sol new file mode 100644 index 00000000..a5fbe227 --- /dev/null +++ b/contracts/src/mocks/plugin/PluginUUPSUpgradeableSetupMock.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* solhint-disable one-contract-per-file */ +pragma solidity ^0.8.8; + +import {PermissionLib} from "../../permission/PermissionLib.sol"; +import {IPluginSetup} from "../../plugin/setup/IPluginSetup.sol"; +import {PluginSetup} from "../../plugin/setup/PluginSetup.sol"; +import {IDAO} from "../../dao/IDAO.sol"; +import {mockPermissions, mockHelpers} from "./PluginSetupMockData.sol"; +import {PluginUUPSUpgradeableMockBuild1, PluginUUPSUpgradeableMockBuild2, PluginUUPSUpgradeableMockBuild3} from "./PluginUUPSUpgradeableMock.sol"; + +contract PluginUUPSUpgradeableSetupMockBuild1 is PluginSetup { + address internal pluginBase; + + constructor() { + pluginBase = address(new PluginUUPSUpgradeableMockBuild1()); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes memory + ) public virtual override returns (address plugin, PreparedSetupData memory preparedSetupData) { + bytes memory initData = abi.encodeCall( + PluginUUPSUpgradeableMockBuild1.initialize, + (IDAO(_dao)) + ); + plugin = createERC1967Proxy(pluginBase, initData); + preparedSetupData.helpers = mockHelpers(1); + preparedSetupData.permissions = mockPermissions(0, 1, PermissionLib.Operation.Grant); + } + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external virtual override returns (PermissionLib.MultiTargetPermission[] memory permissions) { + (_dao, _payload); + permissions = mockPermissions(0, 1, PermissionLib.Operation.Revoke); + } + + /// @inheritdoc IPluginSetup + function implementation() external view virtual override returns (address) { + return address(pluginBase); + } +} + +/// @dev DO NOT USE IN PRODUCTION! +contract PluginUUPSUpgradeableSetupMockBuild2 is PluginSetup { + address internal pluginBase; + + constructor() { + pluginBase = address(new PluginUUPSUpgradeableMockBuild2()); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes memory + ) external override returns (address plugin, PreparedSetupData memory preparedSetupData) { + bytes memory initData = abi.encodeCall( + PluginUUPSUpgradeableMockBuild2.initialize, + (IDAO(_dao)) + ); + plugin = createERC1967Proxy(pluginBase, initData); + preparedSetupData.helpers = mockHelpers(2); + preparedSetupData.permissions = mockPermissions(0, 2, PermissionLib.Operation.Grant); + } + + /// @inheritdoc IPluginSetup + function prepareUpdate( + address _dao, + uint16 _currentBuild, + SetupPayload calldata _payload + ) + external + pure + override + returns (bytes memory initData, PreparedSetupData memory preparedSetupData) + { + (_dao, _payload); + + // Update from Build 1 + if (_currentBuild == 1) { + preparedSetupData.helpers = mockHelpers(2); + initData = abi.encodeCall( + PluginUUPSUpgradeableMockBuild2.initializeFrom, + (_currentBuild) + ); + preparedSetupData.permissions = mockPermissions(1, 2, PermissionLib.Operation.Grant); + } + } + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external pure override returns (PermissionLib.MultiTargetPermission[] memory permissions) { + (_dao, _payload); + permissions = mockPermissions(0, 2, PermissionLib.Operation.Revoke); + } + + /// @inheritdoc IPluginSetup + function implementation() external view override returns (address) { + return address(pluginBase); + } +} + +contract PluginUUPSUpgradeableSetupMockBuild3 is PluginSetup { + address internal pluginBase; + + constructor() { + pluginBase = address(new PluginUUPSUpgradeableMockBuild3()); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes memory + ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { + bytes memory initData = abi.encodeCall( + PluginUUPSUpgradeableMockBuild3.initialize, + (IDAO(_dao)) + ); + plugin = createERC1967Proxy(pluginBase, initData); + preparedSetupData.helpers = mockHelpers(3); + preparedSetupData.permissions = mockPermissions(0, 3, PermissionLib.Operation.Grant); + } + + /// @inheritdoc IPluginSetup + function prepareUpdate( + address _dao, + uint16 _currentBuild, + SetupPayload calldata _payload + ) + external + pure + override + returns (bytes memory initData, PreparedSetupData memory preparedSetupData) + { + (_dao, _payload); + + // Update from Build 1 + if (_currentBuild == 1) { + preparedSetupData.helpers = mockHelpers(3); + initData = abi.encodeCall( + PluginUUPSUpgradeableMockBuild3.initializeFrom, + (_currentBuild) + ); + preparedSetupData.permissions = mockPermissions(1, 3, PermissionLib.Operation.Grant); + } + + // Update from Build 2 + if (_currentBuild == 2) { + preparedSetupData.helpers = mockHelpers(3); + initData = abi.encodeCall( + PluginUUPSUpgradeableMockBuild3.initializeFrom, + (_currentBuild) + ); + preparedSetupData.permissions = mockPermissions(2, 3, PermissionLib.Operation.Grant); + } + } + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external virtual override returns (PermissionLib.MultiTargetPermission[] memory permissions) { + (_dao, _payload); + permissions = mockPermissions(0, 3, PermissionLib.Operation.Revoke); + } + + /// @inheritdoc IPluginSetup + function implementation() external view override returns (address) { + return address(pluginBase); + } +} diff --git a/contracts/src/test/governance/AddresslistMock.sol b/contracts/src/mocks/plugin/extensions/governance/AddresslistMock.sol similarity index 61% rename from contracts/src/test/governance/AddresslistMock.sol rename to contracts/src/mocks/plugin/extensions/governance/AddresslistMock.sol index 30da3f6b..b0a2e72d 100644 --- a/contracts/src/test/governance/AddresslistMock.sol +++ b/contracts/src/mocks/plugin/extensions/governance/AddresslistMock.sol @@ -2,8 +2,10 @@ pragma solidity ^0.8.8; -import {Addresslist} from "../../governance/membership/Addresslist.sol"; +import {Addresslist} from "../../../../plugin/extensions/governance/Addresslist.sol"; +/// @notice A mock addresslist that everyone can add and remove addresses to and from, respectively. +/// @dev DO NOT USE IN PRODUCTION! contract AddresslistMock is Addresslist { function addAddresses(address[] calldata _newAddresses) external { _addAddresses(_newAddresses); diff --git a/contracts/src/test/token/TestERC1155.sol b/contracts/src/mocks/token/ERC1155Mock.sol similarity index 69% rename from contracts/src/test/token/TestERC1155.sol rename to contracts/src/mocks/token/ERC1155Mock.sol index d264e53d..14deafbe 100644 --- a/contracts/src/test/token/TestERC1155.sol +++ b/contracts/src/mocks/token/ERC1155Mock.sol @@ -4,11 +4,9 @@ pragma solidity ^0.8.8; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -/// @title TestERC1155 -/// @author Aragon Association - 2022-2023 -/// @notice A test [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) that can be minted and burned by everyone. +/// @notice A mock [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) token that can be minted and burned by everyone. /// @dev DO NOT USE IN PRODUCTION! -contract TestERC1155 is ERC1155 { +contract ERC1155Mock is ERC1155 { constructor(string memory _uri) ERC1155(_uri) {} function mint(address account, uint256 tokenId, uint256 amount) public { diff --git a/contracts/src/test/token/TestERC20.sol b/contracts/src/mocks/token/ERC20Mock.sol similarity index 80% rename from contracts/src/test/token/TestERC20.sol rename to contracts/src/mocks/token/ERC20Mock.sol index b0fb1922..b191adf3 100644 --- a/contracts/src/test/token/TestERC20.sol +++ b/contracts/src/mocks/token/ERC20Mock.sol @@ -4,11 +4,9 @@ pragma solidity ^0.8.8; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -/// @title TestERC20 -/// @author Aragon Association - 2022-2023 -/// @notice A test [ERC-20](https://eips.ethereum.org/EIPS/eip-20) that can be minted and burned by everyone. +/// @notice A mock [ERC-20](https://eips.ethereum.org/EIPS/eip-20) token that can be minted and burned by everyone. /// @dev DO NOT USE IN PRODUCTION! -contract TestERC20 is ERC20 { +contract ERC20Mock is ERC20 { uint8 public decimals_ = 18; constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} diff --git a/contracts/src/test/token/TestERC721.sol b/contracts/src/mocks/token/ERC721Mock.sol similarity index 67% rename from contracts/src/test/token/TestERC721.sol rename to contracts/src/mocks/token/ERC721Mock.sol index c6abe88b..1ca2b505 100644 --- a/contracts/src/test/token/TestERC721.sol +++ b/contracts/src/mocks/token/ERC721Mock.sol @@ -4,11 +4,9 @@ pragma solidity ^0.8.8; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -/// @title TestERC721 -/// @author Aragon Association - 2022-2023 -/// @notice A test [ERC-721](https://eips.ethereum.org/EIPS/eip-721) that can be minted and burned by everyone. +/// @notice A mock [ERC-721](https://eips.ethereum.org/EIPS/eip-721) token that can be minted and burned by everyone. /// @dev DO NOT USE IN PRODUCTION! -contract TestERC721 is ERC721 { +contract ERC721Mock is ERC721 { constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} function mint(address account, uint256 tokenId) public { diff --git a/contracts/src/mocks/utils/ProtocolVersionMock.sol b/contracts/src/mocks/utils/ProtocolVersionMock.sol new file mode 100644 index 00000000..de3d2393 --- /dev/null +++ b/contracts/src/mocks/utils/ProtocolVersionMock.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ProtocolVersion} from "../../utils/versioning/ProtocolVersion.sol"; + +/// @title ProtocolVersionMock +// solhint-disable-next-line no-empty-blocks +contract ProtocolVersionMock is ProtocolVersion { + +} diff --git a/contracts/src/test/utils/RatioTest.sol b/contracts/src/mocks/utils/RatioMock.sol similarity index 94% rename from contracts/src/test/utils/RatioTest.sol rename to contracts/src/mocks/utils/RatioMock.sol index b039e85f..6f2b6ec9 100644 --- a/contracts/src/test/utils/RatioTest.sol +++ b/contracts/src/mocks/utils/RatioMock.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.8; import {RATIO_BASE, _applyRatioCeiled} from "../../utils/math/Ratio.sol"; -contract RatioTest { +contract RatioMock { function getRatioBase() public pure returns (uint256) { return RATIO_BASE; } diff --git a/contracts/src/test/utils/VersionComparisonLibTest.sol b/contracts/src/mocks/utils/VersionComparisonLibMock.sol similarity index 87% rename from contracts/src/test/utils/VersionComparisonLibTest.sol rename to contracts/src/mocks/utils/VersionComparisonLibMock.sol index ed4da5a0..320dfb25 100644 --- a/contracts/src/test/utils/VersionComparisonLibTest.sol +++ b/contracts/src/mocks/utils/VersionComparisonLibMock.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.8; -import {VersionComparisonLib} from "../../utils/VersionComparisonLib.sol"; +import {VersionComparisonLib} from "../../utils/versioning/VersionComparisonLib.sol"; -contract VersionComparisonLibTest { +contract VersionComparisonLibMock { using VersionComparisonLib for uint8[3]; function eq(uint8[3] memory lhs, uint8[3] memory rhs) public pure returns (bool) { diff --git a/contracts/src/permission/PermissionLib.sol b/contracts/src/permission/PermissionLib.sol new file mode 100644 index 00000000..f44383a9 --- /dev/null +++ b/contracts/src/permission/PermissionLib.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +/// @title PermissionLib +/// @author Aragon Association - 2021-2023 +/// @notice A library containing objects for permission processing. +/// @custom:security-contact sirt@aragon.org +library PermissionLib { + /// @notice A constant expressing that no condition is applied to a permission. + address public constant NO_CONDITION = address(0); + + /// @notice The types of permission operations available in the `PermissionManager`. + /// @param Grant The grant operation setting a permission without a condition. + /// @param Revoke The revoke operation removing a permission (that was granted with or without a condition). + /// @param GrantWithCondition The grant operation setting a permission with a condition. + enum Operation { + Grant, + Revoke, + GrantWithCondition + } + + /// @notice A struct containing the information for a permission to be applied on a single target contract without a condition. + /// @param operation The permission operation type. + /// @param who The address (EOA or contract) receiving the permission. + /// @param permissionId The permission identifier. + struct SingleTargetPermission { + Operation operation; + address who; + bytes32 permissionId; + } + + /// @notice A struct containing the information for a permission to be applied on multiple target contracts, optionally, with a condition. + /// @param operation The permission operation type. + /// @param where The address of the target contract for which `who` receives permission. + /// @param who The address (EOA or contract) receiving the permission. + /// @param condition The `PermissionCondition` that will be asked for authorization on calls connected to the specified permission identifier. + /// @param permissionId The permission identifier. + struct MultiTargetPermission { + Operation operation; + address where; + address who; + address condition; + bytes32 permissionId; + } +} diff --git a/contracts/src/permission/auth/DaoAuthorizable.sol b/contracts/src/permission/auth/DaoAuthorizable.sol new file mode 100644 index 00000000..29ca524a --- /dev/null +++ b/contracts/src/permission/auth/DaoAuthorizable.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; + +import {IDAO} from "../../dao/IDAO.sol"; +import {_auth} from "./auth.sol"; + +/// @title DaoAuthorizable +/// @author Aragon Association - 2022-2023 +/// @notice An abstract contract providing a meta-transaction compatible modifier for non-upgradeable contracts instantiated via the `new` keyword to authorize function calls through an associated DAO. +/// @custom:security-contact sirt@aragon.org +abstract contract DaoAuthorizable is Context { + /// @notice The associated DAO managing the permissions of inheriting contracts. + IDAO private immutable DAO; + + /// @notice Constructs the contract by setting the associated DAO. + /// @param _dao The associated DAO address. + constructor(IDAO _dao) { + DAO = _dao; + } + + /// @notice Returns the DAO contract. + /// @return The DAO contract. + function dao() public view returns (IDAO) { + return DAO; + } + + /// @notice A modifier to make functions on inheriting contracts authorized. Permissions to call the function are checked through the associated DAO's permission manager. + /// @param _permissionId The permission identifier required to call the method this modifier is applied to. + modifier auth(bytes32 _permissionId) { + _auth(DAO, address(this), _msgSender(), _permissionId, _msgData()); + _; + } +} diff --git a/contracts/src/permission/auth/DaoAuthorizableUpgradeable.sol b/contracts/src/permission/auth/DaoAuthorizableUpgradeable.sol new file mode 100644 index 00000000..0462188a --- /dev/null +++ b/contracts/src/permission/auth/DaoAuthorizableUpgradeable.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; + +import {IDAO} from "../../dao/IDAO.sol"; +import {_auth} from "./auth.sol"; + +/// @title DaoAuthorizableUpgradeable +/// @author Aragon Association - 2022-2023 +/// @notice An abstract contract providing a meta-transaction compatible modifier for upgradeable or cloneable contracts to authorize function calls through an associated DAO. +/// @dev Make sure to call `__DaoAuthorizableUpgradeable_init` during initialization of the inheriting contract. +/// @custom:security-contact sirt@aragon.org +abstract contract DaoAuthorizableUpgradeable is ContextUpgradeable { + /// @notice The associated DAO managing the permissions of inheriting contracts. + IDAO private dao_; + + /// @notice Initializes the contract by setting the associated DAO. + /// @param _dao The associated DAO address. + // solhint-disable-next-line func-name-mixedcase + function __DaoAuthorizableUpgradeable_init(IDAO _dao) internal onlyInitializing { + dao_ = _dao; + } + + /// @notice Returns the DAO contract. + /// @return The DAO contract. + function dao() public view returns (IDAO) { + return dao_; + } + + /// @notice A modifier to make functions on inheriting contracts authorized. Permissions to call the function are checked through the associated DAO's permission manager. + /// @param _permissionId The permission identifier required to call the method this modifier is applied to. + modifier auth(bytes32 _permissionId) { + _auth(dao_, address(this), _msgSender(), _permissionId, _msgData()); + _; + } + + /// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)). + uint256[49] private __gap; +} diff --git a/contracts/src/permission/auth/auth.sol b/contracts/src/permission/auth/auth.sol new file mode 100644 index 00000000..744e3c45 --- /dev/null +++ b/contracts/src/permission/auth/auth.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {IDAO} from "../../dao/IDAO.sol"; + +/// @notice Thrown if a call is unauthorized in the associated DAO. +/// @param dao The associated DAO. +/// @param where The context in which the authorization reverted. +/// @param who The address (EOA or contract) missing the permission. +/// @param permissionId The permission identifier. +error DaoUnauthorized(address dao, address where, address who, bytes32 permissionId); + +/// @notice A free function checking if a caller is granted permissions on a target contract via a permission identifier that redirects the approval to a `PermissionCondition` if this was specified in the setup. +/// @param _where The address of the target contract for which `who` receives permission. +/// @param _who The address (EOA or contract) owning the permission. +/// @param _permissionId The permission identifier. +/// @param _data The optional data passed to the `PermissionCondition` registered. +/// @custom:security-contact sirt@aragon.org +function _auth( + IDAO _dao, + address _where, + address _who, + bytes32 _permissionId, + bytes calldata _data +) view { + if (!_dao.hasPermission(_where, _who, _permissionId, _data)) + revert DaoUnauthorized({ + dao: address(_dao), + where: _where, + who: _who, + permissionId: _permissionId + }); +} diff --git a/contracts/src/permission/condition/IPermissionCondition.sol b/contracts/src/permission/condition/IPermissionCondition.sol new file mode 100644 index 00000000..70498ac3 --- /dev/null +++ b/contracts/src/permission/condition/IPermissionCondition.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +/// @title IPermissionCondition +/// @author Aragon Association - 2021-2023 +/// @notice An interface to be implemented to support custom permission logic. +/// @dev To attach a condition to a permission, the `grantWithCondition` function must be used and refer to the implementing contract's address with the `condition` argument. +/// @custom:security-contact sirt@aragon.org +interface IPermissionCondition { + /// @notice Checks if a call is permitted. + /// @param _where The address of the target contract. + /// @param _who The address (EOA or contract) for which the permissions are checked. + /// @param _permissionId The permission identifier. + /// @param _data Optional data passed to the `PermissionCondition` implementation. + /// @return isPermitted Returns true if the call is permitted. + function isGranted( + address _where, + address _who, + bytes32 _permissionId, + bytes calldata _data + ) external view returns (bool isPermitted); +} diff --git a/contracts/src/permission/condition/PermissionCondition.sol b/contracts/src/permission/condition/PermissionCondition.sol new file mode 100644 index 00000000..00fd9be9 --- /dev/null +++ b/contracts/src/permission/condition/PermissionCondition.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +import {IProtocolVersion} from "../../utils/versioning/IProtocolVersion.sol"; +import {ProtocolVersion} from "../../utils/versioning/ProtocolVersion.sol"; +import {IPermissionCondition} from "./IPermissionCondition.sol"; + +/// @title PermissionCondition +/// @author Aragon Association - 2023 +/// @notice An abstract contract for non-upgradeable contracts instantiated via the `new` keyword to inherit from to support customary permissions depending on arbitrary on-chain state. +/// @custom:security-contact sirt@aragon.org +abstract contract PermissionCondition is ERC165, IPermissionCondition, ProtocolVersion { + /// @notice Checks if an interface is supported by this or its parent contract. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return + _interfaceId == type(IPermissionCondition).interfaceId || + _interfaceId == type(IProtocolVersion).interfaceId || + super.supportsInterface(_interfaceId); + } +} diff --git a/contracts/src/permission/condition/PermissionConditionUpgradeable.sol b/contracts/src/permission/condition/PermissionConditionUpgradeable.sol new file mode 100644 index 00000000..3ca8ea0f --- /dev/null +++ b/contracts/src/permission/condition/PermissionConditionUpgradeable.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; + +import {IProtocolVersion} from "../../utils/versioning/IProtocolVersion.sol"; +import {ProtocolVersion} from "../../utils/versioning/ProtocolVersion.sol"; +import {IPermissionCondition} from "./IPermissionCondition.sol"; + +/// @title PermissionConditionUpgradeable +/// @author Aragon Association - 2023 +/// @notice An abstract contract for upgradeable or cloneable contracts to inherit from and to support customary permissions depending on arbitrary on-chain state. +/// @custom:security-contact sirt@aragon.org +abstract contract PermissionConditionUpgradeable is + ERC165Upgradeable, + IPermissionCondition, + ProtocolVersion +{ + /// @notice Checks if an interface is supported by this or its parent contract. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return + _interfaceId == type(IPermissionCondition).interfaceId || + _interfaceId == type(IProtocolVersion).interfaceId || + super.supportsInterface(_interfaceId); + } +} diff --git a/contracts/src/plugin/IPlugin.sol b/contracts/src/plugin/IPlugin.sol new file mode 100644 index 00000000..87e51952 --- /dev/null +++ b/contracts/src/plugin/IPlugin.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +/// @title IPlugin +/// @author Aragon Association - 2022-2023 +/// @notice An interface defining the traits of a plugin. +/// @custom:security-contact sirt@aragon.org +interface IPlugin { + enum PluginType { + UUPS, + Cloneable, + Constructable + } + + /// @notice Returns the plugin's type + function pluginType() external view returns (PluginType); +} diff --git a/contracts/src/plugin/PlaceholderSetup.sol b/contracts/src/plugin/PlaceholderSetup.sol deleted file mode 100644 index b5e2e29c..00000000 --- a/contracts/src/plugin/PlaceholderSetup.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {PermissionLib} from "@aragon/osx/packages/contracts/src/core/permission/PermissionLib.sol"; -import {PluginSetup, IPluginSetup} from "@aragon/osx/packages/contracts/src/framework/plugin/setup/PluginSetup.sol"; - -/// @title PlaceholderSetup -/// @author Aragon Association - 2023 -/// @notice A placeholder setup contract for outdated plugin builds. When moving plugin repos to new chains or layers, where only the latest release and build should be available, this placeholder can be used to populate previous builds. -/// @custom:security-contact sirt@aragon.org -contract PlaceholderSetup is PluginSetup { - /// @notice Thrown if the dummy is used. - error PlaceholderSetupCannotBeUsed(); - - /// @inheritdoc IPluginSetup - function prepareInstallation( - address /*_dao*/, - bytes calldata /*_data*/ - ) external pure returns (address /*plugin*/, PreparedSetupData memory /*preparedSetupData*/) { - revert PlaceholderSetupCannotBeUsed(); - } - - /// @inheritdoc IPluginSetup - function prepareUninstallation( - address /*_dao*/, - SetupPayload calldata /*_payload*/ - ) external pure returns (PermissionLib.MultiTargetPermission[] memory /*permissions*/) { - revert PlaceholderSetupCannotBeUsed(); - } - - /// @inheritdoc IPluginSetup - function implementation() external pure returns (address) { - return address(0); - } -} diff --git a/contracts/src/plugin/Plugin.sol b/contracts/src/plugin/Plugin.sol new file mode 100644 index 00000000..5d4a2b28 --- /dev/null +++ b/contracts/src/plugin/Plugin.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +import {IProtocolVersion} from "../utils/versioning/IProtocolVersion.sol"; +import {ProtocolVersion} from "../utils/versioning/ProtocolVersion.sol"; +import {DaoAuthorizable} from "../permission/auth/DaoAuthorizable.sol"; +import {IDAO} from "../dao/IDAO.sol"; +import {IPlugin} from "./IPlugin.sol"; + +/// @title Plugin +/// @author Aragon Association - 2022-2023 +/// @notice An abstract, non-upgradeable contract to inherit from when creating a plugin being deployed via the `new` keyword. +/// @custom:security-contact sirt@aragon.org +abstract contract Plugin is IPlugin, ERC165, DaoAuthorizable, ProtocolVersion { + /// @notice Constructs the plugin by storing the associated DAO. + /// @param _dao The DAO contract. + constructor(IDAO _dao) DaoAuthorizable(_dao) {} + + /// @inheritdoc IPlugin + function pluginType() public pure override returns (PluginType) { + return PluginType.Constructable; + } + + /// @notice Checks if this or the parent contract supports an interface by its ID. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return + _interfaceId == type(IPlugin).interfaceId || + _interfaceId == type(IProtocolVersion).interfaceId || + super.supportsInterface(_interfaceId); + } +} diff --git a/contracts/src/plugin/PluginCloneable.sol b/contracts/src/plugin/PluginCloneable.sol new file mode 100644 index 00000000..1f9dfa35 --- /dev/null +++ b/contracts/src/plugin/PluginCloneable.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; + +import {IProtocolVersion} from "../utils/versioning/IProtocolVersion.sol"; +import {ProtocolVersion} from "../utils/versioning/ProtocolVersion.sol"; +import {DaoAuthorizableUpgradeable} from "../permission/auth/DaoAuthorizableUpgradeable.sol"; +import {IDAO} from "../dao/IDAO.sol"; +import {IPlugin} from "./IPlugin.sol"; + +/// @title PluginCloneable +/// @author Aragon Association - 2022-2023 +/// @notice An abstract, non-upgradeable contract to inherit from when creating a plugin being deployed via the minimal clones pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)). +/// @custom:security-contact sirt@aragon.org +abstract contract PluginCloneable is + IPlugin, + ERC165Upgradeable, + DaoAuthorizableUpgradeable, + ProtocolVersion +{ + /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Initializes the plugin by storing the associated DAO. + /// @param _dao The DAO contract. + // solhint-disable-next-line func-name-mixedcase + function __PluginCloneable_init(IDAO _dao) internal virtual onlyInitializing { + __DaoAuthorizableUpgradeable_init(_dao); + } + + /// @inheritdoc IPlugin + function pluginType() public pure override returns (PluginType) { + return PluginType.Cloneable; + } + + /// @notice Checks if this or the parent contract supports an interface by its ID. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return + _interfaceId == type(IPlugin).interfaceId || + _interfaceId == type(IProtocolVersion).interfaceId || + super.supportsInterface(_interfaceId); + } +} diff --git a/contracts/src/plugin/PluginUUPSUpgradeable.sol b/contracts/src/plugin/PluginUUPSUpgradeable.sol new file mode 100644 index 00000000..f4e42dd2 --- /dev/null +++ b/contracts/src/plugin/PluginUUPSUpgradeable.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IERC1822ProxiableUpgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/draft-IERC1822Upgradeable.sol"; +import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; + +import {IProtocolVersion} from "../utils/versioning/IProtocolVersion.sol"; +import {ProtocolVersion} from "../utils/versioning/ProtocolVersion.sol"; +import {DaoAuthorizableUpgradeable} from "../permission/auth/DaoAuthorizableUpgradeable.sol"; +import {IDAO} from "../dao/IDAO.sol"; +import {IPlugin} from "./IPlugin.sol"; + +/// @title PluginUUPSUpgradeable +/// @author Aragon Association - 2022-2023 +/// @notice An abstract, upgradeable contract to inherit from when creating a plugin being deployed via the UUPS pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). +/// @custom:security-contact sirt@aragon.org +abstract contract PluginUUPSUpgradeable is + IPlugin, + ERC165Upgradeable, + UUPSUpgradeable, + DaoAuthorizableUpgradeable, + ProtocolVersion +{ + // NOTE: When adding new state variables to the contract, the size of `_gap` has to be adapted below as well. + + /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @inheritdoc IPlugin + function pluginType() public pure override returns (PluginType) { + return PluginType.UUPS; + } + + /// @notice The ID of the permission required to call the `_authorizeUpgrade` function. + bytes32 public constant UPGRADE_PLUGIN_PERMISSION_ID = keccak256("UPGRADE_PLUGIN_PERMISSION"); + + /// @notice Initializes the plugin by storing the associated DAO. + /// @param _dao The DAO contract. + // solhint-disable-next-line func-name-mixedcase + function __PluginUUPSUpgradeable_init(IDAO _dao) internal virtual onlyInitializing { + __DaoAuthorizableUpgradeable_init(_dao); + } + + /// @notice Checks if an interface is supported by this or its parent contract. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return + _interfaceId == type(IPlugin).interfaceId || + _interfaceId == type(IProtocolVersion).interfaceId || + _interfaceId == type(IERC1822ProxiableUpgradeable).interfaceId || + super.supportsInterface(_interfaceId); + } + + /// @notice Returns the address of the implementation contract in the [proxy storage slot](https://eips.ethereum.org/EIPS/eip-1967) slot the [UUPS proxy](https://eips.ethereum.org/EIPS/eip-1822) is pointing to. + /// @return The address of the implementation contract. + function implementation() public view returns (address) { + return _getImplementation(); + } + + /// @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_PLUGIN_PERMISSION_ID` permission. + function _authorizeUpgrade( + address + ) + internal + virtual + override + auth(UPGRADE_PLUGIN_PERMISSION_ID) + // solhint-disable-next-line no-empty-blocks + { + + } + + /// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)). + uint256[50] private __gap; +} diff --git a/contracts/src/plugin/build-metadata.json b/contracts/src/plugin/build-metadata.json deleted file mode 100644 index 70c27df5..00000000 --- a/contracts/src/plugin/build-metadata.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ui": "", - "change": "", - "pluginSetup": { - "prepareInstallation": "", - "prepareUpdate": "", - "prepareUninstallation": "" - } -} diff --git a/contracts/src/governance/membership/Addresslist.sol b/contracts/src/plugin/extensions/governance/Addresslist.sol similarity index 98% rename from contracts/src/governance/membership/Addresslist.sol rename to contracts/src/plugin/extensions/governance/Addresslist.sol index 9f2c422a..5ca40ef7 100644 --- a/contracts/src/governance/membership/Addresslist.sol +++ b/contracts/src/plugin/extensions/governance/Addresslist.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.8; import {CheckpointsUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CheckpointsUpgradeable.sol"; -import {_uncheckedAdd, _uncheckedSub} from "../../utils/math/UncheckedMath.sol"; +import {_uncheckedAdd, _uncheckedSub} from "../../../utils/math/UncheckedMath.sol"; /// @title Addresslist /// @author Aragon Association - 2021-2023 diff --git a/contracts/src/governance/membership/IMembership.sol b/contracts/src/plugin/extensions/membership/IMembership.sol similarity index 100% rename from contracts/src/governance/membership/IMembership.sol rename to contracts/src/plugin/extensions/membership/IMembership.sol diff --git a/contracts/src/governance/proposal/IProposal.sol b/contracts/src/plugin/extensions/proposal/IProposal.sol similarity index 95% rename from contracts/src/governance/proposal/IProposal.sol rename to contracts/src/plugin/extensions/proposal/IProposal.sol index 13575024..9f6769b5 100644 --- a/contracts/src/governance/proposal/IProposal.sol +++ b/contracts/src/plugin/extensions/proposal/IProposal.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.8; -import {IDAO} from "@aragon/osx/packages/contracts/src/core/dao/IDAO.sol"; +import {IDAO} from "../../../dao/IDAO.sol"; /// @title IProposal /// @author Aragon Association - 2022-2023 diff --git a/contracts/src/governance/proposal/Proposal.sol b/contracts/src/plugin/extensions/proposal/Proposal.sol similarity index 98% rename from contracts/src/governance/proposal/Proposal.sol rename to contracts/src/plugin/extensions/proposal/Proposal.sol index f566689f..30901894 100644 --- a/contracts/src/governance/proposal/Proposal.sol +++ b/contracts/src/plugin/extensions/proposal/Proposal.sol @@ -5,8 +5,7 @@ pragma solidity ^0.8.8; import {Counters} from "@openzeppelin/contracts/utils/Counters.sol"; import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import {IDAO} from "@aragon/osx/packages/contracts/src/core/dao/IDAO.sol"; - +import {IDAO} from "../../../dao/IDAO.sol"; import {IProposal} from "./IProposal.sol"; /// @title Proposal diff --git a/contracts/src/governance/proposal/ProposalUpgradeable.sol b/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol similarity index 98% rename from contracts/src/governance/proposal/ProposalUpgradeable.sol rename to contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol index 5ff6c118..8bcf519a 100644 --- a/contracts/src/governance/proposal/ProposalUpgradeable.sol +++ b/contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol @@ -5,8 +5,7 @@ pragma solidity ^0.8.8; import {CountersUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; -import {IDAO} from "@aragon/osx/packages/contracts/src/core/dao/IDAO.sol"; - +import {IDAO} from "../../../dao/IDAO.sol"; import {IProposal} from "./IProposal.sol"; /// @title ProposalUpgradeable diff --git a/contracts/src/plugin/setup/IPluginSetup.sol b/contracts/src/plugin/setup/IPluginSetup.sol new file mode 100644 index 00000000..ce510287 --- /dev/null +++ b/contracts/src/plugin/setup/IPluginSetup.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {PermissionLib} from "../../permission/PermissionLib.sol"; + +// solhint-disable-next-line no-unused-import +import {IDAO} from "../../dao/IDAO.sol"; + +/// @title IPluginSetup +/// @author Aragon Association - 2022-2023 +/// @notice The interface required for a plugin setup contract to be consumed by the `PluginSetupProcessor` for plugin installations, updates, and uninstallations. +/// @custom:security-contact sirt@aragon.org +interface IPluginSetup { + /// @notice The data associated with a prepared setup. + /// @param helpers The address array of helpers (contracts or EOAs) associated with this plugin version after the installation or update. + /// @param permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the installing or updating DAO. + struct PreparedSetupData { + address[] helpers; + PermissionLib.MultiTargetPermission[] permissions; + } + + /// @notice The payload for plugin updates and uninstallations containing the existing contracts as well as optional data to be consumed by the plugin setup. + /// @param plugin The address of the `Plugin`. + /// @param currentHelpers The address array of all current helpers (contracts or EOAs) associated with the plugin to update from. + /// @param data The bytes-encoded data containing the input parameters for the preparation of update/uninstall as specified in the corresponding ABI on the version's metadata. + struct SetupPayload { + address plugin; + address[] currentHelpers; + bytes data; + } + + /// @notice Prepares the installation of a plugin. + /// @param _dao The address of the installing DAO. + /// @param _data The bytes-encoded data containing the input parameters for the installation as specified in the plugin's build metadata JSON file. + /// @return plugin The address of the `Plugin` contract being prepared for installation. + /// @return preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + function prepareInstallation( + address _dao, + bytes calldata _data + ) external returns (address plugin, PreparedSetupData memory preparedSetupData); + + /// @notice Prepares the update of a plugin. + /// @param _dao The address of the updating DAO. + /// @param _currentBuild The build number of the plugin to update from. + /// @param _payload The relevant data necessary for the `prepareUpdate`. See above. + /// @return initData The initialization data to be passed to upgradeable contracts when the update is applied in the `PluginSetupProcessor`. + /// @return preparedSetupData The deployed plugin's relevant data which consists of helpers and permissions. + function prepareUpdate( + address _dao, + uint16 _currentBuild, + SetupPayload calldata _payload + ) external returns (bytes memory initData, PreparedSetupData memory preparedSetupData); + + /// @notice Prepares the uninstallation of a plugin. + /// @param _dao The address of the uninstalling DAO. + /// @param _payload The relevant data necessary for the `prepareUninstallation`. See above. + /// @return permissions The array of multi-targeted permission operations to be applied by the `PluginSetupProcessor` to the uninstalling DAO. + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external returns (PermissionLib.MultiTargetPermission[] memory permissions); + + /// @notice Returns the plugin implementation address. + /// @return The address of the plugin implementation contract. + /// @dev The implementation can be instantiated via the `new` keyword, cloned via the minimal clones pattern (see [ERC-1167](https://eips.ethereum.org/EIPS/eip-1167)), or proxied via the UUPS pattern (see [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822)). + function implementation() external view returns (address); +} diff --git a/contracts/src/plugin/setup/PluginSetup.sol b/contracts/src/plugin/setup/PluginSetup.sol new file mode 100644 index 00000000..7ca76c7c --- /dev/null +++ b/contracts/src/plugin/setup/PluginSetup.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +// solhint-disable-next-line no-unused-import +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +import {IProtocolVersion} from "../../utils/versioning/IProtocolVersion.sol"; +import {ProtocolVersion} from "../../utils/versioning/ProtocolVersion.sol"; +import {createERC1967Proxy as createERC1967} from "../../utils/deployment/Proxy.sol"; +import {IPluginSetup} from "./IPluginSetup.sol"; + +/// @title PluginSetup +/// @author Aragon Association - 2022-2023 +/// @notice An abstract contract that developers have to inherit from to write the setup of a plugin. +/// @custom:security-contact sirt@aragon.org +abstract contract PluginSetup is ERC165, IPluginSetup, ProtocolVersion { + /// @inheritdoc IPluginSetup + function prepareUpdate( + address _dao, + uint16 _currentBuild, + SetupPayload calldata _payload + ) + external + virtual + override + returns (bytes memory initData, PreparedSetupData memory preparedSetupData) + // solhint-disable-next-line no-empty-blocks + { + // Empty to have a default implementation for non-upgradeable plugins. + } + + /// @notice A convenience function to create an [ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) proxy contract pointing to an implementation and being associated to a DAO. + /// @param _implementation The address of the implementation contract to which the proxy is pointing to. + /// @param _data The data to initialize the storage of the proxy contract. + /// @return The address of the created proxy contract. + function createERC1967Proxy( + address _implementation, + bytes memory _data + ) internal returns (address) { + return createERC1967(_implementation, _data); + } + + /// @notice Checks if this or the parent contract supports an interface by its ID. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return + _interfaceId == type(IPluginSetup).interfaceId || + _interfaceId == type(IProtocolVersion).interfaceId || + super.supportsInterface(_interfaceId); + } +} diff --git a/contracts/src/test/Migrations.sol b/contracts/src/test/Migrations.sol deleted file mode 100644 index 4a09cced..00000000 --- a/contracts/src/test/Migrations.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -// Import all contracts from other repositories to make the openzeppelin-upgrades package work to deploy things. -// See related issue here https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/86 - -/* solhint-disable no-unused-import */ -import {DAO} from "@aragon/osx/packages/contracts/src/core/dao/DAO.sol"; - -/* solhint-enable no-unused-import */ diff --git a/contracts/src/test/governance/MajorityVotingMock.sol b/contracts/src/test/governance/MajorityVotingMock.sol deleted file mode 100644 index e2cace0c..00000000 --- a/contracts/src/test/governance/MajorityVotingMock.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {IDAO} from "@aragon/osx/packages/contracts/src/core/dao/IDAO.sol"; - -import {MajorityVotingBase} from "../../governance/majority-voting/MajorityVotingBase.sol"; - -contract MajorityVotingMock is MajorityVotingBase { - function initializeMock(IDAO _dao, VotingSettings calldata _votingSettings) public initializer { - __MajorityVotingBase_init(_dao, _votingSettings); - } - - function createProposal( - bytes calldata /* _metadata */, - IDAO.Action[] calldata /* _actions */, - uint256 /* _allowFailureMap */, - uint64 /* _startDate */, - uint64 /* _endDate */, - VoteOption /* _voteOption */, - bool /* _tryEarlyExecution */ - ) external pure override returns (uint256 proposalId) { - return 0; - } - - function totalVotingPower(uint256 /* _blockNumber */) public pure override returns (uint256) { - return 0; - } - - function _vote( - uint256 /* _proposalId */, - VoteOption /* _voteOption */, - address /* _voter */, - bool /* _tryEarlyExecution */ - ) internal pure override {} // solhint-disable-line no-empty-blocks - - function _canVote( - uint256 /* _proposalId */, - address /* _voter */, - VoteOption /* _voteOption */ - ) internal pure override returns (bool) { - return true; - } -} diff --git a/contracts/src/test/token/TestGovernanceERC20.sol b/contracts/src/test/token/TestGovernanceERC20.sol deleted file mode 100644 index d09dbd80..00000000 --- a/contracts/src/test/token/TestGovernanceERC20.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {IDAO} from "@aragon/osx/packages/contracts/src/core/dao/IDAO.sol"; - -import {GovernanceERC20} from "../../governance/token/erc20/GovernanceERC20.sol"; - -/// @title TestGovernanceERC20 -/// @author Aragon Association - 2022-2023 -/// @notice A test GovernanceERC20 that can be minted and burned by everyone. -/// @dev DO NOT USE IN PRODUCTION! -contract TestGovernanceERC20 is GovernanceERC20 { - constructor( - IDAO _dao, - string memory _name, - string memory _symbol, - MintSettings memory _mintSettings - ) GovernanceERC20(_dao, _name, _symbol, _mintSettings) {} - - // 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); - } - } -} diff --git a/contracts/src/utils/deployment/CloneFactory.sol b/contracts/src/utils/deployment/CloneFactory.sol new file mode 100644 index 00000000..3e13d8c2 --- /dev/null +++ b/contracts/src/utils/deployment/CloneFactory.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +// TODO will be refactored as part of task OS-794 + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +contract CloneFactory { + using Clones for address; + + address private immutable IMPLEMENTATION; + + constructor(address _implementation) { + IMPLEMENTATION = _implementation; + } + + function deployClone() external returns (address clone) { + return IMPLEMENTATION.clone(); + } +} diff --git a/contracts/src/utils/deployment/Proxy.sol b/contracts/src/utils/deployment/Proxy.sol new file mode 100644 index 00000000..86365ec0 --- /dev/null +++ b/contracts/src/utils/deployment/Proxy.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +/// @notice Free function to create a [ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) proxy contract based on the passed base contract address. +/// @param _logic The base contract address. +/// @param _data The constructor arguments for this contract. +/// @return The address of the proxy contract created. +/// @dev Initializes the upgradeable proxy with an initial implementation specified by _logic. If _data is non-empty, it’s used as data in a delegate call to _logic. This will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity constructor (see [OpenZeppelin ERC1967Proxy-constructor](https://docs.openzeppelin.com/contracts/4.x/api/proxy#ERC1967Proxy-constructor-address-bytes-)). +/// @custom:security-contact sirt@aragon.org +function createERC1967Proxy(address _logic, bytes memory _data) returns (address) { + return address(new ERC1967Proxy(_logic, _data)); +} diff --git a/contracts/src/utils/versioning/IProtocolVersion.sol b/contracts/src/utils/versioning/IProtocolVersion.sol new file mode 100644 index 00000000..79cdde4c --- /dev/null +++ b/contracts/src/utils/versioning/IProtocolVersion.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +/// @title IProtocolVersion +/// @author Aragon Association - 2022-2023 +/// @notice An interface defining the semantic Aragon OSx protocol version number. +/// @custom:security-contact sirt@aragon.org +interface IProtocolVersion { + /// @notice Returns the semantic Aragon OSx protocol version number that the implementing contract is associated with. + /// @return _version Returns the semantic Aragon OSx protocol version number. + /// @dev This version number is not to be confused with the `release` and `build` numbers found in the `Version.Tag` struct inside the `PluginRepo` contract being used to version plugin setup and associated plugin implementation contracts. + function protocolVersion() external view returns (uint8[3] memory _version); +} diff --git a/contracts/src/utils/versioning/ProtocolVersion.sol b/contracts/src/utils/versioning/ProtocolVersion.sol new file mode 100644 index 00000000..684aaf82 --- /dev/null +++ b/contracts/src/utils/versioning/ProtocolVersion.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {IProtocolVersion} from "./IProtocolVersion.sol"; + +/// @title ProtocolVersion +/// @author Aragon Association - 2023 +/// @notice An abstract, stateless, non-upgradeable contract providing the current Aragon OSx protocol version number. +/// @dev Do not add any new variables to this contract that would shift down storage in the inheritance chain. +/// @custom:security-contact sirt@aragon.org +abstract contract ProtocolVersion is IProtocolVersion { + // IMPORTANT: Do not add any storage variable, see the above notice. + + /// @inheritdoc IProtocolVersion + function protocolVersion() public pure returns (uint8[3] memory) { + return [1, 4, 0]; + } +} diff --git a/contracts/src/utils/VersionComparisonLib.sol b/contracts/src/utils/versioning/VersionComparisonLib.sol similarity index 100% rename from contracts/src/utils/VersionComparisonLib.sol rename to contracts/src/utils/versioning/VersionComparisonLib.sol diff --git a/contracts/test/governance/majority-voting/majority-voting.ts b/contracts/test/governance/majority-voting/majority-voting.ts deleted file mode 100644 index c16d0e34..00000000 --- a/contracts/test/governance/majority-voting/majority-voting.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { - MajorityVotingMock, - DAO, - IERC165Upgradeable__factory, - IPlugin__factory, - IProposal__factory, - IMajorityVoting__factory, - DAO__factory, - MajorityVotingMock__factory, - IProtocolVersion__factory, -} from '../../../typechain'; -import {getInterfaceId} from '../../../utils/interfaces'; -import {deployWithProxy} from '../../../utils/proxy'; -import {pctToRatio} from '../../utils/math/ratio'; -import {VotingSettings, VotingMode, ONE_HOUR, ONE_YEAR} from './voting-helpers'; -import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {expect} from 'chai'; -import {ethers} from 'hardhat'; - -export const MAJORITY_VOTING_BASE_INTERFACE = new ethers.utils.Interface([ - 'function minDuration()', - 'function minProposerVotingPower()', - 'function votingMode()', - 'function totalVotingPower(uint256)', - 'function getProposal(uint256)', - 'function updateVotingSettings(tuple(uint8,uint32,uint32,uint64,uint256))', - 'function createProposal(bytes,tuple(address,uint256,bytes)[],uint256,uint64,uint64,uint8,bool)', -]); - -describe('MajorityVotingMock', function () { - let signers: SignerWithAddress[]; - let votingBase: MajorityVotingMock; - let dao: DAO; - let ownerAddress: string; - let votingSettings: VotingSettings; - - before(async () => { - signers = await ethers.getSigners(); - ownerAddress = await signers[0].getAddress(); - - dao = await deployWithProxy(new DAO__factory(signers[0])); - await dao.initialize( - [], - ownerAddress, - ethers.constants.AddressZero, - 'examplURI' - ); - }); - - beforeEach(async () => { - votingSettings = { - votingMode: VotingMode.EarlyExecution, - supportThreshold: pctToRatio(50), - minParticipation: pctToRatio(20), - minDuration: ONE_HOUR, - minProposerVotingPower: 0, - }; - - const MajorityVotingBase = new MajorityVotingMock__factory(signers[0]); - - votingBase = await deployWithProxy(MajorityVotingBase); - await dao.grant( - votingBase.address, - ownerAddress, - ethers.utils.id('UPDATE_VOTING_SETTINGS_PERMISSION') - ); - }); - - describe('initialize: ', async () => { - it('reverts if trying to re-initialize', async () => { - await votingBase.initializeMock(dao.address, votingSettings); - - await expect( - votingBase.initializeMock(dao.address, votingSettings) - ).to.be.revertedWith('Initializable: contract is already initialized'); - }); - }); - - describe('ERC-165', async () => { - it('does not support the empty interface', async () => { - expect(await votingBase.supportsInterface('0xffffffff')).to.be.false; - }); - - it('supports the `IERC165Upgradeable` interface', async () => { - const iface = IERC165Upgradeable__factory.createInterface(); - expect(await votingBase.supportsInterface(getInterfaceId(iface))).to.be - .true; - }); - - it('supports the `IPlugin` interface', async () => { - const iface = IPlugin__factory.createInterface(); - expect(await votingBase.supportsInterface(getInterfaceId(iface))).to.be - .true; - }); - - it('supports the `IProtocolVersion` interface', async () => { - const iface = IProtocolVersion__factory.createInterface(); - expect(await votingBase.supportsInterface(getInterfaceId(iface))).to.be - .true; - }); - - it('supports the `IProposal` interface', async () => { - const iface = IProposal__factory.createInterface(); - expect(await votingBase.supportsInterface(getInterfaceId(iface))).to.be - .true; - }); - - it('supports the `IMajorityVoting` interface', async () => { - const iface = IMajorityVoting__factory.createInterface(); - expect(await votingBase.supportsInterface(getInterfaceId(iface))).to.be - .true; - }); - - it('supports the `MajorityVotingBase` interface', async () => { - expect( - await votingBase.supportsInterface( - getInterfaceId(MAJORITY_VOTING_BASE_INTERFACE) - ) - ).to.be.true; - }); - }); - - describe('validateAndSetSettings: ', async () => { - beforeEach(async () => { - await votingBase.initializeMock(dao.address, votingSettings); - }); - - it('reverts if the support threshold specified equals 100%', async () => { - votingSettings.supportThreshold = pctToRatio(100); - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.be.revertedWithCustomError(votingBase, 'RatioOutOfBounds') - .withArgs(pctToRatio(100).sub(1), votingSettings.supportThreshold); - }); - - it('reverts if the support threshold specified exceeds 100%', async () => { - votingSettings.supportThreshold = pctToRatio(1000); - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.be.revertedWithCustomError(votingBase, 'RatioOutOfBounds') - .withArgs(pctToRatio(100).sub(1), votingSettings.supportThreshold); - }); - - it('accepts if the minimum participation specified equals 100%', async () => { - votingSettings.supportThreshold = pctToRatio(1000); - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.be.revertedWithCustomError(votingBase, 'RatioOutOfBounds') - .withArgs(pctToRatio(100).sub(1), votingSettings.supportThreshold); - }); - - it('reverts if the minimum participation specified exceeds 100%', async () => { - votingSettings.minParticipation = pctToRatio(1000); - - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.be.revertedWithCustomError(votingBase, 'RatioOutOfBounds') - .withArgs(pctToRatio(100), votingSettings.minParticipation); - }); - - it('reverts if the minimal duration is shorter than one hour', async () => { - votingSettings.minDuration = ONE_HOUR - 1; - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.be.revertedWithCustomError(votingBase, 'MinDurationOutOfBounds') - .withArgs(ONE_HOUR, votingSettings.minDuration); - }); - - it('reverts if the minimal duration is longer than one year', async () => { - votingSettings.minDuration = ONE_YEAR + 1; - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.be.revertedWithCustomError(votingBase, 'MinDurationOutOfBounds') - .withArgs(ONE_YEAR, votingSettings.minDuration); - }); - - it('should change the voting settings successfully', async () => { - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.emit(votingBase, 'VotingSettingsUpdated') - .withArgs( - votingSettings.votingMode, - votingSettings.supportThreshold, - votingSettings.minParticipation, - votingSettings.minDuration, - votingSettings.minProposerVotingPower - ); - }); - - it('should change the voting settings successfully', async () => { - await expect(votingBase.updateVotingSettings(votingSettings)) - .to.emit(votingBase, 'VotingSettingsUpdated') - .withArgs( - votingSettings.votingMode, - votingSettings.supportThreshold, - votingSettings.minParticipation, - votingSettings.minDuration, - votingSettings.minProposerVotingPower - ); - }); - }); -}); diff --git a/contracts/test/governance/majority-voting/voting-helpers.ts b/contracts/test/governance/majority-voting/voting-helpers.ts deleted file mode 100644 index 5df99417..00000000 --- a/contracts/test/governance/majority-voting/voting-helpers.ts +++ /dev/null @@ -1,99 +0,0 @@ -import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {expect} from 'chai'; -import {BigNumber, Contract} from 'ethers'; -import {ethers} from 'hardhat'; - -export enum VoteOption { - None, - Abstain, - Yes, - No, -} - -export enum VotingMode { - Standard, - EarlyExecution, - VoteReplacement, -} - -export type VotingSettings = { - votingMode: number; - supportThreshold: BigNumber; - minParticipation: BigNumber; - minDuration: number; - minProposerVotingPower: number; -}; - -export const ONE_HOUR = 60 * 60; -export const ONE_DAY = 24 * ONE_HOUR; -export const ONE_YEAR = 365 * ONE_DAY; - -export const MAX_UINT64 = ethers.BigNumber.from(2).pow(64).sub(1); - -export async function getTime(): Promise { - return (await ethers.provider.getBlock('latest')).timestamp; -} - -export async function advanceTime(time: number) { - await ethers.provider.send('evm_increaseTime', [time]); - await ethers.provider.send('evm_mine', []); -} - -export async function advanceTimeTo(timestamp: number) { - const delta = timestamp - (await getTime()); - await advanceTime(delta); -} - -export async function advanceIntoVoteTime(startDate: number, endDate: number) { - await advanceTimeTo(startDate); - expect(await getTime()).to.be.greaterThanOrEqual(startDate); - expect(await getTime()).to.be.lessThan(endDate); -} - -export async function advanceAfterVoteEnd(endDate: number) { - await advanceTimeTo(endDate); - expect(await getTime()).to.be.greaterThanOrEqual(endDate); -} - -export async function voteWithSigners( - votingContract: Contract, - proposalId: number, - signers: SignerWithAddress[], - signerIds: { - yes: number[]; - no: number[]; - abstain: number[]; - } -) { - let promises = signerIds.yes.map(i => - votingContract.connect(signers[i]).vote(proposalId, VoteOption.Yes, false) - ); - - promises = promises.concat( - signerIds.no.map(i => - votingContract.connect(signers[i]).vote(proposalId, VoteOption.No, false) - ) - ); - promises = promises.concat( - signerIds.abstain.map(i => - votingContract - .connect(signers[i]) - .vote(proposalId, VoteOption.Abstain, false) - ) - ); - - await Promise.all(promises); -} - -export async function timestampIn(durationInSec: number): Promise { - return (await ethers.provider.getBlock('latest')).timestamp + durationInSec; -} - -export async function setTimeForNextBlock(timestamp: number): Promise { - await ethers.provider.send('evm_setNextBlockTimestamp', [timestamp]); -} - -export function toBytes32(num: number): string { - const hex = num.toString(16); - return `0x${'0'.repeat(64 - hex.length)}${hex}`; -} diff --git a/contracts/test/governance/membership/membership.ts b/contracts/test/governance/membership/membership.ts deleted file mode 100644 index 70b786d1..00000000 --- a/contracts/test/governance/membership/membership.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/contracts/test/governance/proposal/proposal.ts b/contracts/test/governance/proposal/proposal.ts deleted file mode 100644 index 70b786d1..00000000 --- a/contracts/test/governance/proposal/proposal.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/contracts/test/governance/token/erc20/governance-erc20.ts b/contracts/test/governance/token/erc20/governance-erc20.ts deleted file mode 100644 index d0090035..00000000 --- a/contracts/test/governance/token/erc20/governance-erc20.ts +++ /dev/null @@ -1,785 +0,0 @@ -import { - DAO, - DAO__factory, - GovernanceERC20, - GovernanceERC20__factory, - IERC165Upgradeable__factory, - IERC20MintableUpgradeable__factory, - IERC20PermitUpgradeable__factory, - IERC20Upgradeable__factory, - IVotesUpgradeable__factory, -} from '../../../../typechain'; -import {getInterfaceId} from '../../../../utils/interfaces'; -import {deployWithProxy} from '../../../../utils/proxy'; -import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {expect} from 'chai'; -import {ethers} from 'hardhat'; - -export type MintSettings = { - receivers: string[]; - amounts: number[]; -}; - -const MINT_PERMISSION_ID = ethers.utils.id('MINT_PERMISSION'); -const governanceERC20Name = 'GovernanceToken'; -const governanceERC20Symbol = 'GOV'; - -const addressZero = ethers.constants.AddressZero; - -describe('GovernanceERC20', function () { - let signers: SignerWithAddress[]; - let dao: DAO; - let token: GovernanceERC20; - let GovernanceERC20: GovernanceERC20__factory; - let mintSettings: MintSettings; - let defaultInitData: [string, string, string, MintSettings]; - - let from: SignerWithAddress; - let to: SignerWithAddress; - let other: SignerWithAddress; - let toDelegate: string; - - before(async () => { - signers = await ethers.getSigners(); - GovernanceERC20 = new GovernanceERC20__factory(signers[0]); - - dao = await deployWithProxy(new DAO__factory(signers[0])); - await dao.initialize([], signers[0].address, addressZero, 'exampleURI'); - - from = signers[0]; - to = signers[1]; - other = signers[2]; - }); - - beforeEach(async function () { - mintSettings = { - receivers: signers.slice(0, 3).map(s => s.address), - amounts: [123, 456, 789], - }; - defaultInitData = [ - dao.address, - governanceERC20Name, - governanceERC20Symbol, - mintSettings, - ]; - - token = await GovernanceERC20.deploy(...defaultInitData); - }); - - describe('initialize:', async () => { - it('reverts if trying to re-initialize', async () => { - await expect(token.initialize(...defaultInitData)).to.be.revertedWith( - 'Initializable: contract is already initialized' - ); - }); - - it('sets the token name and symbol', async () => { - expect(await token.name()).to.eq(governanceERC20Name); - expect(await token.symbol()).to.eq(governanceERC20Symbol); - }); - - it('sets the managing DAO ', async () => { - token = await GovernanceERC20.deploy(...defaultInitData); - expect(await token.dao()).to.eq(dao.address); - }); - - it('reverts if the `receivers` and `amounts` array lengths in the mint settings mismatch', async () => { - const receivers = [signers[0].address]; - const amounts = [123, 456]; - await expect( - GovernanceERC20.deploy( - dao.address, - governanceERC20Name, - governanceERC20Symbol, - {receivers: receivers, amounts: amounts} - ) - ) - .to.be.revertedWithCustomError(token, 'MintSettingsArrayLengthMismatch') - .withArgs(receivers.length, amounts.length); - }); - }); - - describe('ERC-165', async () => { - it('does not support the empty interface', async () => { - expect(await token.supportsInterface('0xffffffff')).to.be.false; - }); - - it('supports the `IERC165Upgradeable` interface', async () => { - const iface = IERC165Upgradeable__factory.createInterface(); - expect(await token.supportsInterface(getInterfaceId(iface))).to.be.true; - }); - - it('it supports all inherited interfaces', async () => { - await Promise.all( - [ - IERC20Upgradeable__factory.createInterface(), - IERC20PermitUpgradeable__factory.createInterface(), - IVotesUpgradeable__factory.createInterface(), - IERC20MintableUpgradeable__factory.createInterface(), - ].map(async interfaceName => { - expect(await token.supportsInterface(getInterfaceId(interfaceName))) - .to.be.true; - }) - ); - // We must check `IERC20MetadataUpgradeable` separately as it inherits from `IERC20Upgradeable` and we cannot get the isolated ABI. - const ierc20MetadataInterface = new ethers.utils.Interface([ - 'function name()', - 'function symbol()', - 'function decimals()', - ]); - expect( - await token.supportsInterface(getInterfaceId(ierc20MetadataInterface)) - ).to.be.true; - }); - }); - - describe('mint:', async () => { - it('reverts if the `MINT_PERMISSION_ID` permission is missing', async () => { - await expect(token.mint(signers[0].address, 123)) - .to.be.revertedWithCustomError(token, 'DaoUnauthorized') - .withArgs( - dao.address, - token.address, - signers[0].address, - MINT_PERMISSION_ID - ); - }); - - it('mints tokens if the caller has the `mintPermission`', async () => { - await dao.grant(token.address, signers[0].address, MINT_PERMISSION_ID); - - const receiverAddr = signers[9].address; - const oldBalance = await token.balanceOf(receiverAddr); - - const mintAmount = 100; - await token.mint(receiverAddr, mintAmount); - - expect(await token.balanceOf(receiverAddr)).to.eq( - oldBalance.add(mintAmount) - ); - }); - }); - - describe('delegate', async () => { - it('delegates voting power to another account', async () => { - const balanceSigner0 = await token.balanceOf(signers[0].address); - const balanceSigner1 = await token.balanceOf(signers[1].address); - - // delegate the votes of signers[0] to signers[1] - await token.connect(signers[0]).delegate(signers[1].address); - - // signers[0] delegates to signers[1] - expect(await token.delegates(signers[0].address)).to.eq( - signers[1].address - ); - - // signers[1] delegates to herself - expect(await token.delegates(signers[1].address)).to.eq( - signers[1].address - ); - - // balances remain the same - expect(await token.balanceOf(signers[0].address)).to.eq(balanceSigner0); - expect(await token.balanceOf(signers[1].address)).to.eq(balanceSigner1); - - // the voting power changes - expect(await token.getVotes(signers[0].address)).to.eq(0); - expect(await token.getVotes(signers[1].address)).to.eq( - balanceSigner1.add(balanceSigner0) - ); - }); - - it('is checkpointed', async () => { - const balanceSigner0 = await token.balanceOf(signers[0].address); - const balanceSigner1 = await token.balanceOf(signers[1].address); - const balanceSigner2 = await token.balanceOf(signers[2].address); - - const tx1 = await token.connect(signers[0]).delegate(signers[1].address); - await ethers.provider.send('evm_mine', []); - - // verify that current votes are correct - expect(await token.getVotes(signers[0].address)).to.eq(0); - expect(await token.getVotes(signers[1].address)).to.eq( - balanceSigner1.add(balanceSigner0) - ); - expect(await token.getVotes(signers[2].address)).to.eq(balanceSigner2); - - await token.connect(signers[0]).delegate(signers[2].address); - await ethers.provider.send('evm_mine', []); - - // verify that current votes are correct - expect(await token.getVotes(signers[0].address)).to.eq(0); - expect(await token.getVotes(signers[1].address)).to.eq(balanceSigner1); - expect(await token.getVotes(signers[2].address)).to.eq( - balanceSigner2.add(balanceSigner0) - ); - - // verify that past votes are correct - if (tx1.blockNumber === undefined) { - throw Error('tx was not sent'); - } - expect( - await token.getPastVotes(signers[0].address, tx1.blockNumber) - ).to.eq(0); - expect( - await token.getPastVotes(signers[1].address, tx1.blockNumber) - ).to.eq(balanceSigner1.add(balanceSigner0)); - expect( - await token.getPastVotes(signers[2].address, tx1.blockNumber) - ).to.eq(balanceSigner2); - }); - }); - - describe('afterTokenTransfer', async () => { - beforeEach(async () => { - token = await GovernanceERC20.deploy(dao.address, 'name', 'symbol', { - receivers: [], - amounts: [], - }); - - await dao.grant(token.address, signers[0].address, MINT_PERMISSION_ID); - }); - - it('turns on delegation after mint', async () => { - expect(await token.delegates(signers[0].address)).to.equal(addressZero); - - await expect(token.mint(signers[0].address, 1)).to.emit( - token, - 'DelegateChanged' - ); - - expect(await token.delegates(signers[0].address)).to.equal( - signers[0].address - ); - expect(await token.getVotes(signers[0].address)).to.equal(1); - }); - - it('turns on delegation for the `to` address after transfer', async () => { - // delegation turned on for signers[0] - await token.mint(signers[0].address, 100); - - // At this time, signers[1] doesn't have delegation turned on, - // but the transfer should turn it on. - expect(await token.delegates(signers[1].address)).to.equal(addressZero); - - // Should turn on delegation - await expect(token.transfer(signers[1].address, 50)).to.emit( - token, - 'DelegateChanged' - ); - - expect(await token.getVotes(signers[1].address)).to.equal(50); - expect(await token.delegates(signers[1].address)).to.equal( - signers[1].address - ); - }); - - it('turns on delegation for all users in the chain of transfer A => B => C', async () => { - await token.mint(signers[0].address, 100); - - await expect(token.transfer(signers[1].address, 40)).to.emit( - token, - 'DelegateChanged' - ); - await expect( - token.connect(signers[1]).transfer(signers[2].address, 20) - ).to.emit(token, 'DelegateChanged'); - - expect(await token.getVotes(signers[0].address)).to.equal(60); - expect(await token.getVotes(signers[1].address)).to.equal(20); - expect(await token.getVotes(signers[2].address)).to.equal(20); - }); - - it('should not turn on delegation on `transfer` if `to` manually turned it off', async () => { - await token.mint(signers[0].address, 100); - - await token.transfer(signers[1].address, 50); - - // turn off delegation - await token.connect(signers[1]).delegate(addressZero); - - // Shouldn't turn on delegation - await expect(token.transfer(signers[1].address, 50)).to.not.emit( - token, - 'DelegateChanged' - ); - - expect(await token.delegates(signers[1].address)).to.equal(addressZero); - expect(await token.getVotes(signers[1].address)).to.equal(0); - }); - - it('should not turn on delegation on `mint` if `to` manually turned it off', async () => { - await token.mint(signers[0].address, 100); - - // turn off delegation - await token.delegate(addressZero); - - // Shouldn't turn on delegation - await expect(token.mint(signers[0].address, 50)).to.not.emit( - token, - 'DelegateChanged' - ); - - expect(await token.delegates(signers[0].address)).to.equal(addressZero); - expect(await token.getVotes(signers[1].address)).to.equal(0); - }); - - it('should not rewrite delegation setting for `transfer` if user set it on before receiving tokens', async () => { - await token.mint(signers[0].address, 10); - - // user1 delegated to user2. - await token.connect(signers[1]).delegate(signers[2].address); - - // When user1 receives his first token, delegation to himself - // shouldn't be called so it doesn't overwrite his setting. - await expect(token.transfer(signers[1].address, 10)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('should not rewrite delegation setting for `mint` if user set it on before receiving tokens', async () => { - // user1 delegated to user2 before receiving tokens. - await token.connect(signers[1]).delegate(signers[2].address); - - await expect(token.mint(signers[1].address, 10)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('should not turn on delegation on `mint` if it was turned on at least once in the past', async () => { - await token.mint(signers[0].address, 100); - - await expect(token.mint(signers[0].address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - - expect(await token.getVotes(signers[0].address)).to.equal(200); - }); - - it('should not turn on delegation on `transfer` if it was turned on at least once in the past', async () => { - await token.mint(signers[0].address, 100); - - await token.transfer(signers[1].address, 50); - - await expect(token.transfer(signers[1].address, 30)).to.not.emit( - token, - 'DelegateChanged' - ); - - expect(await token.getVotes(signers[1].address)).to.equal(80); - }); - - it('updates voting power after transfer for `from` if delegation turned on', async () => { - await token.mint(signers[0].address, 100); - - await token.transfer(signers[1].address, 30); - - expect(await token.getVotes(signers[0].address)).to.equal(70); - }); - - it('updates voting power after transfer for `to` if delegation turned on', async () => { - await token.mint(signers[0].address, 100); - - await token.transfer(signers[1].address, 30); - - expect(await token.getVotes(signers[1].address)).to.equal(30); - }); - - context('exhaustive tests', async () => { - context('`to` has a zero balance', async () => { - beforeEach(async () => { - expect(await token.balanceOf(to.address)).to.eq(0); - toDelegate = addressZero; - }); - - context('`to` delegated to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, toDelegate, other.address); - toDelegate = other.address; - }); - - context('`to` receives via `mint` from `address(0)`', async () => { - beforeEach(async () => { - await expect(token.mint(to.address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.mint(from.address, 100); - await expect( - token.connect(from).transfer(to.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - }); - - context('`to` has not delegated before', async () => { - context('`to` receives via `mint` from `address(0)`', async () => { - beforeEach(async () => { - await expect(token.mint(to.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, toDelegate, to.address); // the mint triggers automatic self-delegation - toDelegate = to.address; - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.mint(from.address, 100); - await expect(token.connect(from).transfer(to.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, addressZero, to.address); // the transfer triggers automatic self-delegation - toDelegate = to.address; - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - }); - }); - - context('`to` has a non-zero balance', async () => { - beforeEach(async () => { - await expect(token.mint(to.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, addressZero, to.address); - toDelegate = to.address; - expect(await token.balanceOf(to.address)).to.eq(100); - }); - - context('`to` delegated to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, to.address, other.address); // this changes the delegate from himself (`to`) to `other` - toDelegate = other.address; - }); - - context('`to` receives via `mint` from `address(0)`', async () => { - beforeEach(async () => { - await expect(token.mint(to.address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect( - token.connect(to).transfer(other.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, other.address, other.address); // `to` re-delegates to `other` again - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal( - other.address - ); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - }); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.mint(from.address, 100); - await expect( - token.connect(from).transfer(to.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect( - token.connect(to).transfer(other.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, other.address, other.address); // `to` re-delegates to `other` again - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - }); - }); - - context('`to` has not delegated before', async () => { - context('`to` receives via `mint` from `address(0)`', async () => { - beforeEach(async () => { - await expect(token.mint(to.address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(200); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).transfer(other.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(other.address, addressZero, other.address); // the transfer triggers automatic self-delegation for `other` - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); // 100 tokens are still left - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, to.address, other.address); // `to` delegates to `other` - toDelegate = other.address; - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - }); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.mint(from.address, 100); - await expect( - token.connect(from).transfer(to.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(200); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).transfer(other.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(other.address, addressZero, other.address); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, to.address, other.address); // `to` delegates to `other` - toDelegate = other.address; - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - }); - }); - }); - }); - }); -}); diff --git a/contracts/test/governance/token/erc20/governance-wrapped-erc20.ts b/contracts/test/governance/token/erc20/governance-wrapped-erc20.ts deleted file mode 100644 index ee4bd4b8..00000000 --- a/contracts/test/governance/token/erc20/governance-wrapped-erc20.ts +++ /dev/null @@ -1,929 +0,0 @@ -import { - TestERC20, - TestERC20__factory, - GovernanceWrappedERC20, - GovernanceWrappedERC20__factory, - IERC165Upgradeable__factory, - IGovernanceWrappedERC20__factory, - IERC20Upgradeable__factory, - IERC20PermitUpgradeable__factory, - IVotesUpgradeable__factory, -} from '../../../../typechain'; -import {getInterfaceId} from '../../../../utils/interfaces'; -import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; -import {expect} from 'chai'; -import {ethers} from 'hardhat'; - -export type AccountBalance = {account: string; amount: number}; - -export type MintSettings = { - receivers: string[]; - amounts: number[]; -}; - -const existingErc20Name = 'Token'; -const existingErc20Symbol = 'TOK'; - -const governanceWrappedERC20Name = 'GovernanceWrappedToken'; -const governanceWrappedERC20Symbol = 'gwTOK'; - -const addressZero = ethers.constants.AddressZero; - -let from: SignerWithAddress; -let to: SignerWithAddress; -let other: SignerWithAddress; -let toDelegate: string; - -describe('GovernanceWrappedERC20', function () { - let signers: SignerWithAddress[]; - let governanceToken: GovernanceWrappedERC20; - let erc20: TestERC20; - let TestERC20: TestERC20__factory; - let GovernanceWrappedERC20: GovernanceWrappedERC20__factory; - let defaultBalances: AccountBalance[]; - - let defaultExistingERC20InitData: [string, string, number]; - let defaultGovernanceWrappedERC20InitData: [string, string, string]; - - before(async () => { - signers = await ethers.getSigners(); - - TestERC20 = new TestERC20__factory(signers[0]); - GovernanceWrappedERC20 = new GovernanceWrappedERC20__factory(signers[0]); - defaultBalances = [ - {account: signers[0].address, amount: 123}, - {account: signers[1].address, amount: 456}, - {account: signers[2].address, amount: 789}, - ]; - - from = signers[0]; - to = signers[1]; - other = signers[2]; - }); - - beforeEach(async function () { - defaultExistingERC20InitData = [existingErc20Name, existingErc20Symbol, 0]; - erc20 = await TestERC20.deploy(...defaultExistingERC20InitData); - - const promises = defaultBalances.map(balance => - erc20.setBalance(balance.account, balance.amount) - ); - await Promise.all(promises); - - defaultGovernanceWrappedERC20InitData = [ - erc20.address, - governanceWrappedERC20Name, - governanceWrappedERC20Symbol, - ]; - - governanceToken = await GovernanceWrappedERC20.deploy( - ...defaultGovernanceWrappedERC20InitData - ); - }); - - describe('initialize:', async () => { - it('reverts if trying to re-initialize', async () => { - await expect( - governanceToken.initialize(...defaultGovernanceWrappedERC20InitData) - ).to.be.revertedWith('Initializable: contract is already initialized'); - }); - - it('sets the wrapped token name and symbol', async () => { - governanceToken = await GovernanceWrappedERC20.deploy( - ...defaultGovernanceWrappedERC20InitData - ); - - expect(await governanceToken.name()).to.eq(governanceWrappedERC20Name); - expect(await governanceToken.symbol()).to.eq( - governanceWrappedERC20Symbol - ); - }); - - it('should return default decimals if not modified', async () => { - expect(await governanceToken.decimals()).to.eq(18); - }); - - it('should return modified decimals', async () => { - const defaultDecimals = await erc20.decimals(); - await erc20.setDecimals(5); - expect(await governanceToken.decimals()).to.eq(5); - await erc20.setDecimals(defaultDecimals); - }); - }); - - describe('ERC-165', async () => { - it('does not support the empty interface', async () => { - expect(await governanceToken.supportsInterface('0xffffffff')).to.be.false; - }); - - it('supports the `IERC165Upgradeable` interface', async () => { - const iface = IERC165Upgradeable__factory.createInterface(); - expect(await governanceToken.supportsInterface(getInterfaceId(iface))).to - .be.true; - }); - - it('supports the `IGovernanceWrappedERC20` interface', async () => { - const iface = IGovernanceWrappedERC20__factory.createInterface(); - expect(await governanceToken.supportsInterface(getInterfaceId(iface))).to - .be.true; - }); - - it('supports the `IERC20Upgradeable` interface', async () => { - const iface = IERC20Upgradeable__factory.createInterface(); - expect(await governanceToken.supportsInterface(getInterfaceId(iface))).to - .be.true; - }); - - it('supports the `IERC20PermitUpgradeable` interface', async () => { - const iface = IERC20PermitUpgradeable__factory.createInterface(); - expect(await governanceToken.supportsInterface(getInterfaceId(iface))).to - .be.true; - }); - - it('supports the `IVotesUpgradeable` interface', async () => { - const iface = IVotesUpgradeable__factory.createInterface(); - expect(await governanceToken.supportsInterface(getInterfaceId(iface))).to - .be.true; - }); - - it('supports the `IERC20MetadataUpgradeable` interface', async () => { - // We must check `IERC20MetadataUpgradeable` separately as it inherits from `IERC20Upgradeable` and we cannot get the isolated ABI. - const iface = new ethers.utils.Interface([ - 'function name()', - 'function symbol()', - 'function decimals()', - ]); - expect(await governanceToken.supportsInterface(getInterfaceId(iface))).to - .be.true; - }); - }); - - describe('depositFor', async () => { - it('reverts if the amount is not approved', async () => { - const erc20Balance = await erc20.balanceOf(signers[0].address); - await expect( - governanceToken.depositFor(signers[0].address, erc20Balance) - ).to.be.revertedWith('ERC20: insufficient allowance'); - }); - - it('deposits an amount of tokens', async () => { - // check old balances - const erc20Balance = await erc20.balanceOf(signers[0].address); - const governanceTokenBalance = await governanceToken.balanceOf( - signers[0].address - ); - expect(erc20Balance).to.eq(123); - expect(governanceTokenBalance).to.eq(0); - - // wrap 100 erc20 tokens from signers[0] for signers[0] - const depositAmount = 100; - await erc20.approve(governanceToken.address, depositAmount); - await governanceToken.depositFor(signers[0].address, depositAmount); - - // check new balances - expect(await erc20.balanceOf(signers[0].address)).to.eq( - erc20Balance.sub(depositAmount) - ); - expect(await governanceToken.balanceOf(signers[0].address)).to.eq( - governanceTokenBalance.add(depositAmount) - ); - }); - - it('updates the available votes', async () => { - const erc20Balance = await erc20.balanceOf(signers[0].address); - await erc20.approve(governanceToken.address, erc20Balance); - - // send the entire balance - await governanceToken.depositFor(signers[0].address, erc20Balance); - - expect(await governanceToken.balanceOf(signers[0].address)).to.eq( - erc20Balance - ); - expect(await governanceToken.getVotes(signers[0].address)).to.eq( - erc20Balance - ); - }); - }); - - describe('withdrawTo', async () => { - beforeEach(async function () { - const erc20Balance = await erc20.balanceOf(signers[0].address); - await erc20.approve(governanceToken.address, erc20Balance); - await governanceToken.depositFor(signers[0].address, erc20Balance); - }); - - it('withdraws an amount of tokens', async () => { - // check old balances - const erc20Balance = await erc20.balanceOf(signers[0].address); - const governanceTokenBalance = await governanceToken.balanceOf( - signers[0].address - ); - expect(erc20Balance).to.eq(0); - expect(governanceTokenBalance).to.eq(123); - - // unwrap 100 governance tokens from signers[0] for signers[0] - const withdrawAmount = 100; - await erc20.approve(governanceToken.address, withdrawAmount); - await governanceToken.withdrawTo(signers[0].address, withdrawAmount); - - // check new balances - expect(await erc20.balanceOf(signers[0].address)).to.eq( - erc20Balance.add(withdrawAmount) - ); - expect(await governanceToken.balanceOf(signers[0].address)).to.eq( - governanceTokenBalance.sub(withdrawAmount) - ); - expect(await governanceToken.balanceOf(signers[0].address)).to.eq( - governanceTokenBalance.sub(withdrawAmount) - ); - }); - - it('updates the available votes', async () => { - const governanceTokenBalance = await governanceToken.balanceOf( - signers[0].address - ); - - await governanceToken.withdrawTo( - signers[0].address, - governanceTokenBalance - ); - - expect(await governanceToken.balanceOf(signers[0].address)).to.eq(0); - expect(await governanceToken.getVotes(signers[0].address)).to.eq(0); - }); - }); - - describe('delegate', async () => { - beforeEach(async function () { - // approve and deposit for all token holders - const promises = defaultBalances.map(balance => - erc20.approve(balance.account, balance.amount) - ); - - await Promise.all(promises); - }); - - it('delegates voting power to another account', async () => { - const balanceSigner0 = await governanceToken.balanceOf( - signers[0].address - ); - const balanceSigner1 = await governanceToken.balanceOf( - signers[1].address - ); - - // delegate the votes of signers[0] to signers[1] - await governanceToken.connect(signers[0]).delegate(signers[1].address); - - // signers[0] delegates to signers[1] - expect(await governanceToken.delegates(signers[0].address)).to.eq( - signers[1].address - ); - - // signers[1] has not wrapped yet and therefore `delegates` is set to `address(0)` - expect(await governanceToken.delegates(signers[1].address)).to.eq( - ethers.constants.AddressZero - ); - - // balances remain the same - expect(await governanceToken.balanceOf(signers[0].address)).to.eq( - balanceSigner0 - ); - expect(await governanceToken.balanceOf(signers[1].address)).to.eq( - balanceSigner1 - ); - - // the voting power changes - expect(await governanceToken.getVotes(signers[0].address)).to.eq(0); - expect(await governanceToken.getVotes(signers[1].address)).to.eq( - balanceSigner1.add(balanceSigner0) - ); - }); - - it('is checkpointed', async () => { - const balanceSigner0 = await governanceToken.balanceOf( - signers[0].address - ); - const balanceSigner1 = await governanceToken.balanceOf( - signers[1].address - ); - const balanceSigner2 = await governanceToken.balanceOf( - signers[2].address - ); - - const tx1 = await governanceToken - .connect(signers[0]) - .delegate(signers[1].address); - await ethers.provider.send('evm_mine', []); - - // verify that current votes are correct - expect(await governanceToken.getVotes(signers[0].address)).to.eq(0); - expect(await governanceToken.getVotes(signers[1].address)).to.eq( - balanceSigner1.add(balanceSigner0) - ); - expect(await governanceToken.getVotes(signers[2].address)).to.eq( - balanceSigner2 - ); - - await governanceToken.connect(signers[0]).delegate(signers[2].address); - await ethers.provider.send('evm_mine', []); - - // verify that current votes are correct - expect(await governanceToken.getVotes(signers[0].address)).to.eq(0); - expect(await governanceToken.getVotes(signers[1].address)).to.eq( - balanceSigner1 - ); - expect(await governanceToken.getVotes(signers[2].address)).to.eq( - balanceSigner2.add(balanceSigner0) - ); - - // verify that past votes are correct - if (tx1.blockNumber === undefined) { - throw Error('tx was not sent'); - } - expect( - await governanceToken.getPastVotes(signers[0].address, tx1.blockNumber) - ).to.eq(0); - expect( - await governanceToken.getPastVotes(signers[1].address, tx1.blockNumber) - ).to.eq(balanceSigner1.add(balanceSigner0)); - expect( - await governanceToken.getPastVotes(signers[2].address, tx1.blockNumber) - ).to.eq(balanceSigner2); - }); - }); - - describe('afterTokenTransfer', async () => { - let token: GovernanceWrappedERC20; - - beforeEach(async () => { - token = await GovernanceWrappedERC20.deploy( - erc20.address, - 'name', - 'symbol' - ); - - await erc20.setBalance(signers[0].address, 200); - await erc20.setBalance(signers[1].address, 200); - await erc20.connect(signers[0]).approve(token.address, 200); - await erc20.connect(signers[1]).approve(token.address, 200); - }); - - it('turns on delegation after mint', async () => { - expect(await token.delegates(signers[0].address)).to.equal(addressZero); - - await expect(token.depositFor(signers[0].address, 1)).to.emit( - token, - 'DelegateChanged' - ); - - expect(await token.delegates(signers[0].address)).to.equal( - signers[0].address - ); - expect(await token.getVotes(signers[0].address)).to.equal(1); - }); - - it('turns on delegation for the `to` address after transfer', async () => { - // delegation turned on for signers[0] - await token.depositFor(signers[0].address, 100); - - // At this time, signers[1] doesn't have delegation turned on, - // but the transfer should turn it on. - expect(await token.delegates(signers[1].address)).to.equal(addressZero); - - await expect(token.transfer(signers[1].address, 50)).to.emit( - token, - 'DelegateChanged' - ); - - expect(await token.getVotes(signers[1].address)).to.equal(50); - expect(await token.delegates(signers[1].address)).to.equal( - signers[1].address - ); - }); - - it('turns on delegation for all users in the chain of transfer A => B => C', async () => { - await token.depositFor(signers[0].address, 100); - - await expect(token.transfer(signers[1].address, 40)).to.emit( - token, - 'DelegateChanged' - ); - await expect( - token.connect(signers[1]).transfer(signers[2].address, 20) - ).to.emit(token, 'DelegateChanged'); - - expect(await token.getVotes(signers[0].address)).to.equal(60); - expect(await token.getVotes(signers[1].address)).to.equal(20); - expect(await token.getVotes(signers[2].address)).to.equal(20); - }); - - it('should not turn on delegation on `transfer` if `to` manually turned it off', async () => { - await token.depositFor(signers[0].address, 100); - - // turns on delegation for signers[1] - await token.transfer(signers[1].address, 50); - - // turns off delegation for signers[1] - await token.connect(signers[1]).delegate(addressZero); - - // shouldn't turn on delegation. - await expect(token.transfer(signers[1].address, 50)).to.not.emit( - token, - 'DelegateChanged' - ); - - expect(await token.delegates(signers[1].address)).to.equal(addressZero); - expect(await token.getVotes(signers[1].address)).to.equal(0); - }); - - it('should not turn on delegation on `mint` if `to` manually turned it off', async () => { - await token.depositFor(signers[0].address, 100); - - // turn off delegation - await token.delegate(addressZero); - - await expect(token.depositFor(signers[0].address, 50)).to.not.emit( - token, - 'DelegateChanged' - ); - - expect(await token.delegates(signers[0].address)).to.equal(addressZero); - expect(await token.getVotes(signers[1].address)).to.equal(0); - }); - - it('should not rewrite delegation setting for `transfer` if user set it on before receiving tokens', async () => { - await token.depositFor(signers[0].address, 10); - - // user1 delegated to user2. - await token.connect(signers[1]).delegate(signers[2].address); - - // When user1 receives his first token, delegation to himself - // shouldn't be called so it doesn't overwrite his setting. - await expect(token.transfer(signers[1].address, 10)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('should not rewrite delegation setting for `mint` if user set it on before receiving tokens', async () => { - // user1 delegated to user2 before receiving tokens. - await token.connect(signers[1]).delegate(signers[2].address); - - await expect(token.depositFor(signers[1].address, 10)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('should not turn on delegation on `mint` if it was turned on at least once in the past', async () => { - await token.depositFor(signers[0].address, 100); - - await expect(token.depositFor(signers[0].address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - - expect(await token.getVotes(signers[0].address)).to.equal(200); - }); - - it('should not turn on delegation on `transfer` if it was turned on at least once in the past', async () => { - await token.depositFor(signers[0].address, 100); - - await token.transfer(signers[1].address, 50); - - await expect(token.transfer(signers[1].address, 30)).to.not.emit( - token, - 'DelegateChanged' - ); - expect(await token.getVotes(signers[1].address)).to.equal(80); - }); - - it('updates voting power after transfer for `from` if delegation turned on', async () => { - await token.depositFor(signers[0].address, 100); - - await token.transfer(signers[1].address, 30); - - expect(await token.getVotes(signers[0].address)).to.equal(70); - }); - - it('updates voting power after transfer for `to` if delegation turned on', async () => { - await token.depositFor(signers[0].address, 100); - - await token.transfer(signers[1].address, 30); - - expect(await token.getVotes(signers[1].address)).to.equal(30); - }); - - context('exhaustive tests', async () => { - context('`to` has a zero balance', async () => { - beforeEach(async () => { - expect(await token.balanceOf(to.address)).to.eq(0); - toDelegate = addressZero; - }); - - context('`to` delegated to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, addressZero, other.address); - toDelegate = other.address; - }); - - context( - '`to` receives via `depositFor` from `address(0)`', - async () => { - beforeEach(async () => { - await expect(token.depositFor(to.address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - } - ); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.depositFor(from.address, 100); - await expect( - token.connect(from).transfer(to.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - }); - - context('`to` has not delegated before', async () => { - context( - '`to` receives via `depositFor` from `address(0)`', - async () => { - beforeEach(async () => { - await expect(token.depositFor(to.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, addressZero, to.address); // the mint triggers automatic self-delegation - toDelegate = to.address; - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - } - ); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.depositFor(from.address, 100); - await expect(token.connect(from).transfer(to.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, addressZero, to.address); // the transfer triggers automatic self-delegation - toDelegate = to.address; - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - }); - }); - - context('`to` has a non-zero balance', async () => { - beforeEach(async () => { - await expect(token.depositFor(to.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, addressZero, to.address); - toDelegate = to.address; - expect(await token.balanceOf(to.address)).to.eq(100); - }); - - context('`to` delegated to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, to.address, other.address); // this changes the delegate from himself (`to`) to `other` - toDelegate = other.address; - }); - - context( - '`to` receives via `depositFor` from `address(0)`', - async () => { - beforeEach(async () => { - await expect(token.depositFor(to.address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect( - token.connect(to).transfer(other.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal( - toDelegate - ); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, other.address, other.address); // `to` re-delegates to `other` again - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal( - other.address - ); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - } - ); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.depositFor(from.address, 100); - await expect( - token.connect(from).transfer(to.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(other.address); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect( - token.connect(to).transfer(other.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, other.address, other.address); // `to` re-delegates to `other` again - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - }); - }); - - context('`to` has not delegated before', async () => { - context( - '`to` receives via `depositFor` from `address(0)`', - async () => { - beforeEach(async () => { - await expect(token.depositFor(to.address, 100)).to.not.emit( - token, - 'DelegateChanged' - ); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(200); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).transfer(other.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(other.address, addressZero, other.address); // the transfer triggers automatic self-delegation for `other` - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); // 100 tokens are still left - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal( - toDelegate - ); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, to.address, other.address); // `to` delegates to `other` - toDelegate = other.address; - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal( - toDelegate - ); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - } - ); - - context('`to` receives via transfer from `from`', async () => { - beforeEach(async () => { - await token.depositFor(from.address, 100); - await expect( - token.connect(from).transfer(to.address, 100) - ).to.not.emit(token, 'DelegateChanged'); - }); - - it('`from` has the correct voting power', async () => { - expect(await token.getVotes(from.address)).to.equal(0); - }); - it('`from`s delegate has not changed', async () => { - expect(await token.delegates(from.address)).to.equal( - from.address - ); - }); - it('`from`s delegate has the correct voting power', async () => { - const fromDelegate = await token.delegates(from.address); - expect(await token.getVotes(fromDelegate)).to.equal(0); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(200); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - - context('`to` transfers to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).transfer(other.address, 100)) - .to.emit(token, 'DelegateChanged') - .withArgs(other.address, addressZero, other.address); - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(100); - }); - it('`to`s delegate has not changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(100); - }); - }); - - context('`to` delegates to `other`', async () => { - beforeEach(async () => { - await expect(token.connect(to).delegate(other.address)) - .to.emit(token, 'DelegateChanged') - .withArgs(to.address, to.address, other.address); // `to` delegates to `other` - toDelegate = other.address; - }); - - it('`to` has the correct voting power', async () => { - expect(await token.getVotes(to.address)).to.equal(0); - }); - it('`to`s delegate is correctly changed', async () => { - expect(await token.delegates(to.address)).to.equal(toDelegate); - }); - it('`to`s delegate has the correct voting power', async () => { - expect(await token.getVotes(toDelegate)).to.equal(200); - }); - }); - }); - }); - }); - }); - }); -}); diff --git a/contracts/test/permission/auth/auth.ts b/contracts/test/permission/auth/auth.ts new file mode 100644 index 00000000..fc20d284 --- /dev/null +++ b/contracts/test/permission/auth/auth.ts @@ -0,0 +1,17 @@ +import {expect} from 'chai'; + +// TODO + +describe.skip('auth', async () => { + it('reverts with an error if the permission is not granted', async () => { + expect(true).to.equal(false); + }); + + it('passes if the permission is granted', async () => { + expect(true).to.equal(false); + }); + + it('relays the authorization to `PermissionCondition` if the permission was granted with `grantWithCondition`', async () => { + expect(true).to.equal(false); + }); +}); diff --git a/contracts/test/permission/auth/dao-authorizable.ts b/contracts/test/permission/auth/dao-authorizable.ts new file mode 100644 index 00000000..5d785068 --- /dev/null +++ b/contracts/test/permission/auth/dao-authorizable.ts @@ -0,0 +1,30 @@ +import {expect} from 'chai'; + +// TODO + +describe.skip('DaoAuthorizable', async () => { + // TODO abstract these common tests that also apply to `DaoAuthorizableUpgradeable` + it('initializes the DAO', async () => { + expect(true).to.equal(false); + }); + + it('authorizes calls to `auth`-modifier-protected functions', async () => { + expect(true).to.equal(false); + }); + + it('relays the authorization to `PermissionCondition` if the permission was granted with `grantWithCondition`', async () => { + expect(true).to.equal(false); + }); +}); + +describe.skip('DaoAuthorizableUpgradeable', async () => { + // TODO run the same tests as for `DaoAuthorizable`. + + it('upgrades', async () => { + expect(true).to.equal(false); + }); + + it('can be reinitialized', async () => { + expect(true).to.equal(false); + }); +}); diff --git a/contracts/test/permission/permission-condition.ts b/contracts/test/permission/permission-condition.ts new file mode 100644 index 00000000..a592f7e2 --- /dev/null +++ b/contracts/test/permission/permission-condition.ts @@ -0,0 +1,66 @@ +import { + IERC165__factory, + IPermissionCondition__factory, + IProtocolVersion__factory, + PermissionConditionMock, + PermissionConditionMock__factory, +} from '../../typechain'; +import {getInterfaceId} from '@aragon/osx-commons-sdk'; +import {expect} from 'chai'; +import {ethers} from 'hardhat'; + +// TODO + +describe('PermissionCondition', async () => { + let condition: PermissionConditionMock; + + before(async () => { + const deployer = (await ethers.getSigners())[0]; + condition = await new PermissionConditionMock__factory(deployer).deploy(); + }); + + // TODO abstract these common tests that also apply to `PermissionConditionUpgradeable` + it.skip('throws an error if the permission is not granted', async () => { + expect(true).to.equal(false); + }); + + it.skip('relays the authorization to `PermissionCondition` if the permission was granted with `grantWithCondition`', async () => { + expect(true).to.equal(false); + }); + + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + expect(await condition.supportsInterface('0xffffffff')).to.be.false; + }); + + it('supports the `IERC165` interface', async () => { + const iface = IERC165__factory.createInterface(); + expect(await condition.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IPermissionCondition` interface', async () => { + const iface = IPermissionCondition__factory.createInterface(); + expect(await condition.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IProtocolVersion` interface', async () => { + const iface = IProtocolVersion__factory.createInterface(); + expect(await condition.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + }); +}); + +describe.skip('PermissionConditionUpgradeable', async () => { + // TODO run the same tests as for `PermissionCondition`. + + it('upgrades', async () => { + expect(true).to.equal(false); + }); + + it('can be reinitialized', async () => { + expect(true).to.equal(false); + }); +}); diff --git a/contracts/test/governance/membership/addresslist.ts b/contracts/test/plugin/extensions/governance/addresslist.ts similarity index 99% rename from contracts/test/governance/membership/addresslist.ts rename to contracts/test/plugin/extensions/governance/addresslist.ts index 5318a9c7..7de237bc 100644 --- a/contracts/test/governance/membership/addresslist.ts +++ b/contracts/test/plugin/extensions/governance/addresslist.ts @@ -1,4 +1,4 @@ -import {AddresslistMock, AddresslistMock__factory} from '../../../typechain'; +import {AddresslistMock, AddresslistMock__factory} from '../../../../typechain'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ethers} from 'hardhat'; diff --git a/contracts/test/plugin/extensions/membership.ts b/contracts/test/plugin/extensions/membership.ts new file mode 100644 index 00000000..70ee36dd --- /dev/null +++ b/contracts/test/plugin/extensions/membership.ts @@ -0,0 +1,9 @@ +import {expect} from 'chai'; + +// TODO + +describe.skip('IMembership', async () => { + it('has the same interface ID as in OSx v1.0.0', async () => { + expect(true).to.equal(false); + }); +}); diff --git a/contracts/test/plugin/extensions/proposal.ts b/contracts/test/plugin/extensions/proposal.ts new file mode 100644 index 00000000..4d4a94eb --- /dev/null +++ b/contracts/test/plugin/extensions/proposal.ts @@ -0,0 +1,76 @@ +import {IERC165__factory, IProposal__factory} from '../../../typechain'; +import {getInterfaceId} from '@aragon/osx-commons-sdk'; +import {expect} from 'chai'; +import {Contract} from 'ethers'; + +// TODO + +describe.skip('IProposal', async () => { + it('has the same interface ID as in OSx v1.0.0', async () => { + expect(true).to.equal(false); + }); +}); + +describe.skip('Proposal', async () => { + let contract: Contract; // TODO create ProposalMock + + before(async () => { + //const deployer = (await ethers.getSigners())[0]; + //contract = await new ProposalMock__factory(deployer).deploy(); + }); + + // TODO abstract these common tests that also apply to `DaoAuthorizableUpgradeable` + it('counts proposals', async () => { + expect(true).to.equal(false); + }); + + it('creates proposalIds', async () => { + expect(true).to.equal(false); + }); + + it('creates proposals', async () => { + expect(true).to.equal(false); + }); + + it('emits the `ProposalCreated` event', async () => { + expect(true).to.equal(false); + }); + + it('executes proposals', async () => { + expect(true).to.equal(false); + }); + + it('emits the `ProposalExecuted` event', async () => { + expect(true).to.equal(false); + }); + + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + expect(await contract.supportsInterface('0xffffffff')).to.be.false; + }); + + it('supports the `IERC165` interface', async () => { + const iface = IERC165__factory.createInterface(); + expect(await contract.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IProposal` interface', async () => { + const iface = IProposal__factory.createInterface(); + expect(await contract.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + }); +}); + +describe.skip('ProposalUpgradeable', async () => { + // TODO run the same tests as for `DaoAuthorizable`. + + it('upgrades', async () => { + expect(true).to.equal(false); + }); + + it('can be reinitialized', async () => { + expect(true).to.equal(false); + }); +}); diff --git a/contracts/test/plugin/placeholder-version.ts b/contracts/test/plugin/placeholder-version.ts deleted file mode 100644 index 70b786d1..00000000 --- a/contracts/test/plugin/placeholder-version.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/contracts/test/plugin/plugin-clonable.ts b/contracts/test/plugin/plugin-clonable.ts new file mode 100644 index 00000000..bd6f283a --- /dev/null +++ b/contracts/test/plugin/plugin-clonable.ts @@ -0,0 +1,55 @@ +import { + IERC165__factory, + IPlugin__factory, + IProtocolVersion__factory, + PluginCloneableMockBuild1, + PluginCloneableMockBuild1__factory, +} from '../../typechain'; +import {osxCommonsContractsVersion} from '../utils/versioning/protocol-version'; +import {getInterfaceId, PluginType} from '@aragon/osx-commons-sdk'; +import {expect} from 'chai'; +import {ethers} from 'hardhat'; + +describe('PluginCloneable', function () { + let plugin: PluginCloneableMockBuild1; + + before(async () => { + const deployer = (await ethers.getSigners())[0]; + plugin = await new PluginCloneableMockBuild1__factory(deployer).deploy(); + }); + + describe('Plugin Type', async () => { + it('returns the current protocol version', async () => { + expect(await plugin.pluginType()).to.equal(PluginType.Cloneable); + }); + }); + + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + expect(await plugin.supportsInterface('0xffffffff')).to.be.false; + }); + + it('supports the `IERC165` interface', async () => { + const iface = IERC165__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 `IProtocolVersion` interface', async () => { + const iface = IProtocolVersion__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); + }); + + describe('Protocol version', async () => { + it('returns the current protocol version', async () => { + expect(await plugin.protocolVersion()).to.deep.equal( + osxCommonsContractsVersion() + ); + }); + }); +}); diff --git a/contracts/test/plugin/plugin-uups-upgradeable.ts b/contracts/test/plugin/plugin-uups-upgradeable.ts new file mode 100644 index 00000000..23b68850 --- /dev/null +++ b/contracts/test/plugin/plugin-uups-upgradeable.ts @@ -0,0 +1,67 @@ +import { + IERC165__factory, + IPlugin__factory, + IProtocolVersion__factory, + PluginUUPSUpgradeableMockBuild1, + PluginUUPSUpgradeableMockBuild1__factory, +} from '../../typechain'; +import {osxCommonsContractsVersion as osxCommonsContractsPackageVersion} from '../utils/versioning/protocol-version'; +import {getInterfaceId, PluginType} from '@aragon/osx-commons-sdk'; +import {expect} from 'chai'; +import {ethers} from 'hardhat'; + +describe('PluginUUPSUpgradeable', function () { + let plugin: PluginUUPSUpgradeableMockBuild1; + + before(async () => { + const deployer = (await ethers.getSigners())[0]; + plugin = await new PluginUUPSUpgradeableMockBuild1__factory( + deployer + ).deploy(); + }); + + describe('Plugin Type', async () => { + it('returns the current protocol version', async () => { + expect(await plugin.pluginType()).to.equal(PluginType.UUPS); + }); + }); + + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + expect(await plugin.supportsInterface('0xffffffff')).to.be.false; + }); + + it('supports the `IERC165` interface', async () => { + const iface = IERC165__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 `IProtocolVersion` interface', async () => { + const iface = IProtocolVersion__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); + }); + + describe('Protocol version', async () => { + it('returns the current protocol version', async () => { + expect(await plugin.protocolVersion()).to.deep.equal( + osxCommonsContractsPackageVersion() + ); + }); + }); + + describe.skip('Upgradeability', async () => { + it('upgrades', async () => { + expect(true).to.equal(false); + }); + + it('can be reinitialized', async () => { + expect(true).to.equal(false); + }); + }); +}); diff --git a/contracts/test/plugin/plugin.ts b/contracts/test/plugin/plugin.ts new file mode 100644 index 00000000..3e616b3f --- /dev/null +++ b/contracts/test/plugin/plugin.ts @@ -0,0 +1,57 @@ +import { + IERC165__factory, + IPlugin__factory, + IProtocolVersion__factory, + PluginMockBuild1, + PluginMockBuild1__factory, +} from '../../typechain'; +import {osxCommonsContractsVersion} from '../utils/versioning/protocol-version'; +import {getInterfaceId, PluginType} from '@aragon/osx-commons-sdk'; +import {expect} from 'chai'; +import {ethers} from 'hardhat'; + +describe('Plugin', function () { + let plugin: PluginMockBuild1; + + before(async () => { + const deployer = (await ethers.getSigners())[0]; + plugin = await new PluginMockBuild1__factory(deployer).deploy( + ethers.constants.AddressZero + ); + }); + + describe('Plugin Type', async () => { + it('returns the current protocol version', async () => { + expect(await plugin.pluginType()).to.equal(PluginType.Constructable); + }); + }); + + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + expect(await plugin.supportsInterface('0xffffffff')).to.be.false; + }); + + it('supports the `IERC165` interface', async () => { + const iface = IERC165__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 `IProtocolVersion` interface', async () => { + const iface = IProtocolVersion__factory.createInterface(); + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; + }); + }); + + describe('Protocol version', async () => { + it('returns the current protocol version', async () => { + expect(await plugin.protocolVersion()).to.deep.equal( + osxCommonsContractsVersion() + ); + }); + }); +}); diff --git a/contracts/test/plugin/setup/plugin-setup.ts b/contracts/test/plugin/setup/plugin-setup.ts new file mode 100644 index 00000000..e3a01fc2 --- /dev/null +++ b/contracts/test/plugin/setup/plugin-setup.ts @@ -0,0 +1,59 @@ +import { + IERC165__factory, + IPluginSetup__factory, + IProtocolVersion__factory, +} from '../../../typechain'; +import {getInterfaceId} from '@aragon/osx-commons-sdk'; +import {expect} from 'chai'; +import {Contract} from 'ethers'; + +// TODO + +describe.skip('IPluginSetup', async () => { + it('has the same interface ID as in OSx v1.0.0', async () => { + expect(true).to.equal(false); + }); +}); + +describe.skip('PluginSetup', async () => { + let contract: Contract; // TODO create ProposalMock + + it('creates ERC1967 proxies', async () => { + // TODO this will likely be refactored with task OS-675 + expect(true).to.equal(false); + }); + + // TODO think about more tests + + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + expect(await contract.supportsInterface('0xffffffff')).to.be.false; + }); + + it('supports the `IERC165` interface', async () => { + const iface = IERC165__factory.createInterface(); + expect(await contract.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IPluginSetup` interface', async () => { + const iface = IPluginSetup__factory.createInterface(); + expect(await contract.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IProtocolVersion` interface', async () => { + const iface = IProtocolVersion__factory.createInterface(); + expect(await contract.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + }); + + it('upgrades', async () => { + expect(true).to.equal(false); + }); + + it('can be reinitialized', async () => { + expect(true).to.equal(false); + }); +}); diff --git a/contracts/test/utils/deployment/clone-factory.ts b/contracts/test/utils/deployment/clone-factory.ts new file mode 100644 index 00000000..653ef3d2 --- /dev/null +++ b/contracts/test/utils/deployment/clone-factory.ts @@ -0,0 +1 @@ +// TODO CloneFactory will be refactored as part of Task OS-794. diff --git a/contracts/test/utils/math/ratio.ts b/contracts/test/utils/math/ratio.ts index 7193d5df..fb525082 100644 --- a/contracts/test/utils/math/ratio.ts +++ b/contracts/test/utils/math/ratio.ts @@ -1,17 +1,15 @@ -import {RatioTest, RatioTest__factory} from '../../../typechain'; +import {RatioMock, RatioMock__factory} from '../../../typechain'; +import {RATIO_BASE, pctToRatio} from '@aragon/osx-commons-sdk'; import {expect} from 'chai'; import {ethers} from 'hardhat'; -export const RATIO_BASE = ethers.BigNumber.from(10).pow(6); // 100% => 10**6 -export const pctToRatio = (x: number) => RATIO_BASE.mul(x).div(100); - describe('Ratio', function () { - let ratio: RatioTest; + let ratio: RatioMock; before(async () => { const signers = await ethers.getSigners(); - const RatioTest = new RatioTest__factory(signers[0]); - ratio = await RatioTest.deploy(); + const RatioMock = new RatioMock__factory(signers[0]); + ratio = await RatioMock.deploy(); }); describe('RATIO_BASE', async () => { @@ -50,3 +48,4 @@ describe('Ratio', function () { }); }); }); +export {pctToRatio}; diff --git a/contracts/test/utils/versioning/protocol-version.ts b/contracts/test/utils/versioning/protocol-version.ts new file mode 100644 index 00000000..22d3081c --- /dev/null +++ b/contracts/test/utils/versioning/protocol-version.ts @@ -0,0 +1,30 @@ +import {version} from '../../../package.json'; +import {ProtocolVersionMock__factory} from '../../../typechain'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {expect} from 'chai'; +import {ethers} from 'hardhat'; + +/** + * Returns the NPM version number from the `osx-commons-contracts` package.json file + */ +export function osxCommonsContractsVersion(): [number, number, number] { + const trimmedVersion = version.split('-')[0]; + const semver = trimmedVersion.split('.'); + return [Number(semver[0]), Number(semver[1]), Number(semver[2])]; +} + +describe('ProtocolVersion', function () { + let signers: SignerWithAddress[]; + before(async () => { + signers = await ethers.getSigners(); + }); + + it('returns the current protocol version that must match the semantic version of the `osx-commons-contracts` package', async () => { + const ProtocolVersionMock = await new ProtocolVersionMock__factory( + signers[0] + ).deploy(); + expect(await ProtocolVersionMock.protocolVersion()).to.deep.equal( + osxCommonsContractsVersion() + ); + }); +}); diff --git a/contracts/test/utils/version-comparison-lib.ts b/contracts/test/utils/versioning/version-comparison-lib.ts similarity index 96% rename from contracts/test/utils/version-comparison-lib.ts rename to contracts/test/utils/versioning/version-comparison-lib.ts index b551721f..9a24b9c4 100644 --- a/contracts/test/utils/version-comparison-lib.ts +++ b/contracts/test/utils/versioning/version-comparison-lib.ts @@ -1,18 +1,18 @@ import { - VersionComparisonLibTest, - VersionComparisonLibTest__factory, -} from '../../typechain'; + VersionComparisonLibMock, + VersionComparisonLibMock__factory, +} from '../../../typechain'; import {expect} from 'chai'; import {ethers} from 'hardhat'; type SemVer = [number, number, number]; describe('VersionComparisonLib', function () { - let cmp: VersionComparisonLibTest; + let cmp: VersionComparisonLibMock; before(async () => { const signers = await ethers.getSigners(); - cmp = await new VersionComparisonLibTest__factory(signers[0]).deploy(); + cmp = await new VersionComparisonLibMock__factory(signers[0]).deploy(); }); describe('eq', async () => { diff --git a/contracts/utils/etherscan.ts b/contracts/utils/etherscan.ts deleted file mode 100644 index b09324e0..00000000 --- a/contracts/utils/etherscan.ts +++ /dev/null @@ -1,67 +0,0 @@ -import fs from 'fs'; -import HRE from 'hardhat'; -//import path from 'path'; -import {file} from 'tmp-promise'; - -export function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -export const verifyContract = async ( - address: string, - constructorArguments: any[] -) => { - try { - const msDelay = 500; // minimum delay between tasks - const times = 2; // number of retries - - // Write a temporal file to host complex parameters for hardhat-etherscan https://github.com/nomiclabs/hardhat/tree/master/packages/hardhat-etherscan#complex-arguments - const {fd, path, cleanup} = await file({ - prefix: 'verify-params-', - postfix: '.js', - }); - fs.writeSync( - fd, - `module.exports = ${JSON.stringify([...constructorArguments])};` - ); - - const params = { - address: address, - constructorArgs: path, - }; - await runTaskWithRetry('verify', params, times, msDelay, cleanup); - } catch (error) { - console.warn(`Verify task error: ${error}`); - } -}; - -export const runTaskWithRetry = async ( - task: string, - params: any, - times: number, - msDelay: number, - cleanup: () => void -) => { - let counter = times; - await delay(msDelay); - - try { - if (times) { - await HRE.run(task, params); - cleanup(); - } else { - cleanup(); - console.error( - 'Errors after all the retries, check the logs for more information.' - ); - } - } catch (error: any) { - counter--; - // This is not the ideal check, but it's all that's possible for now https://github.com/nomiclabs/hardhat/issues/1301 - if (!/already verified/i.test(error.message)) { - console.log(`Retrying attemps: ${counter}.`); - console.error(error.message); - await runTaskWithRetry(task, params, counter, msDelay, cleanup); - } - } -}; diff --git a/contracts/utils/events.ts b/contracts/utils/events.ts deleted file mode 100644 index 089920f8..00000000 --- a/contracts/utils/events.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {ContractTransaction} from 'ethers'; -import {Interface, LogDescription} from 'ethers/lib/utils'; - -export async function findEvent(tx: ContractTransaction, eventName: string) { - const receipt = await tx.wait(); - - const event = (receipt.events || []).find(event => event.event === eventName); - - return event as T | undefined; -} - -export async function findEventTopicLog( - tx: ContractTransaction, - iface: Interface, - eventName: string -): Promise { - const receipt = await tx.wait(); - const topic = iface.getEventTopic(eventName); - const log = receipt.logs.find(x => x.topics[0] === topic); - if (!log) { - throw new Error(`No logs found for the topic of event "${eventName}".`); - } - return iface.parseLog(log) as LogDescription & (T | LogDescription); -} diff --git a/contracts/utils/interfaces.ts b/contracts/utils/interfaces.ts deleted file mode 100644 index 013dd5e9..00000000 --- a/contracts/utils/interfaces.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {ethers as ethersDirect} from 'ethers'; -import {ethers} from 'hardhat'; - -export function getInterfaceId( - contractInterface: ethersDirect.utils.Interface -) { - let interfaceID = ethers.constants.Zero; - const functions: string[] = Object.keys(contractInterface.functions); - for (let i = 0; i < functions.length; i++) { - interfaceID = interfaceID.xor(contractInterface.getSighash(functions[i])); - } - return interfaceID.toHexString(); -} diff --git a/contracts/utils/proxy.ts b/contracts/utils/proxy.ts deleted file mode 100644 index f7df90ba..00000000 --- a/contracts/utils/proxy.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {ContractFactory} from 'ethers'; -import {upgrades} from 'hardhat'; - -type DeployOptions = { - constructurArgs?: unknown[]; - proxyType?: 'uups'; -}; - -// Used to deploy the implementation with the ERC1967 Proxy behind it. -// It is designed this way, because it might be desirable to avoid the OpenZeppelin upgrades package. -// In the future, this function might get replaced. -// NOTE: To avoid lots of changes in the whole test codebase, `deployWithProxy` -// won't automatically call `initialize` and it's the caller's responsibility to do so. -export async function deployWithProxy( - contractFactory: ContractFactory, - options: DeployOptions = {} -): Promise { - // NOTE: taking this out of this file and putting this in each test file's - // before hook seems a good idea for efficiency, though, all test files become - // highly dependent on this package which is undesirable for now. - upgrades.silenceWarnings(); - - return upgrades.deployProxy(contractFactory, [], { - kind: options.proxyType || 'uups', - initializer: false, - unsafeAllow: ['constructor'], - constructorArgs: options.constructurArgs || [], - }) as unknown as Promise; -} diff --git a/contracts/utils/storage.ts b/contracts/utils/storage.ts deleted file mode 100644 index c82313ea..00000000 --- a/contracts/utils/storage.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {defaultAbiCoder} from 'ethers/lib/utils'; -import {ethers} from 'hardhat'; - -// See https://eips.ethereum.org/EIPS/eip-1967 -export const ERC1967_IMPLEMENTATION_SLOT = - '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'; // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) - -export const OZ_INITIALIZED_SLOT_POSITION = 0; - -export async function readStorage( - contractAddress: string, - location: number | string, - types: string[] -): Promise { - return await ethers.provider - .getStorageAt(contractAddress, location) - .then(encoded => defaultAbiCoder.decode(types, encoded)[0]); -} diff --git a/contracts/yarn.lock b/contracts/yarn.lock index bf27e8fa..7963f263 100644 --- a/contracts/yarn.lock +++ b/contracts/yarn.lock @@ -2,16 +2,49 @@ # yarn lockfile v1 -"@aragon/osx-ethers@1.3.0": +"@aragon/osx-commons-sdk@0.0.1-alpha.3": + version "0.0.1-alpha.3" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-sdk/-/osx-commons-sdk-0.0.1-alpha.3.tgz#c8ca71702cc404f7ad82cc327fb2355807a84801" + integrity sha512-7ibaTyNouYiPOQuMYFvfonl4Ts/87XPh2XerrqkdwQAEcdRGEkQjmwAd4XO4cHXC8yJh8u9JCTt604muuRJehA== + dependencies: + "@aragon/osx-ethers" "^1.3.0-rc0.4" + "@aragon/osx-ethers-v1.0.0" "npm:@aragon/osx-ethers@1.2.1" + "@aragon/sdk-ipfs" "^1.1.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/providers" "^5.7.2" + "@ethersproject/wallet" "^5.7.0" + graphql "^16.5.0" + graphql-request "^4.3.0" + ipfs-http-client "^51.0.0" + yup "^1.2.0" + +"@aragon/osx-ethers-v1.0.0@npm:@aragon/osx-ethers@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@aragon/osx-ethers/-/osx-ethers-1.2.1.tgz#a442048137153ed5a3ca4eff3f3927b45a5134b5" + integrity sha512-3Fscq8C9elIktiI6OT7fR5iaAvim+ghU6IUvZF3P/phvWm9roNp/GXAROhA/Vx41NQxeqmfXokgFo6KOWt4drA== + dependencies: + ethers "^5.6.2" + +"@aragon/osx-ethers@^1.3.0-rc0.4": version "1.3.0" resolved "https://registry.yarnpkg.com/@aragon/osx-ethers/-/osx-ethers-1.3.0.tgz#85ddd93f4448789e94154b313f9ddd8069dff568" integrity sha512-zfLAYdgZ3SSifHiyJLkBKmoDw/IEX/yzvw6liUAa/gz7HZOsrwp3JsBJaIwB2epOnGa6vhXyWTKKZ15aJvoMaA== dependencies: ethers "^5.6.2" -"@aragon/osx@aragon/osx#develop": - version "0.1.0" - resolved "https://codeload.github.com/aragon/osx/tar.gz/fcf3fca18b1c32e7678f3e28d828eebaa4e7ed1f" +"@aragon/sdk-ipfs@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@aragon/sdk-ipfs/-/sdk-ipfs-1.1.0.tgz#178ee5ce840ce40b44ba0345dd5068e1b5608f9d" + integrity sha512-2uAh/QPcmaita4AfHYV93lESzAhrmGEZ6CL7pvOH86HTkU6j7LnePvD1ly+x0hxRznTb+zgVgSPPKUn0ArPycw== + dependencies: + "@web-std/fetch" "^4.1.0" + "@web-std/file" "^3.0.2" + "@web-std/form-data" "^3.0.2" + isomorphic-unfetch "^3.1.0" "@aws-crypto/sha256-js@1.2.2": version "1.2.2" @@ -1150,13 +1183,20 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.3.tgz#4804fe9cd39da26eb62fa65c15ea77615a187812" integrity sha512-RsOPImTriV/OE4A9qKjMtk2MnXiuLLbcO3nCXK+kvq4nr0iMfFgpjaX3MPLb6f7+EL1FGSelYvuJMV6REH+ZPQ== -"@types/node@*", "@types/node@>=13.7.0": +"@types/node@*": version "20.8.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.7.tgz#ad23827850843de973096edfc5abc9e922492a25" integrity sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ== dependencies: undici-types "~5.25.1" +"@types/node@>=13.7.0": + version "20.11.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f" + integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ== + dependencies: + undici-types "~5.26.4" + "@types/node@^10.0.3": version "10.17.60" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" @@ -1204,6 +1244,66 @@ dependencies: "@types/node" "*" +"@web-std/blob@^3.0.3": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@web-std/blob/-/blob-3.0.5.tgz#391e652dd3cc370dbb32c828368a3022b4d55c9c" + integrity sha512-Lm03qr0eT3PoLBuhkvFBLf0EFkAsNz/G/AYCzpOdi483aFaVX86b4iQs0OHhzHJfN5C15q17UtDbyABjlzM96A== + dependencies: + "@web-std/stream" "1.0.0" + web-encoding "1.1.5" + +"@web-std/fetch@^4.1.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@web-std/fetch/-/fetch-4.2.1.tgz#692c5545787081217fce3c024708fa8979c21f9c" + integrity sha512-M6sgHDgKegcjuVsq8J6jb/4XvhPGui8uwp3EIoADGXUnBl9vKzKLk9H9iFzrPJ6fSV6zZzFWXPyziBJp9hxzBA== + dependencies: + "@web-std/blob" "^3.0.3" + "@web-std/file" "^3.0.2" + "@web-std/form-data" "^3.0.2" + "@web-std/stream" "^1.0.1" + "@web3-storage/multipart-parser" "^1.0.0" + abort-controller "^3.0.0" + data-uri-to-buffer "^3.0.1" + mrmime "^1.0.0" + +"@web-std/file@^3.0.2": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@web-std/file/-/file-3.0.3.tgz#a29b9164d34155a126d1ab2af5e5867e83c8b098" + integrity sha512-X7YYyvEERBbaDfJeC9lBKC5Q5lIEWYCP1SNftJNwNH/VbFhdHm+3neKOQP+kWEYJmosbDFq+NEUG7+XIvet/Jw== + dependencies: + "@web-std/blob" "^3.0.3" + +"@web-std/form-data@^3.0.2": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@web-std/form-data/-/form-data-3.1.0.tgz#573b40f6296e8bdba31f1bbf2db398f104ef4831" + integrity sha512-WkOrB8rnc2hEK2iVhDl9TFiPMptmxJA1HaIzSdc2/qk3XS4Ny4cCt6/V36U3XmoYKz0Md2YyK2uOZecoZWPAcA== + dependencies: + web-encoding "1.1.5" + +"@web-std/stream@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@web-std/stream/-/stream-1.0.0.tgz#01066f40f536e4329d9b696dc29872f3a14b93c1" + integrity sha512-jyIbdVl+0ZJyKGTV0Ohb9E6UnxP+t7ZzX4Do3AHjZKxUXKMs9EmqnBDQgHF7bEw0EzbQygOjtt/7gvtmi//iCQ== + dependencies: + web-streams-polyfill "^3.1.1" + +"@web-std/stream@^1.0.1": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@web-std/stream/-/stream-1.0.3.tgz#a1df4d4612990d3607f77ad17d0338c7960bbe2e" + integrity sha512-5MIngxWyq4rQiGoDAC2WhjLuDraW8+ff2LD2et4NRY933K3gL8CHlUXrh8ZZ3dC9A9Xaub8c9sl5exOJE58D9Q== + dependencies: + web-streams-polyfill "^3.1.1" + +"@web3-storage/multipart-parser@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz#6b69dc2a32a5b207ba43e556c25cc136a56659c4" + integrity sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw== + +"@zxing/text-encoding@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" + integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1970,6 +2070,13 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" +cross-fetch@^3.1.5: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.0, cross-spawn@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1984,6 +2091,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== +data-uri-to-buffer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== + death@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" @@ -2438,6 +2550,11 @@ evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + fast-base64-decode@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" @@ -2546,6 +2663,15 @@ form-data@^2.2.0: combined-stream "^1.0.6" mime-types "^2.1.12" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -2813,6 +2939,20 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphql-request@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-4.3.0.tgz#b934e08fcae764aa2cdc697d3c821f046cb5dbf2" + integrity sha512-2v6hQViJvSsifK606AliqiNiijb1uwWp6Re7o0RTyH+uRTv/u7Uqm2g4Fjq/LgZIzARB38RZEvVBFOQOVdlBow== + dependencies: + cross-fetch "^3.1.5" + extract-files "^9.0.0" + form-data "^3.0.0" + +graphql@^16.5.0: + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== + handlebars@^4.0.1, handlebars@^4.7.7: version "4.7.8" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" @@ -3232,6 +3372,14 @@ ipfs-utils@^8.1.2, ipfs-utils@^8.1.4: react-native-fetch-api "^2.0.0" stream-to-it "^0.2.2" +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" @@ -3307,6 +3455,13 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -3377,7 +3532,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -3421,7 +3576,7 @@ iso-url@^1.1.5: resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-1.2.1.tgz#db96a49d8d9a64a1c889fc07cc525d093afb1811" integrity sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng== -isomorphic-unfetch@^3.0.0: +isomorphic-unfetch@^3.0.0, isomorphic-unfetch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== @@ -3883,6 +4038,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== +mrmime@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -3932,9 +4092,9 @@ nanoid@3.3.3: integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== nanoid@^3.0.2, nanoid@^3.1.12, nanoid@^3.1.20: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== napi-macros@^2.2.2: version "2.2.2" @@ -3968,7 +4128,7 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12: 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== @@ -4226,6 +4386,11 @@ proper-lockfile@^4.1.1: retry "^0.12.0" signal-exit "^3.0.2" +property-expr@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + protobufjs@^6.10.2: version "6.11.4" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" @@ -4971,6 +5136,11 @@ timeout-abort-controller@^1.1.1: abort-controller "^3.0.0" retimer "^2.0.0" +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + tmp-promise@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" @@ -5004,6 +5174,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -5090,6 +5265,11 @@ type-fest@^0.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== +type-fest@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + typechain@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" @@ -5199,6 +5379,11 @@ undici-types@~5.25.1: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^5.14.0: version "5.26.4" resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.4.tgz#dc861c35fb53ae025a173a790d984aa9b2e279a1" @@ -5243,6 +5428,17 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +util@^0.12.3: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -5258,6 +5454,20 @@ varint@^6.0.0: resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== +web-encoding@1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" + integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA== + dependencies: + util "^0.12.3" + optionalDependencies: + "@zxing/text-encoding" "0.9.0" + +web-streams-polyfill@^3.1.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz#32e26522e05128203a7de59519be3c648004343b" + integrity sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ== + web3-utils@^1.3.6: version "1.10.3" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.3.tgz#f1db99c82549c7d9f8348f04ffe4e0188b449714" @@ -5296,7 +5506,7 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.11: +which-typed-array@^1.1.11, which-typed-array@^1.1.2: version "1.1.13" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== @@ -5435,6 +5645,16 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yup@^1.2.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.3.3.tgz#d2f6020ad1679754c5f8178a29243d5447dead04" + integrity sha512-v8QwZSsHH2K3/G9WSkp6mZKO+hugKT1EmnMqLNUcfu51HU9MDyhlETT/JgtzprnrnQHPWsjc6MUDMBp/l9fNnw== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0" + zksync-web3@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.14.3.tgz#64ac2a16d597464c3fc4ae07447a8007631c57c9" diff --git a/package.json b/package.json index b1ef6982..845e052a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "@aragon/osx-commons-repo", + "version": "1.0.0", "license": "AGPL-3.0-or-later", "private": true, "devDependencies": { diff --git a/sdk/package.json b/sdk/package.json index ed74eb80..4923dc29 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,7 +1,7 @@ { "name": "@aragon/osx-commons-sdk", "author": "Aragon Association", - "version": "0.0.1", + "version": "0.0.1-alpha.3", "license": "MIT", "main": "dist/index.js", "module": "dist/osx-commons-sdk.esm.js", @@ -49,20 +49,22 @@ "husky": "^7.0.4", "size-limit": "^7.0.8", "tslib": "^2.3.1", - "typescript": "^5.1.0" + "typescript": "5.0.4" }, "dependencies": { "@aragon/osx-ethers": "^1.3.0-rc0.4", "@aragon/osx-ethers-v1.0.0": "npm:@aragon/osx-ethers@1.2.1", "@aragon/sdk-ipfs": "^1.1.0", - "@ethersproject/abstract-signer": "^5.5.0", - "@ethersproject/bignumber": "^5.6.0", - "@ethersproject/constants": "^5.6.0", - "@ethersproject/contracts": "^5.5.0", - "@ethersproject/providers": "^5.5.0", - "@ethersproject/wallet": "^5.6.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/contracts": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/providers": "^5.7.2", + "@ethersproject/wallet": "^5.7.0", "graphql": "^16.5.0", "graphql-request": "^4.3.0", + "ipfs-http-client": "^51.0.0", "yup": "^1.2.0" } } diff --git a/sdk/src/bitmap.ts b/sdk/src/bitmap.ts new file mode 100644 index 00000000..f0f6e1b3 --- /dev/null +++ b/sdk/src/bitmap.ts @@ -0,0 +1,11 @@ +import {BigNumber} from 'ethers'; + +export function flipBit(index: number, num: BigNumber): BigNumber { + const mask = BigNumber.from(1).shl(index & 0xff); + return num.xor(mask); +} + +export function getBit(index: number, num: BigNumber): boolean { + const mask = BigNumber.from(1).shl(index & 0xff); + return !num.and(mask).eq(0); +} diff --git a/sdk/src/constants.ts b/sdk/src/constants.ts index f1711751..c9aa74cd 100644 --- a/sdk/src/constants.ts +++ b/sdk/src/constants.ts @@ -1,4 +1,5 @@ import {NetworkDeployment} from './internal'; +import {TIME} from './time'; import {ProposalMetadata, SupportedNetwork, SupportedVersion} from './types'; import {activeContractsList} from '@aragon/osx-ethers'; import {activeContractsList as activeContractsListV1_0_0} from '@aragon/osx-ethers-v1.0.0'; @@ -8,7 +9,7 @@ import {toUtf8Bytes} from '@ethersproject/strings'; /** Timeout that will be applied to operations involving * many fetch requests that could take a long time */ -export const MULTI_FETCH_TIMEOUT = 7 * 1000; +export const MULTI_FETCH_TIMEOUT = 7 * TIME.SECOND_MS; type GraphqlNetworks = | 'mainnet' diff --git a/sdk/src/errors.ts b/sdk/src/errors.ts index dca7bd8f..0da2c3e9 100644 --- a/sdk/src/errors.ts +++ b/sdk/src/errors.ts @@ -1,3 +1,5 @@ +import {ContractTransaction} from '@ethersproject/contracts'; + class SdkError extends Error { public cause?: Error | string; constructor(message: string, cause?: any) { @@ -357,3 +359,12 @@ export class InvalidPermissionOperationType extends SdkError { super('Invalid permission operation', cause); } } + +export class EventNotFoundError extends SdkError { + constructor(eventName: string, tx: ContractTransaction) { + super( + `Event "${eventName}" could not be found in transaction ${tx.hash}.`, + tx + ); + } +} diff --git a/sdk/src/events.ts b/sdk/src/events.ts new file mode 100644 index 00000000..20121efc --- /dev/null +++ b/sdk/src/events.ts @@ -0,0 +1,65 @@ +import {EventNotFoundError} from './errors'; +import {ContractTransaction} from 'ethers'; +import {Interface, LogDescription} from 'ethers/lib/utils'; + +/** + * Finds a typed event in transaction given the event name + * + * @export + * @param {ContractReceipt} tx + * @param {string} eventName + * @return {*} {(T)} + */ +export async function findEvent(tx: ContractTransaction, eventName: string) { + const receipt = await tx.wait(); + + const event = (receipt.events || []).find(event => event.event === eventName); + + if (!event) { + throw new EventNotFoundError(eventName, tx); + } + + return event as T; +} + +/** + * Finds a log in a transaction given the interface of the emitting contract and the event name + * + * @export + * @param {ContractTransaction} tx + * @param {Interface} iface + * @param {string} eventName + * @return {*} {(LogDescription | undefined)} + */ +export async function findEventTopicLog( + tx: ContractTransaction, + iface: Interface, + eventName: string +): Promise { + const receipt = await tx.wait(); + const topic = iface.getEventTopic(eventName); + const log = receipt.logs.find(x => x.topics[0] === topic); + if (!log) { + throw new EventNotFoundError(eventName, tx); + } + return iface.parseLog(log) as LogDescription & (T | LogDescription); +} +export const IPROPOSAL_EVENTS = { + PROPOSAL_CREATED: 'ProposalCreated', + PROPOSAL_EXECUTED: 'ProposalExecuted', +}; + +export const IDAO_EVENTS = { + METADATA_SET: 'MetadataSet', + EXECUTED: 'Executed', + DEPOSITED: 'Deposited', + STANDARD_CALLBACK_REGISTERED: 'StandardCallbackRegistered', + TRUSTED_FORWARDER_SET: 'TrustedForwarderSet', + NEW_URI: 'NewURI', +}; + +export const IMEMBERSHIP_EVENTS = { + MEMBERS_ADDED: 'MembersAdded', + MEMBERS_REMOVED: 'MembersRemoved', + MEMBERSHIP_CONTRACT_ANNOUNCED: 'MembershipContractAnnounced', +}; diff --git a/sdk/src/index.ts b/sdk/src/index.ts index fe957d67..119d9ad3 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -7,6 +7,16 @@ export * from './utils'; export * from './schemas'; export * from './validation'; export * from './multiuri'; +export * from './metadata'; +export * from './interfaces'; export * from './encoding'; export * from './promises'; export * from './errors'; + +// From OSX +export * from './bitmap'; +export * from './events'; +export * from './math'; +export * from './permission'; +export * from './proposal'; +export * from './time'; diff --git a/sdk/src/interfaces.ts b/sdk/src/interfaces.ts new file mode 100644 index 00000000..8e6825ac --- /dev/null +++ b/sdk/src/interfaces.ts @@ -0,0 +1,18 @@ +import {Interface} from '@ethersproject/abi'; +import {Zero} from '@ethersproject/constants'; + +/** + * Gets the interfaceId of a given interface + * + * @export + * @param {Interface} iface + * @return {*} {string} + */ +export function getInterfaceId(iface: Interface): string { + let interfaceId = Zero; + const functions: string[] = Object.keys(iface.functions); + for (const func of functions) { + interfaceId = interfaceId.xor(iface.getSighash(func)); + } + return interfaceId.toHexString(); +} diff --git a/contracts/utils/ipfs.ts b/sdk/src/ipfs.ts similarity index 100% rename from contracts/utils/ipfs.ts rename to sdk/src/ipfs.ts diff --git a/sdk/src/math.ts b/sdk/src/math.ts new file mode 100644 index 00000000..c693b32b --- /dev/null +++ b/sdk/src/math.ts @@ -0,0 +1,4 @@ +import {BigNumber} from '@ethersproject/bignumber'; + +export const RATIO_BASE = BigNumber.from(10).pow(6); // 100% => 10**6 +export const pctToRatio = (x: number) => RATIO_BASE.mul(x).div(100); diff --git a/sdk/src/metadata.ts b/sdk/src/metadata.ts new file mode 100644 index 00000000..01eb9365 --- /dev/null +++ b/sdk/src/metadata.ts @@ -0,0 +1,33 @@ +import {MetadataAbiInput} from './types'; + +/** + * Gets the named types from a metadata abi input + * + * @export + * @param {MetadataAbiInput[]} [inputs=[]] + * @return {*} {string[]} + */ +export function getNamedTypesFromMetadata( + inputs: MetadataAbiInput[] = [] +): string[] { + return inputs.map(input => { + if (input.type.startsWith('tuple')) { + const tupleResult = getNamedTypesFromMetadata(input.components).join( + ', ' + ); + + let tupleString = `tuple(${tupleResult})`; + + if (input.type.endsWith('[]')) { + tupleString = tupleString.concat('[]'); + } + + return tupleString; + } else if (input.type.endsWith('[]')) { + const baseType = input.type.slice(0, -2); + return `${baseType}[] ${input.name}`; + } else { + return `${input.type} ${input.name}`; + } + }); +} diff --git a/sdk/src/permission.ts b/sdk/src/permission.ts new file mode 100644 index 00000000..a0440f39 --- /dev/null +++ b/sdk/src/permission.ts @@ -0,0 +1,54 @@ +import {id} from '@ethersproject/hash'; + +export enum Operation { + Grant = 0, + Revoke = 1, + GrantWithCondition = 2, +} + +export const DAO_PERMISSIONS = { + ROOT_PERMISSION_ID: id('ROOT_PERMISSION'), + EXECUTE_PERMISSION_ID: id('EXECUTE_PERMISSION'), + UPGRADE_DAO_PERMISSION_ID: id('UPGRADE_DAO_PERMISSION'), + SET_SIGNATURE_VALIDATOR_PERMISSION_ID: id( + 'SET_SIGNATURE_VALIDATOR_PERMISSION' + ), + SET_TRUSTED_FORWARDER_PERMISSION_ID: id('SET_TRUSTED_FORWARDER_PERMISSION'), + SET_METADATA_PERMISSION_ID: id('SET_METADATA_PERMISSION'), + REGISTER_STANDARD_CALLBACK_PERMISSION_ID: id( + 'REGISTER_STANDARD_CALLBACK_PERMISSION' + ), + VALIDATE_SIGNATURE_PERMISSION_ID: id('VALIDATE_SIGNATURE_PERMISSION'), +}; + +export const PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS = { + UPGRADE_PLUGIN_PERMISSION_ID: id('UPGRADE_PLUGIN_PERMISSION'), +}; + +export const PLUGIN_REPO_PERMISSIONS = { + MAINTAINER_PERMISSION_ID: id('MAINTAINER_PERMISSION'), + UPGRADE_REPO_PERMISSION_ID: id('UPGRADE_REPO_PERMISSION'), +}; + +export const PLUGIN_SETUP_PROCESSOR_PERMISSIONS = { + APPLY_INSTALLATION_PERMISSION_ID: id('APPLY_INSTALLATION_PERMISSION'), + APPLY_UPDATE_PERMISSION_ID: id('APPLY_UPDATE_PERMISSION'), + APPLY_UNINSTALLATION_PERMISSION_ID: id('APPLY_UNINSTALLATION_PERMISSION'), +}; + +export const ENS_REGISTRAR_PERMISSIONS = { + UPGRADE_REGISTRAR_PERMISSION_ID: id('UPGRADE_REGISTRAR_PERMISSION'), + REGISTER_ENS_SUBDOMAIN_PERMISSION_ID: id('REGISTER_ENS_SUBDOMAIN_PERMISSION'), +}; + +export const PLUGIN_REGISTRY_PERMISSIONS = { + UPGRADE_REGISTRY_PERMISSION_ID: id('UPGRADE_REGISTRY_PERMISSION'), + REGISTER_PLUGIN_REPO_PERMISSION_ID: id('REGISTER_PLUGIN_REPO_PERMISSION'), + ENS_REGISTRAR_PERMISSIONS, +}; + +export const DAO_REGISTRY_PERMISSIONS = { + UPGRADE_REGISTRY_PERMISSION_ID: id('UPGRADE_REGISTRY_PERMISSION'), + REGISTER_DAO_PERMISSION_ID: id('REGISTER_DAO_PERMISSION'), + ENS_REGISTRAR_PERMISSIONS, +}; diff --git a/sdk/src/proposal.ts b/sdk/src/proposal.ts new file mode 100644 index 00000000..98f3c472 --- /dev/null +++ b/sdk/src/proposal.ts @@ -0,0 +1,4 @@ +export function proposalIdToBytes32(proposalId: number): string { + const hex = proposalId.toString(16); + return `0x${'0'.repeat(64 - hex.length)}${hex}`; +} diff --git a/sdk/src/time.ts b/sdk/src/time.ts new file mode 100644 index 00000000..b7f697c6 --- /dev/null +++ b/sdk/src/time.ts @@ -0,0 +1,37 @@ +export const TIME = { + SECOND: 1, + get MINUTE() { + return 60 * this.SECOND; + }, + get HOUR() { + return 60 * this.MINUTE; + }, + get DAY() { + return 24 * this.HOUR; + }, + get WEEK() { + return 7 * this.DAY; + }, + get YEAR() { + return 365 * this.DAY; + }, + + toMilliseconds(seconds: number) { + return seconds * 1000; + }, + get SECOND_MS() { + return this.toMilliseconds(this.SECOND); + }, + get HOUR_MS() { + return this.toMilliseconds(this.HOUR); + }, + get DAY_MS() { + return this.toMilliseconds(this.DAY); + }, + get WEEK_MS() { + return this.toMilliseconds(this.WEEK); + }, + get YEAR_MS() { + return this.toMilliseconds(this.YEAR); + }, +}; diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 05fd866b..1c9ed1a4 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -48,6 +48,15 @@ export interface PluginInstallItem { data: Uint8Array; } +/** + * Contains the supported plugin types + */ +export enum PluginType { + UUPS = 0, + Cloneable = 1, + Constructable = 2, +} + export type GasFeeEstimation = { average: bigint; max: bigint; diff --git a/sdk/src/utils.ts b/sdk/src/utils.ts index 24a4ab57..6c66fd92 100644 --- a/sdk/src/utils.ts +++ b/sdk/src/utils.ts @@ -8,15 +8,16 @@ import { PluginUpdatePreparationError, UnsupportedNetworkError, } from './errors'; +import {findEventTopicLog} from './events'; import { IClientGraphQLCore, IClientWeb3Core, SubgraphPluginInstallation, } from './internal'; import {QueryIPlugin} from './internal/graphql-queries'; +import {getNamedTypesFromMetadata} from './metadata'; import { GasFeeEstimation, - MetadataAbiInput, PrepareInstallationParams, PrepareInstallationStep, PrepareInstallationStepValue, @@ -34,35 +35,12 @@ import { import {FunctionFragment, Interface} from '@ethersproject/abi'; import {defaultAbiCoder} from '@ethersproject/abi'; import {isAddress} from '@ethersproject/address'; -import {Zero} from '@ethersproject/constants'; -import {ContractReceipt} from '@ethersproject/contracts'; -import {id} from '@ethersproject/hash'; import {Network} from '@ethersproject/networks'; import { getNetwork as ethersGetNetwork, - Log, Networkish, } from '@ethersproject/providers'; -/** - * Finds a log in a receipt given the event name - * - * @export - * @param {ContractReceipt} receipt - * @param {Interface} iface - * @param {string} eventName - * @return {*} {(Log | undefined)} - */ -export function findLog( - receipt: ContractReceipt, - iface: Interface, - eventName: string -): Log | undefined { - return receipt.logs.find( - log => log.topics[0] === id(iface.getEvent(eventName).format('sighash')) - ); -} - /** * Gets a function fragment from encoded data * @@ -80,38 +58,6 @@ export function getFunctionFragment( return iface.getFunction(hexBytes.substring(0, 10)); } -/** - * Gets the named types from a metadata abi input - * - * @export - * @param {MetadataAbiInput[]} [inputs=[]] - * @return {*} {string[]} - */ -export function getNamedTypesFromMetadata( - inputs: MetadataAbiInput[] = [] -): string[] { - return inputs.map(input => { - if (input.type.startsWith('tuple')) { - const tupleResult = getNamedTypesFromMetadata(input.components).join( - ', ' - ); - - let tupleString = `tuple(${tupleResult})`; - - if (input.type.endsWith('[]')) { - tupleString = tupleString.concat('[]'); - } - - return tupleString; - } else if (input.type.endsWith('[]')) { - const baseType = input.type.slice(0, -2); - return `${baseType}[] ${input.name}`; - } else { - return `${input.type} ${input.name}`; - } - }); -} - /** * Gets the named types from a metadata abi input * @@ -215,15 +161,13 @@ export async function* prepareGenericInstallation( txHash: tx.hash, }; - const receipt = await tx.wait(); - const pspContractInterface = PluginSetupProcessor__factory.createInterface(); - const log = findLog(receipt, pspContractInterface, 'InstallationPrepared'); - if (!log) { - throw new PluginInstallationPreparationError(); - } - const parsedLog = pspContractInterface.parseLog(log); - const pluginAddress = parsedLog.args['plugin']; - const preparedSetupData = parsedLog.args['preparedSetupData']; + const event = await findEventTopicLog( + tx, + PluginSetupProcessor__factory.createInterface(), + 'InstallationPrepared' + ); + const pluginAddress = event.args['plugin']; + const preparedSetupData = event.args['preparedSetupData']; if (!(pluginAddress || preparedSetupData)) { throw new PluginInstallationPreparationError(); } @@ -346,16 +290,15 @@ export async function* prepareGenericUpdate( key: PrepareUpdateStep.PREPARING, txHash: tx.hash, }; - const receipt = await tx.wait(); - const pspContractInterface = PluginSetupProcessor__factory.createInterface(); - const log = findLog(receipt, pspContractInterface, 'UpdatePrepared'); - if (!log) { - throw new PluginUpdatePreparationError(); - } - const parsedLog = pspContractInterface.parseLog(log); - const versionTag = parsedLog.args['versionTag']; - const preparedSetupData = parsedLog.args['preparedSetupData']; - const initData = parsedLog.args['initData']; + + const event = await findEventTopicLog( + tx, + PluginSetupProcessor__factory.createInterface(), + 'UpdatePrepared' + ); + const versionTag = event.args['versionTag']; + const preparedSetupData = event.args['preparedSetupData']; + const initData = event.args['initData']; if ( !versionTag || versionTag.build !== params.newVersion.build || @@ -411,19 +354,3 @@ export function getNetwork(networkish: Networkish): Network { } return network; } - -/** - * Gets the interfaceId of a given interface - * - * @export - * @param {Interface} iface - * @return {*} {string} - */ -export function getInterfaceId(iface: Interface): string { - let interfaceId = Zero; - const functions: string[] = Object.keys(iface.functions); - for (const func of functions) { - interfaceId = interfaceId.xor(iface.getSighash(func)); - } - return interfaceId.toHexString(); -} diff --git a/sdk/test/unit/interfaces.test.ts b/sdk/test/unit/interfaces.test.ts new file mode 100644 index 00000000..1e078cbe --- /dev/null +++ b/sdk/test/unit/interfaces.test.ts @@ -0,0 +1,11 @@ +import {getInterfaceId} from '../../src/interfaces'; +import {ERC165_ABI} from '../constants'; +import {Interface} from '@ethersproject/abi'; + +describe('getInterfaceId', () => { + it('should return the interface id for an ERC165 contract', () => { + const result = getInterfaceId(new Interface(ERC165_ABI)); + // defined here: https://eips.ethereum.org/EIPS/eip-165 + expect(result).toEqual('0x01ffc9a7'); + }); +}); diff --git a/sdk/test/unit/metadata.test.ts b/sdk/test/unit/metadata.test.ts new file mode 100644 index 00000000..8b0a6d80 --- /dev/null +++ b/sdk/test/unit/metadata.test.ts @@ -0,0 +1,13 @@ +import {getNamedTypesFromMetadata} from '../../src/metadata'; +import {TEST_ABI} from '../constants'; + +describe('Utils', () => { + describe('getNamedTypesFromMetadata', () => { + it('test abi with recursion and multiple types', () => { + const result = getNamedTypesFromMetadata(TEST_ABI); + expect(result).toEqual([ + 'tuple(address b1, tuple(uint256 c1, tuple(address d1, tuple(address[] e1, tuple(uint32 f1, tuple(uint256 g1, uint256 g2))))))', + ]); + }); + }); +}); diff --git a/sdk/test/unit/utils.test.ts b/sdk/test/unit/utils.test.ts deleted file mode 100644 index e9860b0b..00000000 --- a/sdk/test/unit/utils.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {getInterfaceId, getNamedTypesFromMetadata} from '../../src'; -import {ERC165_ABI, TEST_ABI} from '../constants'; -import {Interface} from '@ethersproject/abi'; - -describe('Utils', () => { - describe('getNamedTypesFromMetadata', () => { - it('test abi with recursion and multiple types', () => { - const result = getNamedTypesFromMetadata(TEST_ABI); - expect(result).toEqual([ - 'tuple(address b1, tuple(uint256 c1, tuple(address d1, tuple(address[] e1, tuple(uint32 f1, tuple(uint256 g1, uint256 g2))))))', - ]); - }); - }); - describe('getInterfaceId', () => { - it('should return the interface id for an ERC165 contract', () => { - const result = getInterfaceId(new Interface(ERC165_ABI)); - // defined here: https://eips.ethereum.org/EIPS/eip-165 - expect(result).toEqual('0x01ffc9a7'); - }); - }); -}); diff --git a/sdk/yarn.lock b/sdk/yarn.lock index 50740b3f..591c2912 100644 --- a/sdk/yarn.lock +++ b/sdk/yarn.lock @@ -1089,7 +1089,7 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/web" "^5.7.0" -"@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.5.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== @@ -1126,7 +1126,7 @@ "@ethersproject/bytes" "^5.7.0" "@ethersproject/properties" "^5.7.0" -"@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.6.0", "@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== @@ -1142,14 +1142,14 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/constants@5.7.0", "@ethersproject/constants@^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.7.0", "@ethersproject/contracts@^5.5.0": +"@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== @@ -1252,7 +1252,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.5.0": +"@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== @@ -1360,7 +1360,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/wallet@5.7.0", "@ethersproject/wallet@^5.6.0": +"@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== @@ -1422,6 +1422,21 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== +"@ipld/dag-cbor@^6.0.5": + version "6.0.15" + resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-6.0.15.tgz#aebe7a26c391cae98c32faedb681b1519e3d2372" + integrity sha512-Vm3VTSTwlmGV92a3C5aeY+r2A18zbH2amehNhsX8PBa3muXICaWrN8Uri85A5hLH7D7ElhE8PdjxD6kNqUmTZA== + dependencies: + cborg "^1.5.4" + multiformats "^9.5.4" + +"@ipld/dag-pb@^2.1.3": + version "2.1.18" + resolved "https://registry.yarnpkg.com/@ipld/dag-pb/-/dag-pb-2.1.18.tgz#12d63e21580e87c75fd1a2c62e375a78e355c16f" + integrity sha512-ZBnf2fuX9y3KccADURG5vb9FaOeMjFkCrNysB0PtftME/4iCTjxfaLoNq/IAh5fTqUOMXvryN6Jyka4ZGuMLIg== + dependencies: + multiformats "^9.5.4" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1699,6 +1714,59 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@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" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@rollup/plugin-babel@^6.0.3": version "6.0.4" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz#bd698e351fa9aa9619fcae780aea2a603d98e4c4" @@ -1938,11 +2006,21 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + "@types/minimatch@*": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== +"@types/minimatch@^3.0.4": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + "@types/node@*", "@types/node@^20.10.0": version "20.10.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.0.tgz#16ddf9c0a72b832ec4fcce35b8249cf149214617" @@ -1950,6 +2028,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@>=13.7.0": + version "20.10.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" + integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== + dependencies: + undici-types "~5.26.4" + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -2259,6 +2344,14 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +any-signal@^2.1.0, any-signal@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/any-signal/-/any-signal-2.1.2.tgz#8d48270de0605f8b218cf9abe8e9c6a0e7418102" + integrity sha512-B+rDnWasMi/eWcajPcCWSlYc7muXOrcYrqgyzcdKisl2H/WTlQ0gip1KyQfr0ZlxJdsuWCj/LWwQm7fhyhRfIQ== + dependencies: + abort-controller "^3.0.0" + native-abort-controller "^1.0.3" + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -2553,6 +2646,22 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +bl@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-5.1.0.tgz#183715f678c7188ecef9fe475d90209400624273" + integrity sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ== + dependencies: + buffer "^6.0.3" + inherits "^2.0.4" + readable-stream "^3.4.0" + +blob-to-it@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/blob-to-it/-/blob-to-it-1.0.4.tgz#f6caf7a4e90b7bb9215fa6a318ed6bd8ad9898cb" + integrity sha512-iCmk0W4NdbrWgRRuxOriU8aM5ijeVLI61Zulsmg/lUHNr7pYjoj+U77opLefNagevtrrbMt3JQ5Qip7ar178kA== + dependencies: + browser-readablestream-to-it "^1.0.3" + bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -2590,6 +2699,11 @@ brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +browser-readablestream-to-it@^1.0.1, browser-readablestream-to-it@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/browser-readablestream-to-it/-/browser-readablestream-to-it-1.0.3.tgz#ac3e406c7ee6cdf0a502dd55db33bab97f7fba76" + integrity sha512-+12sHB+Br8HIh6VAMVEG5r3UXCyESIgDW7kzk3BjIXa43DVqVwL7GC5TW3jeh+72dtcH99pPVpw0X8i0jt+/kw== + browserslist@^4.21.9, browserslist@^4.22.1: version "4.22.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" @@ -2627,6 +2741,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.1, buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -2666,6 +2788,11 @@ caniuse-lite@^1.0.30001541: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz#eaa8bbc58c0cbccdcb7b41186df39dd2ba591889" integrity sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg== +cborg@^1.5.4: + version "1.10.2" + resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.2.tgz#83cd581b55b3574c816f82696307c7512db759a1" + integrity sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug== + chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -2925,7 +3052,7 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, 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== @@ -3035,6 +3162,15 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dns-over-http-resolver@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/dns-over-http-resolver/-/dns-over-http-resolver-1.2.3.tgz#194d5e140a42153f55bb79ac5a64dd2768c36af9" + integrity sha512-miDiVSI6KSNbi4SVifzO/reD8rMnxgrlnkrlkugOLQpWQTe2qMdHsZp5DmfKjxNE+/T3VAAYLQUZMv9SMr6+AA== + dependencies: + debug "^4.3.1" + native-fetch "^3.0.0" + receptacle "^1.3.2" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -3127,6 +3263,13 @@ dts-cli@^2.0.3: type-fest "^2.19.0" typescript "^5.0.2" +electron-fetch@^1.7.2: + version "1.9.1" + resolved "https://registry.yarnpkg.com/electron-fetch/-/electron-fetch-1.9.1.tgz#e28bfe78d467de3f2dec884b1d72b8b05322f30f" + integrity sha512-M9qw6oUILGVrcENMSRRefE1MbHPIz0h79EKIeJWK9v563aT9Qkh8aEHPO1H5vi970wPirNY+jO9OpFoLiMsMGA== + dependencies: + encoding "^0.1.13" + electron-to-chromium@^1.4.535: version "1.4.593" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.593.tgz#f71b157f7382f3d3a164f73ff2da772d4b3fd984" @@ -3160,6 +3303,13 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -3180,6 +3330,11 @@ entities@^4.4.0: resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +err-code@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920" + integrity sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -3772,6 +3927,11 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== +fast-fifo@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + fast-glob@^3.0.3: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" @@ -3960,6 +4120,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-iterator@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-iterator/-/get-iterator-1.0.2.tgz#cd747c02b4c084461fac14f48f6b45a80ed25c82" + integrity sha512-v+dm9bNVfOYsY1OrhaCrmyOcYoSeVvbt+hHZ0Au+T+p1y+0Uyj9aMaGIeUTT6xdpRbWzDeYKvfOslPhggQMcsg== + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -4239,14 +4404,14 @@ husky@^7.0.4: resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== -iconv-lite@0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -4300,6 +4465,26 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +interface-datastore@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/interface-datastore/-/interface-datastore-5.2.0.tgz#9341b13a8babbfb23961ca7c732c0263f85e5007" + integrity sha512-nthO4C4BMJM2j9x/mT2KFa/g/sbcY8yf9j/kOBgli3u5mq9ZdPvQyDxi0OhKzr4JmoM81OYh5xcFjyebquqwvA== + dependencies: + err-code "^3.0.1" + interface-store "^1.0.2" + ipfs-utils "^8.1.2" + it-all "^1.0.2" + it-drain "^1.0.1" + it-filter "^1.0.2" + it-take "^1.0.1" + nanoid "^3.0.2" + uint8arrays "^3.0.0" + +interface-store@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/interface-store/-/interface-store-1.0.2.tgz#1ebd6cbbae387039a3a2de0cae665da52474800f" + integrity sha512-rUBLYsgoWwxuUpnQoSUr+DR/3dH3reVeIu5aOHFZK31lAexmb++kR6ZECNRgrx6WvoaM3Akdo0A7TDrqgCzZaQ== + internal-slot@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" @@ -4314,6 +4499,101 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +ip-regex@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== + +ipfs-core-types@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/ipfs-core-types/-/ipfs-core-types-0.6.1.tgz#d28318e38578d1f95ef1d97e0e456c235bf2c283" + integrity sha512-2SHaEIb52JGzLikiIq0Y665Qaop0q8ITOI41Z1VfPG9GJNiAl1uyasaKffNIFlq+RQFXVup3V0g1R04engHzEA== + dependencies: + interface-datastore "^5.0.0" + multiaddr "^10.0.0" + multiformats "^9.4.1" + +ipfs-core-utils@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/ipfs-core-utils/-/ipfs-core-utils-0.9.1.tgz#d8aad52e6e77c4e9f4c212055e1c04d16f95b566" + integrity sha512-Odu/9prSB2wnJE1Qm/hCm6cwvI5fIHHef+wJiVvTEs1MApANX4BA6uW5BNDJfVErFdn6mL6yRul5tpDrR3Hwfg== + dependencies: + any-signal "^2.1.2" + blob-to-it "^1.0.1" + browser-readablestream-to-it "^1.0.1" + err-code "^3.0.1" + ipfs-core-types "^0.6.1" + ipfs-unixfs "^5.0.0" + ipfs-utils "^8.1.4" + it-all "^1.0.4" + it-map "^1.0.4" + it-peekable "^1.0.2" + multiaddr "^10.0.0" + multiaddr-to-uri "^8.0.0" + multiformats "^9.4.1" + parse-duration "^1.0.0" + timeout-abort-controller "^1.1.1" + uint8arrays "^2.1.6" + +ipfs-http-client@^51.0.0: + version "51.0.1" + resolved "https://registry.yarnpkg.com/ipfs-http-client/-/ipfs-http-client-51.0.1.tgz#c4b64d0d032b83a4d5fa3ca7a9793e6d3233fb4c" + integrity sha512-qNwD64XWn2SOndLBWr68y/ldPl7dMiPp/ACcb2myExb0qO9HrHpXVc0lCXd00Eo4cH8lb3tIVDn8xYDh5FYZ0Q== + dependencies: + "@ipld/dag-cbor" "^6.0.5" + "@ipld/dag-pb" "^2.1.3" + abort-controller "^3.0.0" + any-signal "^2.1.2" + debug "^4.1.1" + err-code "^3.0.1" + form-data "^4.0.0" + ipfs-core-types "^0.6.1" + ipfs-core-utils "^0.9.1" + ipfs-utils "^8.1.4" + it-first "^1.0.6" + it-last "^1.0.4" + it-map "^1.0.4" + it-tar "^3.0.0" + it-to-stream "^1.0.0" + merge-options "^3.0.4" + multiaddr "^10.0.0" + multiformats "^9.4.1" + nanoid "^3.1.12" + native-abort-controller "^1.0.3" + parse-duration "^1.0.0" + stream-to-it "^0.2.2" + uint8arrays "^2.1.6" + +ipfs-unixfs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ipfs-unixfs/-/ipfs-unixfs-5.0.0.tgz#f4019050a9c1722615edae9e9ef91a5383f0123b" + integrity sha512-6FpBtsxB/9R7o7LGmSMaIoYkWO/cK+JYcj22QYIZaiA5dlk28Dvqme18bA43A7UKEc0M8I7mwD92ZXrlhJ/C7Q== + dependencies: + err-code "^3.0.1" + protobufjs "^6.10.2" + +ipfs-utils@^8.1.2, ipfs-utils@^8.1.4: + version "8.1.6" + resolved "https://registry.yarnpkg.com/ipfs-utils/-/ipfs-utils-8.1.6.tgz#431cb1711e3b666fbc7e4ff830c758e2527da308" + integrity sha512-V/cwb6113DrDhrjDTWImA6+zmJbpdbUkxdxmEQO7it8ykV76bBmzU1ZXSM0QR0qxGy9VW8dkUlPAC2K10VgSmw== + dependencies: + abort-controller "^3.0.0" + any-signal "^2.1.0" + buffer "^6.0.1" + electron-fetch "^1.7.2" + err-code "^3.0.1" + is-electron "^2.2.0" + iso-url "^1.1.5" + it-glob "~0.0.11" + it-to-stream "^1.0.0" + merge-options "^3.0.4" + nanoid "^3.1.20" + native-abort-controller "^1.0.3" + native-fetch "^3.0.0" + node-fetch "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz" + react-native-fetch-api "^2.0.0" + stream-to-it "^0.2.2" + is-arguments@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -4391,6 +4671,11 @@ is-date-object@^1.0.1, is-date-object@^1.0.5: dependencies: has-tostringtag "^1.0.0" +is-electron@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.2.tgz#3778902a2044d76de98036f5dc58089ac4d80bb9" + integrity sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4437,6 +4722,13 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== +is-ip@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8" + integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q== + dependencies: + ip-regex "^4.0.0" + is-map@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" @@ -4474,7 +4766,7 @@ is-path-inside@^3.0.1, is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-plain-obj@2.1.0: +is-plain-obj@2.1.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== @@ -4572,6 +4864,16 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +iso-constants@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/iso-constants/-/iso-constants-0.1.2.tgz#3d2456ed5aeaa55d18564f285ba02a47a0d885b4" + integrity sha512-OTCM5ZCQsHBCI4Wdu4tSxvDIkmDHd5EwJDps5mKqnQnWJSKlnwMs3EDZ4n3Fh1tmkWkDlyd2vCDbEYuPbyrUNQ== + +iso-url@^1.1.5: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-1.2.1.tgz#db96a49d8d9a64a1c889fc07cc525d093afb1811" + integrity sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng== + isomorphic-unfetch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" @@ -4633,6 +4935,92 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +it-all@^1.0.2, it-all@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/it-all/-/it-all-1.0.6.tgz#852557355367606295c4c3b7eff0136f07749335" + integrity sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A== + +it-concat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/it-concat/-/it-concat-2.0.0.tgz#b4dc02aeb7365bada05b247c1ee50f3bbc147419" + integrity sha512-jchrEB3fHlUENWkVJRmbFJ1A7gcjJDmwiolQsHhVC14DpUIbX8fgr3SOC7XNE5OoUUQNL6/RaMCPChkPemyQUw== + dependencies: + bl "^5.0.0" + +it-drain@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/it-drain/-/it-drain-1.0.5.tgz#0466d4e286b37bcd32599d4e99b37a87cb8cfdf6" + integrity sha512-r/GjkiW1bZswC04TNmUnLxa6uovme7KKwPhc+cb1hHU65E3AByypHH6Pm91WHuvqfFsm+9ws0kPtDBV3/8vmIg== + +it-filter@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/it-filter/-/it-filter-1.0.3.tgz#66ea0cc4bf84af71bebd353c05a9c5735fcba751" + integrity sha512-EI3HpzUrKjTH01miLHWmhNWy3Xpbx4OXMXltgrNprL5lDpF3giVpHIouFpr5l+evXw6aOfxhnt01BIB+4VQA+w== + +it-first@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/it-first/-/it-first-1.0.7.tgz#a4bef40da8be21667f7d23e44dae652f5ccd7ab1" + integrity sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g== + +it-glob@~0.0.11: + version "0.0.14" + resolved "https://registry.yarnpkg.com/it-glob/-/it-glob-0.0.14.tgz#24f5e7fa48f9698ce7dd410355f327470c91eb90" + integrity sha512-TKKzs9CglbsihSpcwJPXN5DBUssu4akRzPlp8QJRCoLrKoaOpyY2V1qDlxx+UMivn0i114YyTd4AawWl7eqIdw== + dependencies: + "@types/minimatch" "^3.0.4" + minimatch "^3.0.4" + +it-last@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/it-last/-/it-last-1.0.6.tgz#4106232e5905ec11e16de15a0e9f7037eaecfc45" + integrity sha512-aFGeibeiX/lM4bX3JY0OkVCFkAw8+n9lkukkLNivbJRvNz8lI3YXv5xcqhFUV2lDJiraEK3OXRDbGuevnnR67Q== + +it-map@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/it-map/-/it-map-1.0.6.tgz#6aa547e363eedcf8d4f69d8484b450bc13c9882c" + integrity sha512-XT4/RM6UHIFG9IobGlQPFQUrlEKkU4eBUFG3qhWhfAdh1JfF2x11ShCrKCdmZ0OiZppPfoLuzcfA4cey6q3UAQ== + +it-peekable@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/it-peekable/-/it-peekable-1.0.3.tgz#8ebe933767d9c5aa0ae4ef8e9cb3a47389bced8c" + integrity sha512-5+8zemFS+wSfIkSZyf0Zh5kNN+iGyccN02914BY4w/Dj+uoFEoPSvj5vaWn8pNZJNSxzjW0zHRxC3LUb2KWJTQ== + +it-reader@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/it-reader/-/it-reader-3.0.0.tgz#56596c7742ec7c63b7f7998f6bfa3f712e333d0e" + integrity sha512-NxR40odATeaBmSefn6Xn43DplYvn2KtEKQzn4jrTRuPYXMky5M4e+KQ7aTJh0k0vkytLyeenGO1I1GXlGm4laQ== + dependencies: + bl "^5.0.0" + +it-take@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/it-take/-/it-take-1.0.2.tgz#b5f1570014db7c3454897898b69bb7ac9c3bffc1" + integrity sha512-u7I6qhhxH7pSevcYNaMECtkvZW365ARqAIt9K+xjdK1B2WUDEjQSfETkOCT8bxFq/59LqrN3cMLUtTgmDBaygw== + +it-tar@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/it-tar/-/it-tar-3.0.0.tgz#d25f2777c0da4d4bec1b01a1ab9d79495f459f4f" + integrity sha512-VhD1Hnx4IXDcQgYJnJgltkn+w5F8kiJaB46lqovh+YWfty2JGW7i40QQjWbSvcg1QfaU8is8AVX8xwx/Db9oOg== + dependencies: + bl "^5.0.0" + buffer "^6.0.3" + iso-constants "^0.1.2" + it-concat "^2.0.0" + it-reader "^3.0.0" + p-defer "^3.0.0" + +it-to-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/it-to-stream/-/it-to-stream-1.0.0.tgz#6c47f91d5b5df28bda9334c52782ef8e97fe3a4a" + integrity sha512-pLULMZMAB/+vbdvbZtebC0nWBTbG581lk6w8P7DfIIIKUfa8FbY7Oi0FxZcFPbxvISs7A9E+cMpLDBc1XhpAOA== + dependencies: + buffer "^6.0.3" + fast-fifo "^1.0.0" + get-iterator "^1.0.2" + p-defer "^3.0.0" + p-fifo "^1.0.0" + readable-stream "^3.6.0" + iterator.prototype@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" @@ -5250,6 +5638,11 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -5318,6 +5711,13 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +merge-options@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" + integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== + dependencies: + is-plain-obj "^2.1.0" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -5412,16 +5812,40 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.2.0: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +multiaddr-to-uri@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/multiaddr-to-uri/-/multiaddr-to-uri-8.0.0.tgz#65efe4b1f9de5f6b681aa42ff36a7c8db7625e58" + integrity sha512-dq4p/vsOOUdVEd1J1gl+R2GFrXJQH8yjLtz4hodqdVbieg39LvBOdMQRdQnfbg5LSM/q1BYNVf5CBbwZFFqBgA== + dependencies: + multiaddr "^10.0.0" -nanoid@^3.3.6: +multiaddr@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/multiaddr/-/multiaddr-10.0.1.tgz#0d15848871370860a4d266bb44d93b3dac5d90ef" + integrity sha512-G5upNcGzEGuTHkzxezPrrD6CaIHR9uo+7MwqhNVcXTs33IInon4y7nMiGxl2CY5hG7chvYQUQhz5V52/Qe3cbg== + dependencies: + dns-over-http-resolver "^1.2.3" + err-code "^3.0.1" + is-ip "^3.1.0" + multiformats "^9.4.5" + uint8arrays "^3.0.0" + varint "^6.0.0" + +multiformats@^9.4.1, multiformats@^9.4.2, multiformats@^9.4.5, multiformats@^9.5.4: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + +nanoid@^3.0.2, nanoid@^3.1.12, nanoid@^3.1.20, nanoid@^3.3.6: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +nanoid@^3.2.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + nanospinner@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/nanospinner/-/nanospinner-1.1.0.tgz#d17ff621cb1784b0a206b400da88a0ef6db39b97" @@ -5429,6 +5853,16 @@ nanospinner@^1.0.0: dependencies: picocolors "^1.0.0" +native-abort-controller@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/native-abort-controller/-/native-abort-controller-1.0.4.tgz#39920155cc0c18209ff93af5bc90be856143f251" + integrity sha512-zp8yev7nxczDJMoP6pDxyD20IU0T22eX8VwN2ztDccKvSZhRaV33yP1BGwKSZfXuqWUzsXopVFjBdau9OOAwMQ== + +native-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/native-fetch/-/native-fetch-3.0.0.tgz#06ccdd70e79e171c365c75117959cf4fe14a09bb" + integrity sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -5454,6 +5888,10 @@ node-fetch@^2.6.1, node-fetch@^2.6.12: dependencies: whatwg-url "^5.0.0" +"node-fetch@https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz": + version "2.6.7" + resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -5599,6 +6037,19 @@ ora@^5.4.1: strip-ansi "^6.0.0" wcwidth "^1.0.1" +p-defer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83" + integrity sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw== + +p-fifo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-fifo/-/p-fifo-1.0.0.tgz#e29d5cf17c239ba87f51dde98c1d26a9cfe20a63" + integrity sha512-IjoCxXW48tqdtDFz6fqo5q1UfFVjjVZe8TC1QRflvNUJtNfCUhxOUw6MOVZhDPjqhSzc26xKdugsO17gmzd5+A== + dependencies: + fast-fifo "^1.0.0" + p-defer "^3.0.0" + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -5646,6 +6097,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-duration@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-1.1.0.tgz#5192084c5d8f2a3fd676d04a451dbd2e05a1819c" + integrity sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ== + parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -5785,6 +6241,25 @@ property-expr@^2.0.5: resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== +protobufjs@^6.10.2: + version "6.11.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" + integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -5835,7 +6310,14 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -readable-stream@^3.4.0: +react-native-fetch-api@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-fetch-api/-/react-native-fetch-api-2.0.0.tgz#c4af188b4fce3f3eaf1f1ff4e61dae1a00d4ffa0" + integrity sha512-GOA8tc1EVYLnHvma/TU9VTgLOyralO7eATRuCDchQveXW9Fr9vXygyq9iwqmM7YRZ8qRJfEt9xOS7OYMdJvRFw== + dependencies: + p-defer "^3.0.0" + +readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5851,6 +6333,13 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +receptacle@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/receptacle/-/receptacle-1.3.2.tgz#a7994c7efafc7a01d0e2041839dab6c4951360d2" + integrity sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A== + dependencies: + ms "^2.1.1" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -5993,6 +6482,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +retimer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/retimer/-/retimer-2.0.0.tgz#e8bd68c5e5a8ec2f49ccb5c636db84c04063bbca" + integrity sha512-KLXY85WkEq2V2bKex/LOO1ViXVn2KGYe4PYysAdYdjmraYIUsVkXu8O4am+8+5UbaaGl1qho4aqAAPHNQ4GSbg== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -6263,6 +6757,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stream-to-it@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/stream-to-it/-/stream-to-it-0.2.4.tgz#d2fd7bfbd4a899b4c0d6a7e6a533723af5749bd0" + integrity sha512-4vEbkSs83OahpmBybNJXlJd7d6/RxzkkSdT3I0mnGt79Xd2Kk+e1JqbvAvsQfCeKj3aKb0QIWkyK3/n0j506vQ== + dependencies: + get-iterator "^1.0.2" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -6446,6 +6947,14 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +timeout-abort-controller@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/timeout-abort-controller/-/timeout-abort-controller-1.1.1.tgz#2c3c3c66f13c783237987673c276cbd7a9762f29" + integrity sha512-BsF9i3NAJag6T0ZEjki9j654zoafI2X6ayuNd6Tp8+Ul6Tr5s4jo973qFeiWrRSweqvskC+AHDKUmIW4b7pdhQ== + dependencies: + abort-controller "^3.0.0" + retimer "^2.0.0" + tiny-case@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" @@ -6634,11 +7143,30 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typescript@^5.0.2, typescript@^5.1.0: +typescript@5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== + +typescript@^5.0.2: version "5.3.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== +uint8arrays@^2.1.6: + version "2.1.10" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-2.1.10.tgz#34d023c843a327c676e48576295ca373c56e286a" + integrity sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A== + dependencies: + multiformats "^9.4.2" + +uint8arrays@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" + integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg== + dependencies: + multiformats "^9.4.2" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -6745,6 +7273,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +varint@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" + integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== + w3c-xmlserializer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" diff --git a/yarn.lock b/yarn.lock index 3c3d6eb5..43b73ef4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -139,10 +139,10 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4" integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== -"@eslint/eslintrc@^2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" - integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -154,17 +154,17 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.51.0": - version "8.51.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa" - integrity sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg== +"@eslint/js@8.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@humanwhocodes/config-array@^0.11.11": - version "0.11.11" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" - integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== +"@humanwhocodes/config-array@^0.11.13": + version "0.11.13" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" + integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" + "@humanwhocodes/object-schema" "^2.0.1" debug "^4.1.1" minimatch "^3.0.5" @@ -173,10 +173,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" + integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" @@ -344,6 +344,11 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -600,17 +605,18 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.41.0: - version "8.51.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.51.0.tgz#4a82dae60d209ac89a5cff1604fea978ba4950f3" - integrity sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA== + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.51.0" - "@humanwhocodes/config-array" "^0.11.11" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.56.0" + "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2"