From f85e45e563dd8fabae8c9562d51f6899e88f8155 Mon Sep 17 00:00:00 2001 From: Michael Heuer Date: Wed, 28 Feb 2024 18:07:32 +0100 Subject: [PATCH 01/17] feat: moved over files --- packages/contracts/src/IMultisig.sol | 48 + packages/contracts/src/Multisig.sol | 462 ++++++ packages/contracts/src/MultisigSetup.sol | 128 ++ packages/contracts/src/MyPlugin.sol | 41 - packages/contracts/src/MyPluginSetup.sol | 77 - packages/contracts/src/build-metadata.json | 41 +- packages/contracts/src/release-metadata.json | 4 +- .../test/10_unit-testing/11_plugin.ts | 1370 ++++++++++++++++- .../test/10_unit-testing/12_plugin-setup.ts | 635 ++++++-- packages/contracts/test/multisig-constants.ts | 22 + 10 files changed, 2542 insertions(+), 286 deletions(-) create mode 100644 packages/contracts/src/IMultisig.sol create mode 100644 packages/contracts/src/Multisig.sol create mode 100644 packages/contracts/src/MultisigSetup.sol delete mode 100644 packages/contracts/src/MyPlugin.sol delete mode 100644 packages/contracts/src/MyPluginSetup.sol create mode 100644 packages/contracts/test/multisig-constants.ts diff --git a/packages/contracts/src/IMultisig.sol b/packages/contracts/src/IMultisig.sol new file mode 100644 index 00000000..ec464d1c --- /dev/null +++ b/packages/contracts/src/IMultisig.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; + +/// @title IMultisig +/// @author Aragon Association - 2023 +/// @notice An interface for an on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. +/// @custom:security-contact sirt@aragon.org +interface IMultisig { + /// @notice Adds new members to the address list. Previously, it checks if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + /// @param _members The addresses of the members to be added. + function addAddresses(address[] calldata _members) external; + + /// @notice Removes existing members from the address list. Previously, it checks if the new address list length is at least as long as the minimum approvals parameter requires. Note that `minApprovals` is must be at least 1 so the address list cannot become empty. + /// @param _members The addresses of the members to be removed. + function removeAddresses(address[] calldata _members) external; + + /// @notice Approves and, optionally, executes the proposal. + /// @param _proposalId The ID of the proposal. + /// @param _tryExecution If `true`, execution is tried after the approval cast. The call does not revert if execution is not possible. + function approve(uint256 _proposalId, bool _tryExecution) external; + + /// @notice Checks if an account can participate on a proposal vote. This can be because the vote + /// - was executed, or + /// - the voter is not listed. + /// @param _proposalId The proposal Id. + /// @param _account The address of the user to check. + /// @return Returns true if the account is allowed to vote. + /// @dev The function assumes the queried proposal exists. + function canApprove(uint256 _proposalId, address _account) 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 Returns whether the account has approved the proposal. Note, that this does not check if the account is listed. + /// @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 hasApproved(uint256 _proposalId, address _account) external view returns (bool); + + /// @notice Executes a proposal. + /// @param _proposalId The ID of the proposal to be executed. + function execute(uint256 _proposalId) external; +} diff --git a/packages/contracts/src/Multisig.sol b/packages/contracts/src/Multisig.sol new file mode 100644 index 00000000..84bac939 --- /dev/null +++ b/packages/contracts/src/Multisig.sol @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {SafeCastUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; + +import {IMembership} from "@aragon/osx-commons-contracts/src/plugin/extensions/membership/IMembership.sol"; +import {Addresslist} from "@aragon/osx-commons-contracts/src/plugin/extensions/governance/Addresslist.sol"; +import {ProposalUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/extensions/proposal/ProposalUpgradeable.sol"; +import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/PluginUUPSUpgradeable.sol"; +import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; + +import {IMultisig} from "./IMultisig.sol"; + +/// @title Multisig +/// @author Aragon Association - 2022-2023 +/// @notice The on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. +/// @dev v1.2 (Release 1, Build 2) +/// @custom:security-contact sirt@aragon.org +contract Multisig is + IMultisig, + IMembership, + PluginUUPSUpgradeable, + ProposalUpgradeable, + Addresslist +{ + using SafeCastUpgradeable for uint256; + + /// @notice A container for proposal-related information. + /// @param executed Whether the proposal is executed or not. + /// @param approvals The number of approvals casted. + /// @param parameters The proposal-specific approve settings at the time of the proposal creation. + /// @param approvers The approves casted by the approvers. + /// @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; + uint16 approvals; + ProposalParameters parameters; + mapping(address => bool) approvers; + IDAO.Action[] actions; + uint256 allowFailureMap; + } + + /// @notice A container for the proposal parameters. + /// @param minApprovals The number of approvals required. + /// @param snapshotBlock The number of the block prior to the proposal creation. + /// @param startDate The timestamp when the proposal starts. + /// @param endDate The timestamp when the proposal expires. + struct ProposalParameters { + uint16 minApprovals; + uint64 snapshotBlock; + uint64 startDate; + uint64 endDate; + } + + /// @notice A container for the plugin settings. + /// @param onlyListed Whether only listed addresses can create a proposal or not. + /// @param minApprovals The minimal number of approvals required for a proposal to pass. + struct MultisigSettings { + bool onlyListed; + uint16 minApprovals; + } + + /// @notice The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract. + bytes4 internal constant MULTISIG_INTERFACE_ID = + this.initialize.selector ^ + this.updateMultisigSettings.selector ^ + this.createProposal.selector ^ + this.getProposal.selector; + + /// @notice The ID of the permission required to call the `addAddresses` and `removeAddresses` functions. + bytes32 public constant UPDATE_MULTISIG_SETTINGS_PERMISSION_ID = + keccak256("UPDATE_MULTISIG_SETTINGS_PERMISSION"); + + /// @notice A mapping between proposal IDs and proposal information. + mapping(uint256 => Proposal) internal proposals; + + /// @notice The current plugin settings. + MultisigSettings public multisigSettings; + + /// @notice Keeps track at which block number the multisig settings have been changed the last time. + /// @dev This variable prevents a proposal from being created in the same block in which the multisig settings change. + uint64 public lastMultisigSettingsChange; + + /// @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 approver is not allowed to cast an approve. This can be because the proposal + /// - is not open, + /// - was executed, or + /// - the approver is not on the address list + /// @param proposalId The ID of the proposal. + /// @param sender The address of the sender. + error ApprovalCastForbidden(uint256 proposalId, address sender); + + /// @notice Thrown if the proposal execution is forbidden. + /// @param proposalId The ID of the proposal. + error ProposalExecutionForbidden(uint256 proposalId); + + /// @notice Thrown if the minimal approvals value is out of bounds (less than 1 or greater than the number of members in the address list). + /// @param limit The maximal value. + /// @param actual The actual value. + error MinApprovalsOutOfBounds(uint16 limit, uint16 actual); + + /// @notice Thrown if the address list length is out of bounds. + /// @param limit The limit value. + /// @param actual The actual value. + error AddresslistLengthOutOfBounds(uint16 limit, uint256 actual); + + /// @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 Emitted when a proposal is approve by an approver. + /// @param proposalId The ID of the proposal. + /// @param approver The approver casting the approve. + event Approved(uint256 indexed proposalId, address indexed approver); + + /// @notice Emitted when the plugin settings are set. + /// @param onlyListed Whether only listed addresses can create a proposal. + /// @param minApprovals The minimum amount of approvals needed to pass a proposal. + event MultisigSettingsUpdated(bool onlyListed, uint16 indexed minApprovals); + + /// @notice Initializes Release 1, Build 2. + /// @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 _members The addresses of the initial members to be added. + /// @param _multisigSettings The multisig settings. + function initialize( + IDAO _dao, + address[] calldata _members, + MultisigSettings calldata _multisigSettings + ) external initializer { + __PluginUUPSUpgradeable_init(_dao); + + if (_members.length > type(uint16).max) { + revert AddresslistLengthOutOfBounds({limit: type(uint16).max, actual: _members.length}); + } + + _addAddresses(_members); + emit MembersAdded({members: _members}); + + _updateMultisigSettings(_multisigSettings); + } + + /// @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(PluginUUPSUpgradeable, ProposalUpgradeable) returns (bool) { + return + _interfaceId == MULTISIG_INTERFACE_ID || + _interfaceId == type(IMultisig).interfaceId || + _interfaceId == type(Addresslist).interfaceId || + _interfaceId == type(IMembership).interfaceId || + super.supportsInterface(_interfaceId); + } + + /// @inheritdoc IMultisig + function addAddresses( + address[] calldata _members + ) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { + uint256 newAddresslistLength = addresslistLength() + _members.length; + + // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + if (newAddresslistLength > type(uint16).max) { + revert AddresslistLengthOutOfBounds({ + limit: type(uint16).max, + actual: newAddresslistLength + }); + } + + _addAddresses(_members); + + emit MembersAdded({members: _members}); + } + + /// @inheritdoc IMultisig + function removeAddresses( + address[] calldata _members + ) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { + uint16 newAddresslistLength = uint16(addresslistLength() - _members.length); + + // Check if the new address list length would become less than the current minimum number of approvals required. + if (newAddresslistLength < multisigSettings.minApprovals) { + revert MinApprovalsOutOfBounds({ + limit: newAddresslistLength, + actual: multisigSettings.minApprovals + }); + } + + _removeAddresses(_members); + + emit MembersRemoved({members: _members}); + } + + /// @notice Updates the plugin settings. + /// @param _multisigSettings The new settings. + function updateMultisigSettings( + MultisigSettings calldata _multisigSettings + ) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { + _updateMultisigSettings(_multisigSettings); + } + + /// @notice Creates a new multisig proposal. + /// @param _metadata The metadata of the proposal. + /// @param _actions The actions that will be executed after 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. + /// @param _approveProposal If `true`, the sender will approve the proposal. + /// @param _tryExecution If `true`, execution is tried after the vote cast. The call does not revert if early execution is not possible. + /// @param _startDate The start date of the proposal. + /// @param _endDate The end date of the proposal. + /// @return proposalId The ID of the proposal. + function createProposal( + bytes calldata _metadata, + IDAO.Action[] calldata _actions, + uint256 _allowFailureMap, + bool _approveProposal, + bool _tryExecution, + uint64 _startDate, + uint64 _endDate + ) external returns (uint256 proposalId) { + if (multisigSettings.onlyListed && !isListed(_msgSender())) { + revert ProposalCreationForbidden(_msgSender()); + } + + uint64 snapshotBlock; + unchecked { + snapshotBlock = block.number.toUint64() - 1; // The snapshot block must be mined already to protect the transaction against backrunning transactions causing census changes. + } + + // Revert if the settings have been changed in the same block as this proposal should be created in. + // This prevents a malicious party from voting with previous addresses and the new settings. + if (lastMultisigSettingsChange > snapshotBlock) { + revert ProposalCreationForbidden(_msgSender()); + } + + if (_startDate == 0) { + _startDate = block.timestamp.toUint64(); + } else if (_startDate < block.timestamp.toUint64()) { + revert DateOutOfBounds({limit: block.timestamp.toUint64(), actual: _startDate}); + } + + if (_endDate < _startDate) { + revert DateOutOfBounds({limit: _startDate, actual: _endDate}); + } + + proposalId = _createProposal({ + _creator: _msgSender(), + _metadata: _metadata, + _startDate: _startDate, + _endDate: _endDate, + _actions: _actions, + _allowFailureMap: _allowFailureMap + }); + + // Create the proposal + Proposal storage proposal_ = proposals[proposalId]; + + proposal_.parameters.snapshotBlock = snapshotBlock; + proposal_.parameters.startDate = _startDate; + proposal_.parameters.endDate = _endDate; + proposal_.parameters.minApprovals = multisigSettings.minApprovals; + + // Reduce costs + if (_allowFailureMap != 0) { + proposal_.allowFailureMap = _allowFailureMap; + } + + for (uint256 i; i < _actions.length; ) { + proposal_.actions.push(_actions[i]); + unchecked { + ++i; + } + } + + if (_approveProposal) { + approve(proposalId, _tryExecution); + } + } + + /// @inheritdoc IMultisig + function approve(uint256 _proposalId, bool _tryExecution) public { + address approver = _msgSender(); + if (!_canApprove(_proposalId, approver)) { + revert ApprovalCastForbidden(_proposalId, approver); + } + + Proposal storage proposal_ = proposals[_proposalId]; + + // As the list can never become more than type(uint16).max(due to addAddresses check) + // It's safe to use unchecked as it would never overflow. + unchecked { + proposal_.approvals += 1; + } + + proposal_.approvers[approver] = true; + + emit Approved({proposalId: _proposalId, approver: approver}); + + if (_tryExecution && _canExecute(_proposalId)) { + _execute(_proposalId); + } + } + + /// @inheritdoc IMultisig + function canApprove(uint256 _proposalId, address _account) external view returns (bool) { + return _canApprove(_proposalId, _account); + } + + /// @inheritdoc IMultisig + function canExecute(uint256 _proposalId) external view returns (bool) { + return _canExecute(_proposalId); + } + + /// @notice Returns all information for a proposal vote by its ID. + /// @param _proposalId The ID of the proposal. + /// @return executed Whether the proposal is executed or not. + /// @return approvals The number of approvals casted. + /// @return parameters The parameters of the proposal vote. + /// @return actions The actions to be executed in the associated DAO after the proposal has passed. + /// @param allowFailureMap A bitmap allowing the proposal to succeed, even if individual actions might revert. If the bit at index `i` is 1, the proposal succeeds even if the `i`th action reverts. A failure map value of 0 requires every action to not revert. + function getProposal( + uint256 _proposalId + ) + public + view + returns ( + bool executed, + uint16 approvals, + ProposalParameters memory parameters, + IDAO.Action[] memory actions, + uint256 allowFailureMap + ) + { + Proposal storage proposal_ = proposals[_proposalId]; + + executed = proposal_.executed; + approvals = proposal_.approvals; + parameters = proposal_.parameters; + actions = proposal_.actions; + allowFailureMap = proposal_.allowFailureMap; + } + + /// @inheritdoc IMultisig + function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { + return proposals[_proposalId].approvers[_account]; + } + + /// @inheritdoc IMultisig + function execute(uint256 _proposalId) public { + if (!_canExecute(_proposalId)) { + revert ProposalExecutionForbidden(_proposalId); + } + + _execute(_proposalId); + } + + /// @inheritdoc IMembership + function isMember(address _account) external view returns (bool) { + return isListed(_account); + } + + /// @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 { + Proposal storage proposal_ = proposals[_proposalId]; + + proposal_.executed = true; + + _executeProposal( + dao(), + _proposalId, + proposals[_proposalId].actions, + proposals[_proposalId].allowFailureMap + ); + } + + /// @notice Internal function to check if an account can approve. It assumes the queried proposal exists. + /// @param _proposalId The ID of the proposal. + /// @param _account The account to check. + /// @return Returns `true` if the given account can approve on a certain proposal and `false` otherwise. + function _canApprove(uint256 _proposalId, address _account) internal view returns (bool) { + Proposal storage proposal_ = proposals[_proposalId]; + + if (!_isProposalOpen(proposal_)) { + // The proposal was executed already + return false; + } + + if (!isListedAtBlock(_account, proposal_.parameters.snapshotBlock)) { + // The approver has no voting power. + return false; + } + + if (proposal_.approvers[_account]) { + // The approver has already approved + return false; + } + + return true; + } + + /// @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 Returns `true` if the proposal can be executed and `false` otherwise. + function _canExecute(uint256 _proposalId) internal view returns (bool) { + Proposal storage proposal_ = proposals[_proposalId]; + + // Verify that the proposal has not been executed or expired. + if (!_isProposalOpen(proposal_)) { + return false; + } + + return proposal_.approvals >= proposal_.parameters.minApprovals; + } + + /// @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 returns (bool) { + uint64 currentTimestamp64 = block.timestamp.toUint64(); + return + !proposal_.executed && + proposal_.parameters.startDate <= currentTimestamp64 && + proposal_.parameters.endDate >= currentTimestamp64; + } + + /// @notice Internal function to update the plugin settings. + /// @param _multisigSettings The new settings. + function _updateMultisigSettings(MultisigSettings calldata _multisigSettings) internal { + uint16 addresslistLength_ = uint16(addresslistLength()); + + if (_multisigSettings.minApprovals > addresslistLength_) { + revert MinApprovalsOutOfBounds({ + limit: addresslistLength_, + actual: _multisigSettings.minApprovals + }); + } + + if (_multisigSettings.minApprovals < 1) { + revert MinApprovalsOutOfBounds({limit: 1, actual: _multisigSettings.minApprovals}); + } + + multisigSettings = _multisigSettings; + lastMultisigSettingsChange = block.number.toUint64(); + + emit MultisigSettingsUpdated({ + onlyListed: _multisigSettings.onlyListed, + minApprovals: _multisigSettings.minApprovals + }); + } + + /// @dev This empty reserved space is put in place to allow future versions to add new + /// variables without shifting down storage in the inheritance chain. + /// https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps + uint256[47] private __gap; +} diff --git a/packages/contracts/src/MultisigSetup.sol b/packages/contracts/src/MultisigSetup.sol new file mode 100644 index 00000000..631165dd --- /dev/null +++ b/packages/contracts/src/MultisigSetup.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; +import {IPluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol"; +import {PluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/PluginSetup.sol"; +import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; + +import {DAO} from "../../../core/dao/DAO.sol"; +import {Multisig} from "./Multisig.sol"; + +/// @title MultisigSetup +/// @author Aragon Association - 2022-2023 +/// @notice The setup contract of the `Multisig` plugin. +/// @dev v1.2 (Release 1, Build 2) +/// @custom:security-contact sirt@aragon.org +contract MultisigSetup is PluginSetup { + /// @notice The address of `Multisig` plugin logic contract to be used in creating proxy contracts. + Multisig private immutable multisigBase; + + /// @notice The contract constructor, that deploys the `Multisig` plugin logic contract. + constructor() { + multisigBase = new Multisig(); + } + + /// @inheritdoc IPluginSetup + function prepareInstallation( + address _dao, + bytes calldata _data + ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { + // Decode `_data` to extract the params needed for deploying and initializing `Multisig` plugin. + (address[] memory members, Multisig.MultisigSettings memory multisigSettings) = abi.decode( + _data, + (address[], Multisig.MultisigSettings) + ); + + // Prepare and Deploy the plugin proxy. + plugin = createERC1967Proxy( + address(multisigBase), + abi.encodeCall(Multisig.initialize, (IDAO(_dao), members, multisigSettings)) + ); + + // Prepare permissions + PermissionLib.MultiTargetPermission[] + memory permissions = new PermissionLib.MultiTargetPermission[](3); + + // Set permissions to be granted. + // Grant the list of permissions of the plugin to the DAO. + permissions[0] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: plugin, + who: _dao, + condition: PermissionLib.NO_CONDITION, + permissionId: multisigBase.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + }); + + permissions[1] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: plugin, + who: _dao, + condition: PermissionLib.NO_CONDITION, + permissionId: multisigBase.UPGRADE_PLUGIN_PERMISSION_ID() + }); + + // Grant `EXECUTE_PERMISSION` of the DAO to the plugin. + permissions[2] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Grant, + where: _dao, + who: plugin, + condition: PermissionLib.NO_CONDITION, + permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() + }); + + preparedSetupData.permissions = permissions; + } + + /// @inheritdoc IPluginSetup + function prepareUpdate( + address _dao, + uint16 _currentBuild, + SetupPayload calldata _payload + ) + external + pure + override + returns (bytes memory initData, PreparedSetupData memory preparedSetupData) + {} + + /// @inheritdoc IPluginSetup + function prepareUninstallation( + address _dao, + SetupPayload calldata _payload + ) external view returns (PermissionLib.MultiTargetPermission[] memory permissions) { + // Prepare permissions + permissions = new PermissionLib.MultiTargetPermission[](3); + + // Set permissions to be Revoked. + permissions[0] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _payload.plugin, + who: _dao, + condition: PermissionLib.NO_CONDITION, + permissionId: multisigBase.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + }); + + permissions[1] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _payload.plugin, + who: _dao, + condition: PermissionLib.NO_CONDITION, + permissionId: multisigBase.UPGRADE_PLUGIN_PERMISSION_ID() + }); + + permissions[2] = PermissionLib.MultiTargetPermission({ + operation: PermissionLib.Operation.Revoke, + where: _dao, + who: _payload.plugin, + condition: PermissionLib.NO_CONDITION, + permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() + }); + } + + /// @inheritdoc IPluginSetup + function implementation() external view returns (address) { + return address(multisigBase); + } +} diff --git a/packages/contracts/src/MyPlugin.sol b/packages/contracts/src/MyPlugin.sol deleted file mode 100644 index ec8739f7..00000000 --- a/packages/contracts/src/MyPlugin.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.8; - -import {PluginUUPSUpgradeable} from "@aragon/osx-commons-contracts/src/plugin/PluginUUPSUpgradeable.sol"; -import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; - -/// @title MyPlugin -/// @dev Release 1, Build 1 -contract MyPlugin is PluginUUPSUpgradeable { - /// @notice The ID of the permission required to call the `storeNumber` function. - bytes32 public constant STORE_PERMISSION_ID = keccak256("STORE_PERMISSION"); - - uint256 public number; // added in build 1 - - /// @notice Emitted when a number is stored. - /// @param number The number. - event NumberStored(uint256 number); - - /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized. - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the plugin when build 1 is installed. - /// @param _number The number to be stored. - function initialize(IDAO _dao, uint256 _number) external initializer { - __PluginUUPSUpgradeable_init(_dao); - number = _number; - - emit NumberStored({number: _number}); - } - - /// @notice Stores a new number to storage. Caller needs STORE_PERMISSION. - /// @param _number The number to be stored. - function storeNumber(uint256 _number) external auth(STORE_PERMISSION_ID) { - number = _number; - - emit NumberStored({number: _number}); - } -} diff --git a/packages/contracts/src/MyPluginSetup.sol b/packages/contracts/src/MyPluginSetup.sol deleted file mode 100644 index 01993a20..00000000 --- a/packages/contracts/src/MyPluginSetup.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; -import {ProxyLib} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyLib.sol"; -import {PluginUpgradeableSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/PluginUpgradeableSetup.sol"; -import {IPluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol"; -import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; - -import {MyPlugin} from "./MyPlugin.sol"; - -/// @title MyPluginSetup -/// @dev Release 1, Build 1 -contract MyPluginSetup is PluginUpgradeableSetup { - using ProxyLib for address; - - /// @notice Constructs the `PluginUpgradeableSetup` by storing the `MyPlugin` implementation address. - /// @dev The implementation address is used to deploy UUPS proxies referencing it and - /// to verify the plugin on the respective block explorers. - constructor() PluginUpgradeableSetup(address(new MyPlugin())) {} - - /// @notice The ID of the permission required to call the `storeNumber` function. - bytes32 internal constant STORE_PERMISSION_ID = keccak256("STORE_PERMISSION"); - - /// @inheritdoc IPluginSetup - function prepareInstallation( - address _dao, - bytes memory _data - ) external returns (address plugin, PreparedSetupData memory preparedSetupData) { - uint256 number = abi.decode(_data, (uint256)); - - plugin = IMPLEMENTATION.deployUUPSProxy( - abi.encodeCall(MyPlugin.initialize, (IDAO(_dao), number)) - ); - - PermissionLib.MultiTargetPermission[] - memory permissions = new PermissionLib.MultiTargetPermission[](1); - - permissions[0] = PermissionLib.MultiTargetPermission({ - operation: PermissionLib.Operation.Grant, - where: plugin, - who: _dao, - condition: PermissionLib.NO_CONDITION, - permissionId: STORE_PERMISSION_ID - }); - - preparedSetupData.permissions = permissions; - } - - /// @inheritdoc IPluginSetup - /// @dev The default implementation for the initial build 1 that reverts because no earlier build exists. - function prepareUpdate( - address _dao, - uint16 _fromBuild, - SetupPayload calldata _payload - ) external pure virtual returns (bytes memory, PreparedSetupData memory) { - (_dao, _fromBuild, _payload); - revert InvalidUpdatePath({fromBuild: 0, thisBuild: 1}); - } - - /// @inheritdoc IPluginSetup - function prepareUninstallation( - address _dao, - SetupPayload calldata _payload - ) external pure returns (PermissionLib.MultiTargetPermission[] memory permissions) { - permissions = new PermissionLib.MultiTargetPermission[](1); - - permissions[0] = PermissionLib.MultiTargetPermission({ - operation: PermissionLib.Operation.Revoke, - where: _payload.plugin, - who: _dao, - condition: PermissionLib.NO_CONDITION, - permissionId: STORE_PERMISSION_ID - }); - } -} diff --git a/packages/contracts/src/build-metadata.json b/packages/contracts/src/build-metadata.json index ba36b8b4..c7b8cbec 100644 --- a/packages/contracts/src/build-metadata.json +++ b/packages/contracts/src/build-metadata.json @@ -1,21 +1,46 @@ { "ui": {}, - "change": "Initial build.", + "change": "- The ability to create a proposal now depends on the membership status of the current instead of the snapshot block.\n- Added a check ensuring that the initial member list cannot overflow.", "pluginSetup": { "prepareInstallation": { - "description": "The information required for the installation of build 1.", + "description": "The information required for the installation.", "inputs": [ { - "name": "number", - "type": "uint256", - "internalType": "uint256", - "description": "The initial number to be stored." + "internalType": "address[]", + "name": "members", + "type": "address[]", + "description": "The addresses of the initial members to be added." + }, + { + "components": [ + { + "internalType": "bool", + "name": "onlyListed", + "type": "bool", + "description": "Whether only listed addresses can create a proposal or not." + }, + { + "internalType": "uint16", + "name": "minApprovals", + "type": "uint16", + "description": "The minimal number of approvals required for a proposal to pass." + } + ], + "internalType": "struct Multisig.MultisigSettings", + "name": "multisigSettings", + "type": "tuple", + "description": "The inital multisig settings." } ] }, - "prepareUpdate": {}, + "prepareUpdate": { + "1": { + "description": "No input is required for the update.", + "inputs": [] + } + }, "prepareUninstallation": { - "description": "The information required for the uninstallation of build 1.", + "description": "No input is required for the uninstallation.", "inputs": [] } } diff --git a/packages/contracts/src/release-metadata.json b/packages/contracts/src/release-metadata.json index a99ed49b..bd15c0c0 100644 --- a/packages/contracts/src/release-metadata.json +++ b/packages/contracts/src/release-metadata.json @@ -1,5 +1,5 @@ { - "name": "PluginName", - "description": "Description.", + "name": "Multisig", + "description": "", "images": {} } diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index 715b0922..cf6ead5c 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -1,102 +1,1328 @@ -import {PLUGIN_CONTRACT_NAME} from '../../plugin-settings'; +import {MULTISIG_INTERFACE_ID} from '../../../../../subgraph/src/utils/constants'; import { - DAOMock, - DAOMock__factory, - MyPlugin, - MyPlugin__factory, -} from '../../typechain'; -import '../../typechain/src/MyPlugin'; -import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; + Addresslist__factory, + DAO, + DAO__factory, + IERC165Upgradeable__factory, + IMembership__factory, + IMultisig__factory, + IPlugin__factory, + IProposal__factory, + IProtocolVersion__factory, + Multisig, + Multisig__factory, +} from '../../../../typechain'; +import {Multisig__factory as Multisig_V1_0_0__factory} from '../../../../typechain/@aragon/osx-v1.0.1/plugins/governance/multisig/Multisig.sol'; +import {Multisig__factory as Multisig_V1_3_0__factory} from '../../../../typechain/@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig.sol'; +import {ExecutedEvent} from '../../../../typechain/DAO'; +import {ProposalCreatedEvent} from '../../../../typechain/IProposal'; +import { + ApprovedEvent, + ProposalExecutedEvent, +} from '../../../../typechain/Multisig'; +import {deployNewDAO} from '../../../test-utils/dao'; +import {osxContractsVersion} from '../../../test-utils/protocol-version'; +import {deployWithProxy} from '../../../test-utils/proxy'; +import { + getProtocolVersion, + deployAndUpgradeFromToCheck, + deployAndUpgradeSelfCheck, +} from '../../../test-utils/uups-upgradeable'; +import { + MULTISIG_EVENTS, + MULTISIG_INTERFACE, + MultisigSettings, +} from './multisig-constants'; +import { + getInterfaceId, + proposalIdToBytes32, + IDAO_EVENTS, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, + IMEMBERSHIP_EVENTS, + IPROPOSAL_EVENTS, + findEvent, + findEventTopicLog, + TIME, +} from '@aragon/osx-commons-sdk'; +import {time} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {BigNumber} from 'ethers'; -import {ethers, upgrades} from 'hardhat'; - -export type InitData = {number: BigNumber}; -export const defaultInitData: InitData = { - number: BigNumber.from(123), -}; - -export const STORE_PERMISSION_ID = ethers.utils.id('STORE_PERMISSION'); - -type FixtureResult = { - deployer: SignerWithAddress; - alice: SignerWithAddress; - bob: SignerWithAddress; - plugin: MyPlugin; - daoMock: DAOMock; -}; - -async function fixture(): Promise { - const [deployer, alice, bob] = await ethers.getSigners(); - const daoMock = await new DAOMock__factory(deployer).deploy(); - const plugin = (await upgrades.deployProxy( - new MyPlugin__factory(deployer), - [daoMock.address, defaultInitData.number], - { - kind: 'uups', - initializer: 'initialize', - unsafeAllow: ['constructor'], - constructorArgs: [], - } - )) as unknown as MyPlugin; - - return {deployer, alice, bob, plugin, daoMock}; +import {Contract, ContractFactory} from 'ethers'; +import {ethers} from 'hardhat'; + +export async function approveWithSigners( + multisigContract: Contract, + proposalId: number, + signers: SignerWithAddress[], + signerIds: number[] +) { + let promises = signerIds.map(i => + multisigContract.connect(signers[i]).approve(proposalId, false) + ); + + await Promise.all(promises); } -describe(PLUGIN_CONTRACT_NAME, function () { - describe('initialize', async () => { +describe('Multisig', function () { + let signers: SignerWithAddress[]; + let multisig: Multisig; + let dao: DAO; + let dummyActions: any; + let dummyMetadata: string; + let startDate: number; + let endDate: number; + let multisigSettings: MultisigSettings; + + const id = 0; + + before(async () => { + signers = await ethers.getSigners(); + dummyActions = [ + { + to: signers[0].address, + data: '0x00000000', + value: 0, + }, + ]; + dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + + dao = await deployNewDAO(signers[0]); + }); + + beforeEach(async function () { + startDate = (await time.latest()) + 1000; + endDate = startDate + 1000; + + multisigSettings = { + minApprovals: 3, + onlyListed: true, + }; + + const MultisigFactory = new Multisig__factory(signers[0]); + multisig = await deployWithProxy(MultisigFactory); + + dao.grant( + dao.address, + multisig.address, + ethers.utils.id('EXECUTE_PERMISSION') + ); + dao.grant( + multisig.address, + signers[0].address, + ethers.utils.id('UPDATE_MULTISIG_SETTINGS_PERMISSION') + ); + }); + + describe('initialize:', async () => { it('reverts if trying to re-initialize', async () => { - const {plugin, daoMock} = await loadFixture(fixture); + await multisig.initialize( + dao.address, + signers.slice(0, 5).map(s => s.address), + multisigSettings + ); + await expect( - plugin.initialize(daoMock.address, defaultInitData.number) + multisig.initialize( + dao.address, + signers.slice(0, 5).map(s => s.address), + multisigSettings + ) ).to.be.revertedWith('Initializable: contract is already initialized'); }); - it('stores the number', async () => { - const {plugin} = await loadFixture(fixture); + it('adds the initial addresses to the address list', async () => { + expect(await multisig.addresslistLength()).to.equal(0); + + multisigSettings.minApprovals = 2; + await multisig.initialize( + dao.address, + signers.slice(0, 2).map(s => s.address), + multisigSettings + ); + + expect(await multisig.addresslistLength()).to.equal(2); + expect(await multisig.isListed(signers[0].address)).to.equal(true); + expect(await multisig.isListed(signers[1].address)).to.equal(true); + }); + + it('should set the `minApprovals`', async () => { + await multisig.initialize( + dao.address, + signers.slice(0, 5).map(s => s.address), + multisigSettings + ); + expect((await multisig.multisigSettings()).minApprovals).to.be.eq( + multisigSettings.minApprovals + ); + }); + + it('should set `onlyListed`', async () => { + await multisig.initialize( + dao.address, + signers.slice(0, 5).map(s => s.address), + multisigSettings + ); + expect((await multisig.multisigSettings()).onlyListed).to.be.eq( + multisigSettings.onlyListed + ); + }); + + it('should emit `MultisigSettingsUpdated` during initialization', async () => { + await expect( + multisig.initialize( + dao.address, + signers.slice(0, 5).map(s => s.address), + multisigSettings + ) + ) + .to.emit(multisig, MULTISIG_EVENTS.MULTISIG_SETTINGS_UPDATED) + .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); + }); + + it('should revert if members list is longer than uint16 max', async () => { + const megaMember = signers[1]; + const members: string[] = new Array(65537).fill(megaMember.address); + await expect(multisig.initialize(dao.address, members, multisigSettings)) + .to.revertedWithCustomError(multisig, 'AddresslistLengthOutOfBounds') + .withArgs(65535, members.length); + }); + }); + + describe('Upgrades', () => { + let legacyContractFactory: ContractFactory; + let currentContractFactory: ContractFactory; + let initArgs: any; + + before(() => { + currentContractFactory = new Multisig__factory(signers[0]); + }); + + beforeEach(() => { + initArgs = { + dao: dao.address, + members: [signers[0].address, signers[1].address, signers[2].address], + multisigSettings: multisigSettings, + }; + }); + + it('upgrades to a new implementation', async () => { + await deployAndUpgradeSelfCheck( + signers[0], + signers[1], + initArgs, + 'initialize', + currentContractFactory, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + dao + ); + }); + + it('upgrades from v1.0.0', async () => { + legacyContractFactory = new Multisig_V1_0_0__factory(signers[0]); + + const {fromImplementation, toImplementation} = + await deployAndUpgradeFromToCheck( + signers[0], + signers[1], + initArgs, + 'initialize', + legacyContractFactory, + currentContractFactory, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + dao + ); + expect(toImplementation).to.not.equal(fromImplementation); // The build did change + + const fromProtocolVersion = await getProtocolVersion( + legacyContractFactory.attach(fromImplementation) + ); + const toProtocolVersion = await getProtocolVersion( + currentContractFactory.attach(toImplementation) + ); + + expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); + expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); + expect(toProtocolVersion).to.deep.equal(osxContractsVersion()); + }); + + it('from v1.3.0', async () => { + legacyContractFactory = new Multisig_V1_3_0__factory(signers[0]); - expect(await plugin.number()).to.equal(defaultInitData.number); + const {fromImplementation, toImplementation} = + await deployAndUpgradeFromToCheck( + signers[0], + signers[1], + initArgs, + 'initialize', + legacyContractFactory, + currentContractFactory, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + dao + ); + expect(toImplementation).to.not.equal(fromImplementation); + + const fromProtocolVersion = await getProtocolVersion( + legacyContractFactory.attach(fromImplementation) + ); + const toProtocolVersion = await getProtocolVersion( + currentContractFactory.attach(toImplementation) + ); + + expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); + expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); + expect(toProtocolVersion).to.deep.equal(osxContractsVersion()); }); }); - describe('storeNumber', async () => { - it('reverts if sender lacks permission', async () => { - const newNumber = BigNumber.from(456); + describe('ERC-165', async () => { + it('does not support the empty interface', async () => { + expect(await multisig.supportsInterface('0xffffffff')).to.be.false; + }); - const {bob, plugin, daoMock} = await loadFixture(fixture); + it('supports the `IERC165Upgradeable` interface', async () => { + const iface = IERC165Upgradeable__factory.createInterface(); + expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); - expect(await daoMock.hasPermissionReturnValueMock()).to.equal(false); + it('supports the `IPlugin` interface', async () => { + const iface = IPlugin__factory.createInterface(); + expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IProtocolVersion` interface', async () => { + const iface = IProtocolVersion__factory.createInterface(); + expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); - await expect(plugin.connect(bob).storeNumber(newNumber)) - .to.be.revertedWithCustomError(plugin, 'DaoUnauthorized') + it('supports the `IProposal` interface', async () => { + const iface = IProposal__factory.createInterface(); + expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IMembership` interface', async () => { + const iface = IMembership__factory.createInterface(); + expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `Addresslist` interface', async () => { + const iface = Addresslist__factory.createInterface(); + expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `IMultisig` interface', async () => { + const iface = IMultisig__factory.createInterface(); + expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be + .true; + }); + + it('supports the `Multisig` interface', async () => { + const iface = getInterfaceId(MULTISIG_INTERFACE); + expect(iface).to.equal(MULTISIG_INTERFACE_ID); // checks that it didn't change + expect(await multisig.supportsInterface(iface)).to.be.true; + }); + }); + + describe('updateMultisigSettings:', async () => { + beforeEach(async () => { + await multisig.initialize( + dao.address, + signers.slice(0, 5).map(s => s.address), + multisigSettings + ); + }); + + it('should not allow to set minApprovals larger than the address list length', async () => { + let addresslistLength = (await multisig.addresslistLength()).toNumber(); + + multisigSettings.minApprovals = addresslistLength + 1; + + await expect(multisig.updateMultisigSettings(multisigSettings)) + .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + .withArgs(addresslistLength, multisigSettings.minApprovals); + }); + + it('should not allow to set `minApprovals` to 0', async () => { + multisigSettings.minApprovals = 0; + await expect(multisig.updateMultisigSettings(multisigSettings)) + .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + .withArgs(1, 0); + }); + + it('should emit `MultisigSettingsUpdated` when `updateMutlsigSettings` gets called', async () => { + await expect(multisig.updateMultisigSettings(multisigSettings)) + .to.emit(multisig, MULTISIG_EVENTS.MULTISIG_SETTINGS_UPDATED) + .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); + }); + }); + + describe('isListed:', async () => { + it('should return false, if a user is not listed', async () => { + multisigSettings.minApprovals = 1; + await multisig.initialize( + dao.address, + [signers[0].address], + multisigSettings + ); + + expect(await multisig.isListed(signers[9].address)).to.equal(false); + }); + }); + + describe('isMember', async () => { + it('should return false, if user is not listed', async () => { + expect(await multisig.isMember(signers[0].address)).to.be.false; + }); + + it('should return true if user is in the latest list', async () => { + multisigSettings.minApprovals = 1; + await multisig.initialize( + dao.address, + [signers[0].address], + multisigSettings + ); + expect(await multisig.isMember(signers[0].address)).to.be.true; + }); + }); + + describe('addAddresses:', async () => { + it('should add new members to the address list and emit the `MembersAdded` event', async () => { + multisigSettings.minApprovals = 1; + await multisig.initialize( + dao.address, + [signers[0].address], + multisigSettings + ); + + expect(await multisig.isListed(signers[0].address)).to.equal(true); + expect(await multisig.isListed(signers[1].address)).to.equal(false); + + // add a new member + await expect(multisig.addAddresses([signers[1].address])) + .to.emit(multisig, IMEMBERSHIP_EVENTS.MEMBERS_ADDED) + .withArgs([signers[1].address]); + + expect(await multisig.isListed(signers[0].address)).to.equal(true); + expect(await multisig.isListed(signers[1].address)).to.equal(true); + }); + }); + + describe('removeAddresses:', async () => { + it('should remove users from the address list and emit the `MembersRemoved` event', async () => { + multisigSettings.minApprovals = 1; + await multisig.initialize( + dao.address, + signers.slice(0, 2).map(s => s.address), + multisigSettings + ); + + expect(await multisig.isListed(signers[0].address)).to.equal(true); + expect(await multisig.isListed(signers[1].address)).to.equal(true); + + // remove an existing member + await expect(multisig.removeAddresses([signers[1].address])) + .to.emit(multisig, IMEMBERSHIP_EVENTS.MEMBERS_REMOVED) + .withArgs([signers[1].address]); + + expect(await multisig.isListed(signers[0].address)).to.equal(true); + expect(await multisig.isListed(signers[1].address)).to.equal(false); + }); + + it('reverts if the address list would become empty', async () => { + multisigSettings.minApprovals = 1; + await multisig.initialize( + dao.address, + [signers[0].address], + multisigSettings + ); + + await expect(multisig.removeAddresses([signers[0].address])) + .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') .withArgs( - daoMock.address, - plugin.address, - bob.address, - STORE_PERMISSION_ID + (await multisig.addresslistLength()).sub(1), + multisigSettings.minApprovals ); }); - it('stores the number', async () => { - const newNumber = BigNumber.from(456); + it('reverts if the address list would become shorter than the current minimum approval parameter requires', async () => { + multisigSettings.minApprovals = 2; + await multisig.initialize( + dao.address, + signers.slice(0, 3).map(s => s.address), + multisigSettings + ); - const {plugin, daoMock} = await loadFixture(fixture); - await daoMock.setHasPermissionReturnValueMock(true); + await expect(multisig.removeAddresses([signers[1].address])).not.to.be + .reverted; - await expect(plugin.storeNumber(newNumber)).to.not.be.reverted; - expect(await plugin.number()).to.equal(newNumber); + await expect(multisig.removeAddresses([signers[2].address])) + .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + .withArgs( + (await multisig.addresslistLength()).sub(1), + multisigSettings.minApprovals + ); }); + }); + + describe('createProposal:', async () => { + beforeEach(async () => { + multisigSettings.minApprovals = 1; + }); + + it('increments the proposal counter', async () => { + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + + expect(await multisig.proposalCount()).to.equal(0); + + await expect( + multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + startDate + ) + ).not.to.be.reverted; + + expect(await multisig.proposalCount()).to.equal(1); + }); + + it('creates unique proposal IDs for each proposal', async () => { + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + await ethers.provider.send('evm_mine', []); + + const proposalId0 = await multisig.callStatic.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + // create a new proposal for the proposalCounter to be incremented + await expect( + multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + startDate + ) + ).not.to.be.reverted; + + const proposalId1 = await multisig.callStatic.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + + expect(proposalId0).to.equal(0); // To be removed when proposal ID is generated as a hash. + expect(proposalId1).to.equal(1); // To be removed when proposal ID is generated as a hash. + + expect(proposalId0).to.not.equal(proposalId1); + }); + + it('emits the `ProposalCreated` event', async () => { + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + + const allowFailureMap = 1; + + await expect( + multisig + .connect(signers[0]) + .createProposal( + dummyMetadata, + [], + allowFailureMap, + false, + false, + startDate, + endDate + ) + ) + .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .withArgs( + id, + signers[0].address, + startDate, + endDate, + dummyMetadata, + [], + allowFailureMap + ); + }); + + it('reverts if the multisig settings have been changed in the same block', async () => { + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + await dao.grant( + multisig.address, + dao.address, + await multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + + await ethers.provider.send('evm_setAutomine', [false]); + + await multisig.connect(signers[0]).createProposal( + dummyMetadata, + [ + { + to: multisig.address, + value: 0, + data: multisig.interface.encodeFunctionData( + 'updateMultisigSettings', + [ + { + onlyListed: false, + minApprovals: 1, + }, + ] + ), + }, + ], + 0, + true, + true, + 0, + endDate + ); + await expect( + multisig + .connect(signers[0]) + .createProposal(dummyMetadata, [], 0, true, true, 0, endDate) + ) + .to.revertedWithCustomError(multisig, 'ProposalCreationForbidden') + .withArgs(signers[0].address); + + await ethers.provider.send('evm_setAutomine', [true]); + }); + + context('`onlyListed` is set to `false`:', async () => { + beforeEach(async () => { + multisigSettings.onlyListed = false; + + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + }); + + it('creates a proposal when unlisted accounts are allowed', async () => { + await expect( + multisig + .connect(signers[1]) // not listed + .createProposal( + dummyMetadata, + [], + 0, + false, + false, + startDate, + endDate + ) + ) + .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .withArgs( + id, + signers[1].address, + startDate, + endDate, + dummyMetadata, + [], + 0 + ); + }); + }); + + context('`onlyListed` is set to `true`:', async () => { + beforeEach(async () => { + multisigSettings.onlyListed = true; + + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + }); + + it('reverts if the user is not on the list and only listed accounts can create proposals', async () => { + await expect( + multisig + .connect(signers[1]) // not listed + .createProposal(dummyMetadata, [], 0, false, false, 0, startDate) + ) + .to.be.revertedWithCustomError(multisig, 'ProposalCreationForbidden') + .withArgs(signers[1].address); + + await expect( + multisig + .connect(signers[0]) + .createProposal(dummyMetadata, [], 0, false, false, 0, startDate) + ).not.to.be.reverted; + }); + + it('reverts if `_msgSender` is not listed in the current block although he was listed in the last block', async () => { + await ethers.provider.send('evm_setAutomine', [false]); + const expectedSnapshotBlockNumber = ( + await ethers.provider.getBlock('latest') + ).number; + + // Transaction 1 & 2: Add signers[1] and remove signers[0] + const tx1 = await multisig.addAddresses([signers[1].address]); + const tx2 = await multisig.removeAddresses([signers[0].address]); + + // Transaction 3: Expect the proposal creation to fail for signers[0] because he was removed as a member in transaction 2. + await expect( + multisig + .connect(signers[0]) + .createProposal(dummyMetadata, [], 0, false, false, 0, startDate) + ) + .to.be.revertedWithCustomError(multisig, 'ProposalCreationForbidden') + .withArgs(signers[0].address); - it('emits the NumberStored event', async () => { - const newNumber = BigNumber.from(456); + // Transaction 4: Create the proposal as signers[1] + const tx4 = await multisig + .connect(signers[1]) + .createProposal(dummyMetadata, [], 0, false, false, 0, startDate); - const {plugin, daoMock} = await loadFixture(fixture); - await daoMock.setHasPermissionReturnValueMock(true); + // Check the listed members before the block is mined + expect(await multisig.isListed(signers[0].address)).to.equal(true); + expect(await multisig.isListed(signers[1].address)).to.equal(false); + + // Mine the block + await ethers.provider.send('evm_mine', []); + const minedBlockNumber = (await ethers.provider.getBlock('latest')) + .number; + + // Expect all transaction receipts to be in the same block after the snapshot block. + expect((await tx1.wait()).blockNumber).to.equal(minedBlockNumber); + expect((await tx2.wait()).blockNumber).to.equal(minedBlockNumber); + expect((await tx4.wait()).blockNumber).to.equal(minedBlockNumber); + expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); + + // Expect the listed member to have changed + expect(await multisig.isListed(signers[0].address)).to.equal(false); + expect(await multisig.isListed(signers[1].address)).to.equal(true); + + // Check the `ProposalCreatedEvent` for the creator and proposalId + const event = await findEvent( + tx4, + 'ProposalCreated' + ); + expect(event.args.proposalId).to.equal(id); + expect(event.args.creator).to.equal(signers[1].address); + + // Check that the snapshot block stored in the proposal struct + const proposal = await multisig.getProposal(id); + expect(proposal.parameters.snapshotBlock).to.equal( + expectedSnapshotBlockNumber + ); + + await ethers.provider.send('evm_setAutomine', [true]); + }); + + it('creates a proposal successfully and does not approve if not specified', async () => { + await time.setNextBlockTimestamp(startDate); + + await expect( + multisig.createProposal( + dummyMetadata, + [], + 0, + false, + false, + startDate, + endDate + ) + ) + .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .withArgs( + id, + signers[0].address, + startDate, + endDate, + dummyMetadata, + [], + 0 + ); + + const block = await ethers.provider.getBlock('latest'); + + const proposal = await multisig.getProposal(id); + expect(proposal.executed).to.equal(false); + expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + expect(proposal.parameters.minApprovals).to.equal( + multisigSettings.minApprovals + ); + expect(proposal.allowFailureMap).to.equal(0); + expect(proposal.parameters.startDate).to.equal(startDate); + expect(proposal.parameters.endDate).to.equal(endDate); + expect(proposal.approvals).to.equal(0); + expect(proposal.actions.length).to.equal(0); + + expect(await multisig.canApprove(id, signers[0].address)).to.be.true; + expect(await multisig.canApprove(id, signers[1].address)).to.be.false; + }); + + it('creates a proposal successfully and approves if specified', async () => { + const allowFailureMap = 1; + + await time.setNextBlockTimestamp(startDate); + + await expect( + multisig.createProposal( + dummyMetadata, + [], + allowFailureMap, + true, + false, + 0, + endDate + ) + ) + .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .withArgs( + id, + signers[0].address, + startDate, + endDate, + dummyMetadata, + [], + allowFailureMap + ) + .to.emit(multisig, MULTISIG_EVENTS.APPROVED) + .withArgs(id, signers[0].address); + + const block = await ethers.provider.getBlock('latest'); + + const proposal = await multisig.getProposal(id); + expect(proposal.executed).to.equal(false); + expect(proposal.allowFailureMap).to.equal(allowFailureMap); + expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); + expect(proposal.parameters.minApprovals).to.equal( + multisigSettings.minApprovals + ); + expect(proposal.parameters.startDate).to.equal(startDate); + expect(proposal.parameters.endDate).to.equal(endDate); + expect(proposal.approvals).to.equal(1); + }); + + it('increases the proposal count', async () => { + expect(await multisig.proposalCount()).to.equal(0); + + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + true, + false, + 0, + endDate + ); + expect(await multisig.proposalCount()).to.equal(1); + + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + true, + false, + 0, + endDate + ); + expect(await multisig.proposalCount()).to.equal(2); + }); + }); + + it('should revert if startDate is < than now', async () => { + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + + const currentDate = await time.latest(); + const startDateInThePast = currentDate - 1; + const endDate = 0; // startDate + minDuration + + await expect( + multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + true, + false, + startDateInThePast, + endDate + ) + ) + .to.be.revertedWithCustomError(multisig, 'DateOutOfBounds') + .withArgs( + currentDate + 1, // await takes one second + startDateInThePast + ); + }); + + it('should revert if endDate is < than startDate', async () => { + await multisig.initialize( + dao.address, + [signers[0].address], // signers[0] is listed + multisigSettings + ); + + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate - 1; // endDate < startDate + await expect( + multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + true, + false, + startDate, + endDate + ) + ) + .to.be.revertedWithCustomError(multisig, 'DateOutOfBounds') + .withArgs(startDate, endDate); + }); + }); + + context('Approving and executing proposals', async () => { + beforeEach(async () => { + multisigSettings.minApprovals = 3; + await multisig.initialize( + dao.address, + signers.slice(0, 5).map(s => s.address), + multisigSettings + ); + + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + }); + + describe('canApprove:', async () => { + it('returns `false` if the proposal is already executed', async () => { + await approveWithSigners(multisig, id, signers, [0, 1]); + + await multisig.connect(signers[2]).approve(id, true); + expect((await multisig.getProposal(id)).executed).to.be.true; + + expect(await multisig.canApprove(id, signers[3].address)).to.be.false; + }); + + it('returns `false` if the approver is not listed', async () => { + expect(await multisig.isListed(signers[9].address)).to.be.false; + + expect(await multisig.canApprove(id, signers[9].address)).to.be.false; + }); + + it('returns `false` if the approver has already approved', async () => { + await multisig.connect(signers[0]).approve(id, false); + expect(await multisig.canApprove(id, signers[0].address)).to.be.false; + }); + + it('returns `true` if the approver is listed', async () => { + expect(await multisig.canApprove(id, signers[0].address)).to.be.true; + }); + + it("returns `false` if the proposal hasn't started yet", async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + + expect(await multisig.canApprove(1, signers[0].address)).to.be.false; + + await time.increaseTo(startDate); + + expect(await multisig.canApprove(1, signers[0].address)).to.be.true; + }); + + it('returns `false` if the proposal has ended', async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + + expect(await multisig.canApprove(1, signers[0].address)).to.be.true; + + await time.increaseTo(endDate + 1); + + expect(await multisig.canApprove(1, signers[0].address)).to.be.false; + }); + }); + + describe('hasApproved', async () => { + it("returns `false` if user hasn't approved yet", async () => { + expect(await multisig.hasApproved(id, signers[0].address)).to.be.false; + }); + + it('returns `true` if user has approved', async () => { + await multisig.approve(id, true); + expect(await multisig.hasApproved(id, signers[0].address)).to.be.true; + }); + }); + + describe('approve:', async () => { + it('reverts when approving multiple times', async () => { + await multisig.approve(id, true); + + // Try to vote again + await expect(multisig.approve(id, true)) + .to.be.revertedWithCustomError(multisig, 'ApprovalCastForbidden') + .withArgs(id, signers[0].address); + }); + + it('reverts if minimal approval is not met yet', async () => { + const proposal = await multisig.getProposal(id); + expect(proposal.approvals).to.eq(0); + await expect(multisig.execute(id)) + .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') + .withArgs(id); + }); + + it('approves with the msg.sender address', async () => { + expect((await multisig.getProposal(id)).approvals).to.equal(0); + + const tx = await multisig.connect(signers[0]).approve(id, false); + + const event = await findEvent(tx, 'Approved'); + expect(event.args.proposalId).to.eq(id); + expect(event.args.approver).to.not.eq(multisig.address); + expect(event.args.approver).to.eq(signers[0].address); + + expect((await multisig.getProposal(id)).approvals).to.equal(1); + }); + + it("reverts if the proposal hasn't started yet", async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + + await expect(multisig.approve(1, false)).to.be.revertedWithCustomError( + multisig, + 'ApprovalCastForbidden' + ); + + await time.increaseTo(startDate); + + await expect(multisig.approve(1, false)).not.to.be.reverted; + }); + + it('reverts if the proposal has ended', async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + + await expect(multisig.connect(signers[1]).approve(1, false)).not.to.be + .reverted; + + await time.increaseTo(endDate + 1); + + await expect(multisig.approve(1, false)).to.be.revertedWithCustomError( + multisig, + 'ApprovalCastForbidden' + ); + }); + }); + + describe('canExecute:', async () => { + it('returns `false` if the proposal has not reached the minimum approval yet', async () => { + const proposal = await multisig.getProposal(id); + expect(proposal.approvals).to.be.lt(proposal.parameters.minApprovals); + + expect(await multisig.canExecute(id)).to.be.false; + }); + + it('returns `false` if the proposal is already executed', async () => { + await approveWithSigners(multisig, id, signers, [0, 1]); + await multisig.connect(signers[2]).approve(id, true); + + expect((await multisig.getProposal(id)).executed).to.be.true; + + expect(await multisig.canExecute(id)).to.be.false; + }); + + it('returns `true` if the proposal can be executed', async () => { + await approveWithSigners(multisig, id, signers, [0, 1, 2]); + + expect((await multisig.getProposal(id)).executed).to.be.false; + + expect(await multisig.canExecute(id)).to.be.true; + }); + + it("returns `false` if the proposal hasn't started yet", async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + + expect(await multisig.canExecute(1)).to.be.false; + + await time.increaseTo(startDate); + await multisig.connect(signers[0]).approve(1, false); + await multisig.connect(signers[1]).approve(1, false); + await multisig.connect(signers[2]).approve(1, false); + + expect(await multisig.canExecute(1)).to.be.true; + }); + + it('returns `false` if the proposal has ended', async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + + await multisig.connect(signers[0]).approve(1, false); + await multisig.connect(signers[1]).approve(1, false); + await multisig.connect(signers[2]).approve(1, false); + + expect(await multisig.canExecute(1)).to.be.true; + + await time.increaseTo(endDate + 1); + + expect(await multisig.canExecute(1)).to.be.false; + }); + }); + + describe('execute:', async () => { + it('reverts if the minimum approval is not met', async () => { + await expect(multisig.execute(id)) + .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') + .withArgs(id); + }); + + it('executes if the minimum approval is met', async () => { + await approveWithSigners(multisig, id, signers, [0, 1, 2]); + + const proposal = await multisig.getProposal(id); + + expect(proposal.parameters.minApprovals).to.equal( + multisigSettings.minApprovals + ); + expect(proposal.approvals).to.be.eq(multisigSettings.minApprovals); + + expect(await multisig.canExecute(id)).to.be.true; + await expect(multisig.execute(id)).not.to.be.reverted; + }); + + it('executes if the minimum approval is met and can be called by an unlisted accounts', async () => { + await approveWithSigners(multisig, id, signers, [0, 1, 2]); + + const proposal = await multisig.getProposal(id); + + expect(proposal.parameters.minApprovals).to.equal( + multisigSettings.minApprovals + ); + expect(proposal.approvals).to.be.eq(multisigSettings.minApprovals); + + expect(await multisig.canExecute(id)).to.be.true; + expect(await multisig.isListed(signers[9].address)).to.be.false; // signers[9] is not listed + await expect(multisig.connect(signers[9]).execute(id)).not.to.be + .reverted; + }); + + it('executes if the minimum approval is met when multisig with the `tryExecution` option', async () => { + await multisig.connect(signers[0]).approve(id, true); + + expect(await multisig.canExecute(id)).to.equal(false); + + // `tryExecution` is turned on but the vote is not decided yet + let tx = await multisig.connect(signers[1]).approve(id, true); + await expect( + findEventTopicLog( + tx, + DAO__factory.createInterface(), + IDAO_EVENTS.EXECUTED + ) + ).to.rejectedWith( + `Event "${IDAO_EVENTS.EXECUTED}" could not be found in transaction ${tx.hash}.` + ); + + expect(await multisig.canExecute(id)).to.equal(false); + + // `tryExecution` is turned off and the vote is decided + tx = await multisig.connect(signers[2]).approve(id, false); + await expect( + findEventTopicLog( + tx, + DAO__factory.createInterface(), + IDAO_EVENTS.EXECUTED + ) + ).to.rejectedWith( + `Event "${IDAO_EVENTS.EXECUTED}" could not be found in transaction ${tx.hash}.` + ); + + // `tryEarlyExecution` is turned on and the vote is decided + tx = await multisig.connect(signers[3]).approve(id, true); + { + const event = await findEventTopicLog( + tx, + DAO__factory.createInterface(), + IDAO_EVENTS.EXECUTED + ); + + expect(event.args.actor).to.equal(multisig.address); + expect(event.args.callId).to.equal(proposalIdToBytes32(id)); + expect(event.args.actions.length).to.equal(1); + expect(event.args.actions[0].to).to.equal(dummyActions[0].to); + expect(event.args.actions[0].value).to.equal(dummyActions[0].value); + expect(event.args.actions[0].data).to.equal(dummyActions[0].data); + expect(event.args.execResults).to.deep.equal(['0x']); + + const prop = await multisig.getProposal(id); + expect(prop.executed).to.equal(true); + } + + // check for the `ProposalExecuted` event in the multisig contract + { + const event = await findEvent( + tx, + IPROPOSAL_EVENTS.PROPOSAL_EXECUTED + ); + expect(event.args.proposalId).to.equal(id); + } + + // calling execute again should fail + await expect(multisig.execute(id)) + .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') + .withArgs(id); + }); + + it('emits the `ProposalExecuted` and `Executed` events', async () => { + await approveWithSigners(multisig, id, signers, [0, 1, 2]); + + await expect(multisig.connect(signers[3]).execute(id)) + .to.emit(dao, IDAO_EVENTS.EXECUTED) + .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_EXECUTED) + .to.not.emit(multisig, MULTISIG_EVENTS.APPROVED); + }); + + it('emits the `Approved`, `ProposalExecuted`, and `Executed` events if execute is called inside the `approve` method', async () => { + await approveWithSigners(multisig, id, signers, [0, 1]); + + await expect(multisig.connect(signers[2]).approve(id, true)) + .to.emit(dao, IDAO_EVENTS.EXECUTED) + .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_EXECUTED) + .to.emit(multisig, MULTISIG_EVENTS.APPROVED); + }); + + it("reverts if the proposal hasn't started yet", async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + + await expect(multisig.execute(1)).to.be.revertedWithCustomError( + multisig, + 'ProposalExecutionForbidden' + ); + + await time.increaseTo(startDate); + + await multisig.connect(signers[0]).approve(1, false); + await multisig.connect(signers[1]).approve(1, false); + await multisig.connect(signers[2]).approve(1, false); + + await expect(multisig.execute(1)).not.to.be.reverted; + }); + + it('reverts if the proposal has ended', async () => { + await multisig.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + await multisig.connect(signers[0]).approve(1, false); + await multisig.connect(signers[1]).approve(1, false); + await multisig.connect(signers[2]).approve(1, false); - await expect(plugin.storeNumber(newNumber)) - .to.emit(plugin, 'NumberStored') - .withArgs(newNumber); + await time.increase(10000); + await expect( + multisig.connect(signers[1]).execute(1) + ).to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden'); + }); }); }); }); diff --git a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts index 381580b2..fc5dbfed 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -1,137 +1,600 @@ -import {PLUGIN_SETUP_CONTRACT_NAME} from '../../plugin-settings'; -import buildMetadata from '../../src/build-metadata.json'; +import metadata from '../../../../src/plugins/governance/multisig/build-metadata.json'; import { - DAOMock, - DAOMock__factory, - MyPluginSetup, - MyPluginSetup__factory, - MyPlugin__factory, -} from '../../typechain'; -import {STORE_PERMISSION_ID, defaultInitData} from './11_plugin'; + DAO, + InterfaceBasedRegistryMock, + InterfaceBasedRegistryMock__factory, + IPluginRepo__factory, + Multisig, + MultisigSetup, + MultisigSetup__factory, + Multisig__factory, + PluginRepo, + PluginRepo__factory, + PluginSetupProcessor, + PluginSetupProcessor__factory, +} from '../../../../typechain'; import { - ADDRESS, + InstallationPreparedEvent, + UpdatePreparedEvent, +} from '../../../../typechain/PluginSetupProcessor'; +import {hashHelpers} from '../../../../utils/psp'; +import {deployNewDAO} from '../../../test-utils/dao'; +import {deployWithProxy} from '../../../test-utils/proxy'; +import { + MULTISIG_INTERFACE, + MultisigSettings, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, +} from './multisig-constants'; +import { + getInterfaceId, + findEvent, Operation, - PERMISSION_MANAGER_FLAGS, + DAO_PERMISSIONS, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; -import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ethers} from 'hardhat'; -type FixtureResult = { - deployer: SignerWithAddress; - alice: SignerWithAddress; - bob: SignerWithAddress; - pluginSetup: MyPluginSetup; - prepareInstallationInputs: string; - prepareUninstallationInputs: string; - daoMock: DAOMock; -}; - -async function fixture(): Promise { - const [deployer, alice, bob] = await ethers.getSigners(); - const daoMock = await new DAOMock__factory(deployer).deploy(); - const pluginSetup = await new MyPluginSetup__factory(deployer).deploy(); - - const prepareInstallationInputs = ethers.utils.defaultAbiCoder.encode( - getNamedTypesFromMetadata( - buildMetadata.pluginSetup.prepareInstallation.inputs - ), - [defaultInitData.number] - ); - - const prepareUninstallationInputs = ethers.utils.defaultAbiCoder.encode( - getNamedTypesFromMetadata( - buildMetadata.pluginSetup.prepareUninstallation.inputs - ), - [] - ); - - return { - deployer, - alice, - bob, - pluginSetup, - prepareInstallationInputs, - prepareUninstallationInputs, - daoMock, - }; -} - -describe(PLUGIN_SETUP_CONTRACT_NAME, function () { +const abiCoder = ethers.utils.defaultAbiCoder; +const AddressZero = ethers.constants.AddressZero; +const EMPTY_DATA = '0x'; + +let defaultMultisigSettings: MultisigSettings; + +describe('MultisigSetup', function () { + let signers: SignerWithAddress[]; + let multisigSetup: MultisigSetup; + let MultisigFactory: Multisig__factory; + let implementationAddress: string; + let targetDao: DAO; + let minimum_data: any; + + before(async () => { + signers = await ethers.getSigners(); + targetDao = await deployNewDAO(signers[0]); + + defaultMultisigSettings = { + onlyListed: true, + minApprovals: 1, + }; + + minimum_data = abiCoder.encode( + getNamedTypesFromMetadata( + metadata.pluginSetup.prepareInstallation.inputs + ), + [[signers[0].address], Object.values(defaultMultisigSettings)] + ); + + const MultisigSetup = new MultisigSetup__factory(signers[0]); + multisigSetup = await MultisigSetup.deploy(); + + MultisigFactory = new Multisig__factory(signers[0]); + + implementationAddress = await multisigSetup.implementation(); + }); + + it('does not support the empty interface', async () => { + expect(await multisigSetup.supportsInterface('0xffffffff')).to.be.false; + }); + + it('creates multisig base with the correct interface', async () => { + const factory = new Multisig__factory(signers[0]); + const multisigContract = factory.attach(implementationAddress); + + expect( + await multisigContract.supportsInterface( + getInterfaceId(MULTISIG_INTERFACE) + ) + ).to.be.true; + }); + describe('prepareInstallation', async () => { - it('returns the plugin, helpers, and permissions', async () => { - const {deployer, pluginSetup, prepareInstallationInputs, daoMock} = - await loadFixture(fixture); + it('fails if data is empty, or not of minimum length', async () => { + await expect( + multisigSetup.prepareInstallation(targetDao.address, EMPTY_DATA) + ).to.be.reverted; + + await expect( + multisigSetup.prepareInstallation( + targetDao.address, + minimum_data.substring(0, minimum_data.length - 2) + ) + ).to.be.reverted; + + await expect( + multisigSetup.prepareInstallation(targetDao.address, minimum_data) + ).not.to.be.reverted; + }); + + it('reverts if zero members are provided in `_data`', async () => { + const noMembers: string[] = []; + + const wrongPrepareInstallationData = abiCoder.encode( + getNamedTypesFromMetadata( + metadata.pluginSetup.prepareInstallation.inputs + ), + [noMembers, defaultMultisigSettings] + ); + + const nonce = await ethers.provider.getTransactionCount( + multisigSetup.address + ); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: multisigSetup.address, + nonce, + }); + + const multisig = MultisigFactory.attach(anticipatedPluginAddress); + + await expect( + multisigSetup.prepareInstallation( + targetDao.address, + wrongPrepareInstallationData + ) + ) + .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + .withArgs(0, 1); + }); + + it('reverts if the `minApprovals` value in `_data` is zero', async () => { + const multisigSettings: MultisigSettings = { + onlyListed: true, + minApprovals: 0, + }; + const members = [signers[0].address]; + + const wrongPrepareInstallationData = abiCoder.encode( + getNamedTypesFromMetadata( + metadata.pluginSetup.prepareInstallation.inputs + ), + [members, multisigSettings] + ); + + const nonce = await ethers.provider.getTransactionCount( + multisigSetup.address + ); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: multisigSetup.address, + nonce, + }); + const multisig = MultisigFactory.attach(anticipatedPluginAddress); + + await expect( + multisigSetup.prepareInstallation( + targetDao.address, + wrongPrepareInstallationData + ) + ) + .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + .withArgs(1, 0); + }); + + it('reverts if the `minApprovals` value in `_data` is greater than the number members', async () => { + const multisigSettings: MultisigSettings = { + onlyListed: true, + minApprovals: 2, + }; + const members = [signers[0].address]; + + const wrongPrepareInstallationData = abiCoder.encode( + getNamedTypesFromMetadata( + metadata.pluginSetup.prepareInstallation.inputs + ), + [members, multisigSettings] + ); const nonce = await ethers.provider.getTransactionCount( - pluginSetup.address + multisigSetup.address ); const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: pluginSetup.address, + from: multisigSetup.address, + nonce, + }); + const multisig = MultisigFactory.attach(anticipatedPluginAddress); + + await expect( + multisigSetup.prepareInstallation( + targetDao.address, + wrongPrepareInstallationData + ) + ) + .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + .withArgs(members.length, multisigSettings.minApprovals); + }); + + it('returns the plugin, helpers, and permissions', async () => { + const nonce = await ethers.provider.getTransactionCount( + multisigSetup.address + ); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: multisigSetup.address, nonce, }); const { plugin, preparedSetupData: {helpers, permissions}, - } = await pluginSetup.callStatic.prepareInstallation( - daoMock.address, - prepareInstallationInputs + } = await multisigSetup.callStatic.prepareInstallation( + targetDao.address, + minimum_data ); expect(plugin).to.be.equal(anticipatedPluginAddress); expect(helpers.length).to.be.equal(0); - expect(permissions.length).to.be.equal(1); + expect(permissions.length).to.be.equal(3); expect(permissions).to.deep.equal([ [ Operation.Grant, plugin, - daoMock.address, - PERMISSION_MANAGER_FLAGS.NO_CONDITION, - STORE_PERMISSION_ID, + targetDao.address, + AddressZero, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Grant, + plugin, + targetDao.address, + AddressZero, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + ], + [ + Operation.Grant, + targetDao.address, + plugin, + AddressZero, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID, ], ]); + }); + + it('sets up the plugin', async () => { + const daoAddress = targetDao.address; - await pluginSetup.prepareInstallation( - daoMock.address, - prepareInstallationInputs + const nonce = await ethers.provider.getTransactionCount( + multisigSetup.address ); - const myPlugin = new MyPlugin__factory(deployer).attach(plugin); + const anticipatedPluginAddress = ethers.utils.getContractAddress({ + from: multisigSetup.address, + nonce, + }); - // initialization is correct - expect(await myPlugin.dao()).to.eq(daoMock.address); - expect(await myPlugin.number()).to.be.eq(defaultInitData.number); + await multisigSetup.prepareInstallation(daoAddress, minimum_data); + + const factory = new Multisig__factory(signers[0]); + const multisigContract = factory.attach(anticipatedPluginAddress); + + expect(await multisigContract.dao()).to.eq(daoAddress); + expect(await multisigContract.addresslistLength()).to.be.eq(1); + const settings = await multisigContract.multisigSettings(); + expect(settings.onlyListed).to.be.true; + expect(settings.minApprovals).to.eq(1); }); }); - describe('prepareUninstallation', async () => { - it('returns the permissions', async () => { - const {pluginSetup, daoMock, prepareUninstallationInputs} = - await loadFixture(fixture); + describe('prepareUpdate', async () => { + it('should return nothing', async () => { + const dao = ethers.Wallet.createRandom().address; + const currentBuild = 1; + const prepareUpdateData = await multisigSetup.callStatic.prepareUpdate( + dao, + currentBuild, + { + currentHelpers: [ + ethers.Wallet.createRandom().address, + ethers.Wallet.createRandom().address, + ], + data: '0x00', + plugin: ethers.Wallet.createRandom().address, + } + ); + expect(prepareUpdateData.initData).to.be.eq('0x'); + expect(prepareUpdateData.preparedSetupData.permissions).to.be.eql([]); + expect(prepareUpdateData.preparedSetupData.helpers).to.be.eql([]); + }); + }); - const dummyAddr = ADDRESS.ZERO; + describe('prepareUninstallation', async () => { + it('correctly returns permissions', async () => { + const plugin = ethers.Wallet.createRandom().address; - const permissions = await pluginSetup.callStatic.prepareUninstallation( - daoMock.address, + const permissions = await multisigSetup.callStatic.prepareUninstallation( + targetDao.address, { - plugin: dummyAddr, + plugin, currentHelpers: [], - data: prepareUninstallationInputs, + data: EMPTY_DATA, } ); - expect(permissions.length).to.be.equal(1); + expect(permissions.length).to.be.equal(3); expect(permissions).to.deep.equal([ [ Operation.Revoke, - dummyAddr, - daoMock.address, - PERMISSION_MANAGER_FLAGS.NO_CONDITION, - STORE_PERMISSION_ID, + plugin, + targetDao.address, + AddressZero, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, + ], + [ + Operation.Revoke, + plugin, + targetDao.address, + AddressZero, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + ], + [ + Operation.Revoke, + targetDao.address, + plugin, + AddressZero, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID, ], ]); }); }); + + // TODO: Improve checks by using smock with the proxy (We don't know how yet) + describe('Updates', async () => { + let psp: PluginSetupProcessor; + let setup1: MultisigSetup; + let setup2: MultisigSetup; + let dao: DAO; + let managingDAO: DAO; + let owner: SignerWithAddress; + let pluginRepoRegistry: InterfaceBasedRegistryMock; + let pluginRepo: PluginRepo; + + before(async () => { + [owner] = await ethers.getSigners(); + managingDAO = await deployNewDAO(owner); + + // Create the PluginRepo + const pluginRepoFactory = new PluginRepo__factory(owner); + pluginRepo = await deployWithProxy(pluginRepoFactory); + await pluginRepo.initialize(owner.address); + + // Create the PluginRepoRegistry + const pluginRepoRegistryFactory = new InterfaceBasedRegistryMock__factory( + owner + ); + pluginRepoRegistry = await pluginRepoRegistryFactory.deploy(); + pluginRepoRegistry.initialize( + managingDAO.address, + getInterfaceId(IPluginRepo__factory.createInterface()) + ); + + // Grant the owner full rights on the registry + await managingDAO.grant( + pluginRepoRegistry.address, + owner.address, + await pluginRepoRegistry.REGISTER_PERMISSION_ID() + ); + + // Register the PluginRepo in the registry + await pluginRepoRegistry.register(pluginRepo.address); + + // Create the PluginSetupProcessor + const pspFactory = new PluginSetupProcessor__factory(owner); + psp = await pspFactory.deploy(pluginRepoRegistry.address); + + // Prepare all MultisigSetup' - We can reuse the same for now + const multisigSetupFactory = new MultisigSetup__factory(owner); + setup1 = await multisigSetupFactory.deploy(); + setup2 = await multisigSetupFactory.deploy(); + + // Create the versions in the plugin repo + await expect(pluginRepo.createVersion(1, setup1.address, '0x00', '0x00')) + .to.emit(pluginRepo, 'VersionCreated') + .withArgs(1, 1, setup1.address, '0x00'); + await expect(pluginRepo.createVersion(1, setup2.address, '0x00', '0x00')) + .to.emit(pluginRepo, 'VersionCreated') + .withArgs(1, 2, setup2.address, '0x00'); + }); + + describe('Release 1 Build 1', () => { + let plugin: Multisig; + let helpers: string[]; + + before(async () => { + dao = await deployNewDAO(owner); + // grant the owner full permission for plugins + await dao.applySingleTargetPermissions(psp.address, [ + { + operation: Operation.Grant, + who: owner.address, + permissionId: await psp.APPLY_INSTALLATION_PERMISSION_ID(), + }, + { + operation: Operation.Grant, + who: owner.address, + permissionId: await psp.APPLY_UPDATE_PERMISSION_ID(), + }, + { + operation: Operation.Grant, + who: owner.address, + permissionId: await psp.APPLY_UNINSTALLATION_PERMISSION_ID(), + }, + ]); + // grant the PSP root to apply stuff + await dao.grant( + dao.address, + psp.address, + await dao.ROOT_PERMISSION_ID() + ); + }); + + it('should install', async () => { + const tx = await psp.prepareInstallation(dao.address, { + pluginSetupRef: { + versionTag: { + build: 1, + release: 1, + }, + pluginSetupRepo: pluginRepo.address, + }, + data: ethers.utils.defaultAbiCoder.encode( + ['address[]', '(bool, uint16)'], + [[owner.address], [true, 1]] + ), + }); + const preparedEvent = await findEvent( + tx, + 'InstallationPrepared' + ); + + await expect( + psp.applyInstallation(dao.address, { + pluginSetupRef: { + versionTag: { + build: 1, + release: 1, + }, + pluginSetupRepo: pluginRepo.address, + }, + helpersHash: hashHelpers( + preparedEvent.args.preparedSetupData.helpers + ), + permissions: preparedEvent.args.preparedSetupData.permissions, + plugin: preparedEvent.args.plugin, + }) + ).to.emit(psp, 'InstallationApplied'); + + plugin = Multisig__factory.connect(preparedEvent.args.plugin, owner); + helpers = preparedEvent.args.preparedSetupData.helpers; + + expect(await plugin.implementation()).to.be.eq( + await setup1.implementation() + ); + }); + + it('should update to Release 1 Build 2', async () => { + // grant psp permission to update the proxy implementation + await dao.grant( + plugin.address, + psp.address, + await plugin.UPGRADE_PLUGIN_PERMISSION_ID() + ); + + const tx = await psp.prepareUpdate(dao.address, { + currentVersionTag: { + release: 1, + build: 1, + }, + newVersionTag: { + release: 1, + build: 2, + }, + pluginSetupRepo: pluginRepo.address, + setupPayload: { + plugin: plugin.address, + currentHelpers: helpers, + data: '0x00', + }, + }); + const preparedEvent = await findEvent( + tx, + 'UpdatePrepared' + ); + + await expect( + psp.applyUpdate(dao.address, { + plugin: plugin.address, + helpersHash: hashHelpers( + preparedEvent.args.preparedSetupData.helpers + ), + permissions: preparedEvent.args.preparedSetupData.permissions, + initData: preparedEvent.args.initData, + pluginSetupRef: { + versionTag: { + release: 1, + build: 2, + }, + pluginSetupRepo: pluginRepo.address, + }, + }) + ).to.emit(psp, 'UpdateApplied'); + + expect(await plugin.implementation()).to.be.eq( + await setup2.implementation() + ); + }); + }); + + describe('Release 1 Build 2', () => { + before(async () => { + dao = await deployNewDAO(owner); + // grant the owner full permission for plugins + await dao.applySingleTargetPermissions(psp.address, [ + { + operation: Operation.Grant, + who: owner.address, + permissionId: await psp.APPLY_INSTALLATION_PERMISSION_ID(), + }, + { + operation: Operation.Grant, + who: owner.address, + permissionId: await psp.APPLY_UPDATE_PERMISSION_ID(), + }, + { + operation: Operation.Grant, + who: owner.address, + permissionId: await psp.APPLY_UNINSTALLATION_PERMISSION_ID(), + }, + ]); + // grant the PSP root to apply stuff + await dao.grant( + dao.address, + psp.address, + await dao.ROOT_PERMISSION_ID() + ); + }); + + it('should install', async () => { + const tx = await psp.prepareInstallation(dao.address, { + pluginSetupRef: { + versionTag: { + release: 1, + build: 2, + }, + pluginSetupRepo: pluginRepo.address, + }, + data: ethers.utils.defaultAbiCoder.encode( + ['address[]', '(bool, uint16)'], + [[owner.address], [true, 1]] + ), + }); + const preparedEvent = await findEvent( + tx, + 'InstallationPrepared' + ); + + await expect( + psp.applyInstallation(dao.address, { + pluginSetupRef: { + versionTag: { + release: 1, + build: 2, + }, + pluginSetupRepo: pluginRepo.address, + }, + helpersHash: hashHelpers( + preparedEvent.args.preparedSetupData.helpers + ), + permissions: preparedEvent.args.preparedSetupData.permissions, + plugin: preparedEvent.args.plugin, + }) + ).to.emit(psp, 'InstallationApplied'); + + let plugin = Multisig__factory.connect( + preparedEvent.args.plugin, + owner + ); + expect(await plugin.implementation()).to.be.eq( + await setup2.implementation() + ); + }); + }); + }); }); diff --git a/packages/contracts/test/multisig-constants.ts b/packages/contracts/test/multisig-constants.ts new file mode 100644 index 00000000..7175b09c --- /dev/null +++ b/packages/contracts/test/multisig-constants.ts @@ -0,0 +1,22 @@ +import {ethers} from 'hardhat'; + +export const MULTISIG_EVENTS = { + MULTISIG_SETTINGS_UPDATED: 'MultisigSettingsUpdated', + APPROVED: 'Approved', +}; + +export const MULTISIG_INTERFACE = new ethers.utils.Interface([ + 'function initialize(address,address[],tuple(bool,uint16))', + 'function updateMultisigSettings(tuple(bool,uint16))', + 'function createProposal(bytes,tuple(address,uint256,bytes)[],uint256,bool,bool,uint64,uint64) ', + 'function getProposal(uint256)', +]); + +export const UPDATE_MULTISIG_SETTINGS_PERMISSION_ID = ethers.utils.id( + 'UPDATE_MULTISIG_SETTINGS_PERMISSION' +); + +export type MultisigSettings = { + minApprovals: number; + onlyListed: boolean; +}; From 3c79bb5aee4375c0d509968d461fa8d11485d669 Mon Sep 17 00:00:00 2001 From: Michael Heuer Date: Wed, 28 Feb 2024 18:22:45 +0100 Subject: [PATCH 02/17] feat: copy admin deploy scripts --- .../deploy/20_new_version/21_setup.ts | 2 +- .../20_new_version/22_setup_conclude.ts | 9 +- .../deploy/20_new_version/23_publish.ts | 74 +++++++++--- .../deploy/30_upgrade_repo/31_upgrade_repo.ts | 58 ++++++++- .../40_conclude/41_transfer_ownership.ts | 111 ++++++++++++++++++ .../{41_conclude.ts => 42_info.ts} | 0 packages/contracts/utils/helpers.ts | 62 +++++++++- 7 files changed, 287 insertions(+), 29 deletions(-) create mode 100644 packages/contracts/deploy/40_conclude/41_transfer_ownership.ts rename packages/contracts/deploy/40_conclude/{41_conclude.ts => 42_info.ts} (100%) diff --git a/packages/contracts/deploy/20_new_version/21_setup.ts b/packages/contracts/deploy/20_new_version/21_setup.ts index 7ef97b63..a52a1b28 100644 --- a/packages/contracts/deploy/20_new_version/21_setup.ts +++ b/packages/contracts/deploy/20_new_version/21_setup.ts @@ -21,7 +21,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }); console.log( - `Deployed '${PLUGIN_SETUP_CONTRACT_NAME}' contract at '${res.address}'` + `Deployed contract '${PLUGIN_SETUP_CONTRACT_NAME}' at ${res.address}.` ); }; diff --git a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts index 1b514b68..8639c348 100644 --- a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts +++ b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts @@ -1,5 +1,5 @@ import {PLUGIN_SETUP_CONTRACT_NAME} from '../../plugin-settings'; -import {MyPluginSetup__factory, MyPlugin__factory} from '../../typechain'; +import {AdminSetup__factory, Admin__factory} from '../../typechain'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; @@ -17,12 +17,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Get the plugin setup address const setupDeployment = await deployments.get(PLUGIN_SETUP_CONTRACT_NAME); - const setup = MyPluginSetup__factory.connect( - setupDeployment.address, - deployer - ); + const setup = AdminSetup__factory.connect(setupDeployment.address, deployer); // Get the plugin implementation address - const implementation = MyPlugin__factory.connect( + const implementation = Admin__factory.connect( await setup.implementation(), deployer ); diff --git a/packages/contracts/deploy/20_new_version/23_publish.ts b/packages/contracts/deploy/20_new_version/23_publish.ts index ea146b9b..0dbba782 100644 --- a/packages/contracts/deploy/20_new_version/23_publish.ts +++ b/packages/contracts/deploy/20_new_version/23_publish.ts @@ -1,5 +1,6 @@ import { METADATA, + PLUGIN_CONTRACT_NAME, PLUGIN_REPO_ENS_SUBDOMAIN_NAME, PLUGIN_SETUP_CONTRACT_NAME, VERSION, @@ -7,6 +8,8 @@ import { import { findPluginRepo, getPastVersionCreatedEvents, + impersonatedManagementDaoSigner, + isLocal, pluginEnsDomain, } from '../../utils/helpers'; import { @@ -14,6 +17,7 @@ import { toHex, uploadToIPFS, } from '@aragon/osx-commons-sdk'; +import {writeFile} from 'fs/promises'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; @@ -77,25 +81,42 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ); } - // Create Version + if (setup == undefined || setup?.receipt == undefined) { + throw Error('setup deployment unavailable'); + } + + const isDeployerMaintainer = await pluginRepo.isGranted( + pluginRepo.address, + deployer.address, + PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, + [] + ); + + // If this is a local deployment and the deployer doesn't have `MAINTAINER_PERMISSION_ID` permission + // we impersonate the management DAO for integration testing purposes. + const signer = + isDeployerMaintainer || !isLocal(hre) + ? deployer + : await impersonatedManagementDaoSigner(hre); + + // Check if the signer has the permission to maintain the plugin repo if ( - await pluginRepo.callStatic.isGranted( + await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + signer.address, PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, [] ) ) { - const tx = await pluginRepo.createVersion( - VERSION.release, - setup.address, - toHex(buildMetadataURI), - toHex(releaseMetadataURI) - ); - - if (setup == undefined || setup?.receipt == undefined) { - throw Error('setup deployment unavailable'); - } + // Create the new version + const tx = await pluginRepo + .connect(signer) + .createVersion( + VERSION.release, + setup.address, + toHex(buildMetadataURI), + toHex(releaseMetadataURI) + ); await tx.wait(); @@ -110,8 +131,31 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { `Published ${PLUGIN_SETUP_CONTRACT_NAME} at ${setup.address} in PluginRepo ${PLUGIN_REPO_ENS_SUBDOMAIN_NAME} at ${pluginRepo.address}.` ); } else { - throw Error( - `The new version cannot be published because the deployer ('${deployer.address}') is lacking the ${PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID} permission on repo (${pluginRepo.address}).` + // The deployer does not have `MAINTAINER_PERMISSION_ID` permission and we are not deploying to a production network, + // so we write the data into a file for a management DAO member to create a proposal from it. + const data = { + proposalTitle: `Publish '${PLUGIN_CONTRACT_NAME}' plugin v${VERSION.release}.${VERSION.build}`, + proposalSummary: `Publishes v${VERSION.release}.${VERSION.build} of the '${PLUGIN_CONTRACT_NAME}' plugin in the '${ensDomain}' plugin repo.`, + proposalDescription: `Publishes the '${PLUGIN_SETUP_CONTRACT_NAME}' deployed at '${setup.address}' + as v${VERSION.release}.${VERSION.build} in the '${ensDomain}' plugin repo at '${pluginRepo.address}', + with release metadata '${releaseMetadataURI}' and (immutable) build metadata '${buildMetadataURI}'.`, + actions: [ + { + to: pluginRepo.address, + createVersion: { + _release: VERSION.release, + _pluginSetup: setup.address, + _buildMetadata: toHex(buildMetadataURI), + _releaseMetadata: toHex(releaseMetadataURI), + }, + }, + ], + }; + + const path = `./createVersionProposalData-${hre.network.name}.json`; + await writeFile(path, JSON.stringify(data, null, 2)); + console.log( + `Saved data to '${path}'. Use this to create a proposal on the managing DAO calling the 'createVersion' function on the ${ensDomain} plugin repo deployed at ${pluginRepo.address}.` ); } }; diff --git a/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts b/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts index f862559c..7ca2b168 100644 --- a/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts +++ b/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts @@ -1,4 +1,9 @@ -import {findPluginRepo, getProductionNetworkName} from '../../utils/helpers'; +import { + findPluginRepo, + getProductionNetworkName, + impersonatedManagementDaoSigner, + isLocal, +} from '../../utils/helpers'; import { getLatestNetworkDeployment, getNetworkNameByAlias, @@ -9,6 +14,7 @@ import { } from '@aragon/osx-commons-sdk'; import {PluginRepo__factory} from '@aragon/osx-ethers'; import {BytesLike} from 'ethers'; +import {writeFile} from 'fs/promises'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; @@ -74,11 +80,25 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { */ const initializeFromCalldata: BytesLike = []; - // Check if deployer has the permission to upgrade the plugin repo + const isDeployerUpgrader = await pluginRepo.isGranted( + pluginRepo.address, + deployer.address, + PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, + [] + ); + + // If this is a local deployment and the deployer doesn't have `UPGRADE_REPO_PERMISSION_ID` permission + // we impersonate the management DAO for integration testing purposes. + const signer = + isDeployerUpgrader || !isLocal(hre) + ? deployer + : await impersonatedManagementDaoSigner(hre); + + // Check if the signer has the permission to upgrade the plugin repo if ( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + signer.address, PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, [] ) @@ -94,9 +114,35 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await pluginRepo.upgradeTo(latestPluginRepoImplementation.address); } } else { - throw Error( - `The new version cannot be published because the deployer ('${deployer.address}') - is lacking the ${PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID} permission.` + // The deployer does not have `UPGRADE_REPO_PERMISSION_ID` permission and we are not deploying to a production network, + // so we write the data into a file for a management DAO member to create a proposal from it. + const upgradeAction = + initializeFromCalldata.length === 0 + ? { + to: pluginRepo.address, + upgradeTo: { + NewImplementation: latestPluginRepoImplementation.address, + }, + } + : { + to: pluginRepo.address, + upgradeToAndCall: { + NewImplementation: latestPluginRepoImplementation.address, + Data: initializeFromCalldata, + PayableAmount: 0, + }, + }; + const data = { + proposalTitle: `Upgrade the '${ensDomain}' plugin repo`, + proposalSummary: `Upgrades '${ensDomain}' plugin repo at '${pluginRepo.address}',' plugin in the '${ensDomain}' plugin repo.`, + proposalDescription: `TODO: Describe the changes to the 'PluginRepo' implementation.`, + actions: [upgradeAction], + }; + + const path = `./upgradeRepoProposalData-${hre.network.name}.json`; + await writeFile(path, JSON.stringify(data, null, 2)); + console.log( + `Saved data to '${path}'. Use this to create a proposal on the managing DAO calling the 'upgradeTo' or 'upgradeToAndCall' function on the ${ensDomain} plugin repo deployed at ${pluginRepo.address}.` ); } }; diff --git a/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts b/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts new file mode 100644 index 00000000..aac32467 --- /dev/null +++ b/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts @@ -0,0 +1,111 @@ +import {findPluginRepo, getManagementDao} from '../../utils/helpers'; +import { + DAO_PERMISSIONS, + Operation, + PERMISSION_MANAGER_FLAGS, + PLUGIN_REPO_PERMISSIONS, +} from '@aragon/osx-commons-sdk'; +import {DAOStructs} from '@aragon/osx-ethers'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import path from 'path'; + +/** + * Creates a plugin repo under Aragon's ENS base domain with subdomain requested in the `./plugin-settings.ts` file. + * @param {HardhatRuntimeEnvironment} hre + */ +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + // Get PluginRepo + const {pluginRepo, ensDomain} = await findPluginRepo(hre); + if (pluginRepo === null) { + throw `PluginRepo '${ensDomain}' does not exist yet.`; + } + + // Get the management DAO address + const managementDao = await getManagementDao(hre); + const [deployer] = await hre.ethers.getSigners(); + + console.log( + `Transferring ownership of the '${ensDomain}' plugin repo at '${pluginRepo.address}' from the deployer '${deployer.address}' to the management DAO at '${managementDao.address}'...` + ); + + const permissions: DAOStructs.MultiTargetPermissionStruct[] = [ + // Grant to the managment DAO + { + operation: Operation.Grant, + where: pluginRepo.address, + who: managementDao.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, + }, + { + operation: Operation.Grant, + where: pluginRepo.address, + who: managementDao.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, + }, + { + operation: Operation.Grant, + where: pluginRepo.address, + who: managementDao.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: DAO_PERMISSIONS.ROOT_PERMISSION_ID, + }, + // Revoke from deployer + { + operation: Operation.Revoke, + where: pluginRepo.address, + who: deployer.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, + }, + { + operation: Operation.Revoke, + where: pluginRepo.address, + who: deployer.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, + }, + { + operation: Operation.Revoke, + where: pluginRepo.address, + who: deployer.address, + condition: PERMISSION_MANAGER_FLAGS.NO_CONDITION, + permissionId: DAO_PERMISSIONS.ROOT_PERMISSION_ID, + }, + ]; + + await pluginRepo.connect(deployer).applyMultiTargetPermissions(permissions); +}; + +export default func; +func.tags = ['TransferOwnershipToManagmentDao']; + +/** + * Skips the transfer of ownership if it has already been transferred to the management DAO + * @param {HardhatRuntimeEnvironment} hre + */ +func.skip = async (hre: HardhatRuntimeEnvironment) => { + console.log(`\n🏗️ ${path.basename(__filename)}:`); + + const {pluginRepo, ensDomain} = await findPluginRepo(hre); + if (pluginRepo === null) { + throw `PluginRepo '${ensDomain}' does not exist yet.`; + } + const managementDao = await getManagementDao(hre); + + const mgmtDaoHasRootPerm = await pluginRepo.isGranted( + pluginRepo.address, + managementDao.address, + DAO_PERMISSIONS.ROOT_PERMISSION_ID, + [] + ); + + if (mgmtDaoHasRootPerm) + console.log( + `The ownership of the plugin repo '${ensDomain}' has already been transferred to the management DAO. Skipping...` + ); + + return mgmtDaoHasRootPerm; +}; diff --git a/packages/contracts/deploy/40_conclude/41_conclude.ts b/packages/contracts/deploy/40_conclude/42_info.ts similarity index 100% rename from packages/contracts/deploy/40_conclude/41_conclude.ts rename to packages/contracts/deploy/40_conclude/42_info.ts diff --git a/packages/contracts/utils/helpers.ts b/packages/contracts/utils/helpers.ts index c76f6992..6b13eb8d 100644 --- a/packages/contracts/utils/helpers.ts +++ b/packages/contracts/utils/helpers.ts @@ -10,6 +10,8 @@ import { findEvent, } from '@aragon/osx-commons-sdk'; import { + DAO, + DAO__factory, ENSSubdomainRegistrar__factory, ENS__factory, IAddrResolver__factory, @@ -17,7 +19,9 @@ import { PluginRepoEvents, PluginRepo__factory, } from '@aragon/osx-ethers'; -import {ContractTransaction} from 'ethers'; +import {setBalance} from '@nomicfoundation/hardhat-network-helpers'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {BigNumber, ContractTransaction} from 'ethers'; import {LogDescription, defaultAbiCoder, keccak256} from 'ethers/lib/utils'; import {ethers} from 'hardhat'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -68,6 +72,7 @@ export async function findPluginRepo( ): Promise<{pluginRepo: PluginRepo | null; ensDomain: string}> { const [deployer] = await hre.ethers.getSigners(); const productionNetworkName: string = getProductionNetworkName(hre); + const network = getNetworkNameByAlias(productionNetworkName); if (network === null) { throw new UnsupportedNetworkError(productionNetworkName); @@ -107,6 +112,61 @@ export async function findPluginRepo( } } +export async function getManagementDao( + hre: HardhatRuntimeEnvironment +): Promise { + const [deployer] = await hre.ethers.getSigners(); + const productionNetworkName = getProductionNetworkName(hre); + const network = getNetworkNameByAlias(productionNetworkName); + if (network === null) { + throw new UnsupportedNetworkError(productionNetworkName); + } + const networkDeployments = getLatestNetworkDeployment(network); + if (networkDeployments === null) { + throw `Deployments are not available on network ${network}.`; + } + + return DAO__factory.connect( + networkDeployments.ManagementDAOProxy.address, + deployer + ); +} + +export async function getManagementDaoMultisig( + hre: HardhatRuntimeEnvironment +): Promise { + const [deployer] = await hre.ethers.getSigners(); + const productionNetworkName = getProductionNetworkName(hre); + const network = getNetworkNameByAlias(productionNetworkName); + if (network === null) { + throw new UnsupportedNetworkError(productionNetworkName); + } + const networkDeployments = getLatestNetworkDeployment(network); + if (networkDeployments === null) { + throw `Deployments are not available on network ${network}.`; + } + + return DAO__factory.connect( + networkDeployments.ManagementDAOProxy.address, + deployer + ); +} + +export async function impersonatedManagementDaoSigner( + hre: HardhatRuntimeEnvironment +): Promise { + return await (async () => { + const managementDaoProxy = getManagementDao(hre); + const signer = await hre.ethers.getImpersonatedSigner( + ( + await managementDaoProxy + ).address + ); + await setBalance(signer.address, BigNumber.from(10).pow(18)); + return signer; + })(); +} + export type EventWithBlockNumber = { event: LogDescription; blockNumber: number; From be0009308b04cc6e7c7756b94d68077e50483da8 Mon Sep 17 00:00:00 2001 From: Michael Heuer Date: Wed, 28 Feb 2024 18:23:48 +0100 Subject: [PATCH 03/17] feat: copy admin integration tests and helpers --- .../20_integration-testing/21_deployment.ts | 27 ++++--- .../22_setup-processing.ts | 74 +++++++++++-------- .../20_integration-testing/test-helpers.ts | 11 +-- 3 files changed, 67 insertions(+), 45 deletions(-) diff --git a/packages/contracts/test/20_integration-testing/21_deployment.ts b/packages/contracts/test/20_integration-testing/21_deployment.ts index 1bdcfb7f..eb6509e9 100644 --- a/packages/contracts/test/20_integration-testing/21_deployment.ts +++ b/packages/contracts/test/20_integration-testing/21_deployment.ts @@ -13,6 +13,8 @@ import { uploadToIPFS, } from '@aragon/osx-commons-sdk'; import { + DAO, + DAO__factory, PluginRepo, PluginRepoRegistry, PluginRepoRegistry__factory, @@ -31,13 +33,13 @@ describe(`Deployment on network '${productionNetworkName}'`, function () { expect(await pluginRepoRegistry.entries(pluginRepo.address)).to.be.true; }); - it('makes the deployer the repo maintainer', async () => { - const {deployer, pluginRepo} = await loadFixture(fixture); + it('gives the management DAO permissions over the repo', async () => { + const {pluginRepo, managementDaoProxy} = await loadFixture(fixture); expect( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + managementDaoProxy.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID, PERMISSION_MANAGER_FLAGS.NO_CONDITION ) @@ -46,7 +48,7 @@ describe(`Deployment on network '${productionNetworkName}'`, function () { expect( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + managementDaoProxy.address, PLUGIN_REPO_PERMISSIONS.UPGRADE_REPO_PERMISSION_ID, PERMISSION_MANAGER_FLAGS.NO_CONDITION ) @@ -55,7 +57,7 @@ describe(`Deployment on network '${productionNetworkName}'`, function () { expect( await pluginRepo.isGranted( pluginRepo.address, - deployer.address, + managementDaoProxy.address, PLUGIN_REPO_PERMISSIONS.MAINTAINER_PERMISSION_ID, PERMISSION_MANAGER_FLAGS.NO_CONDITION ) @@ -89,16 +91,17 @@ type FixtureResult = { deployer: SignerWithAddress; pluginRepo: PluginRepo; pluginRepoRegistry: PluginRepoRegistry; + managementDaoProxy: DAO; }; async function fixture(): Promise { // Deploy all - const tags = ['CreateRepo', 'NewVersion']; - await deployments.fixture(tags); + const tags = ['CreateRepo', 'NewVersion', 'TransferOwnershipToManagmentDao']; + await deployments.fixture(tags); const [deployer] = await ethers.getSigners(); - // Plugin Repo + // Plugin repo const {pluginRepo, ensDomain} = await findPluginRepo(env); if (pluginRepo === null) { throw `PluginRepo '${ensDomain}' does not exist yet.`; @@ -119,5 +122,11 @@ async function fixture(): Promise { deployer ); - return {deployer, pluginRepo, pluginRepoRegistry}; + // Management DAO proxy + const managementDaoProxy = DAO__factory.connect( + networkDeployments.ManagementDAOProxy.address, + deployer + ); + + return {deployer, pluginRepo, pluginRepoRegistry, managementDaoProxy}; } diff --git a/packages/contracts/test/20_integration-testing/22_setup-processing.ts b/packages/contracts/test/20_integration-testing/22_setup-processing.ts index bb9da6af..7a84dd6c 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -1,11 +1,6 @@ -import {METADATA} from '../../plugin-settings'; -import { - DAOMock, - DAOMock__factory, - MyPluginSetup, - MyPluginSetup__factory, - MyPlugin__factory, -} from '../../typechain'; +import {createDaoProxy} from '../10_unit-testing/11_plugin'; +import {METADATA, VERSION} from '../../plugin-settings'; +import {AdminSetup, AdminSetup__factory, Admin__factory} from '../../typechain'; import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; import {installPLugin, uninstallPLugin} from './test-helpers'; import { @@ -13,6 +8,8 @@ import { getNetworkNameByAlias, } from '@aragon/osx-commons-configs'; import { + DAO_PERMISSIONS, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS, UnsupportedNetworkError, getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; @@ -21,55 +18,67 @@ import { PluginRepo, PluginSetupProcessorStructs, PluginSetupProcessor__factory, + DAO, } from '@aragon/osx-ethers'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {BigNumber} from 'ethers'; import env, {deployments, ethers} from 'hardhat'; const productionNetworkName = getProductionNetworkName(env); describe(`PluginSetup processing on network '${productionNetworkName}'`, function () { it('installs & uninstalls the current build', async () => { - const {deployer, psp, daoMock, pluginSetup, pluginSetupRef} = - await loadFixture(fixture); + const {alice, deployer, psp, dao, pluginSetupRef} = await loadFixture( + fixture + ); - // Allow all authorized calls to happen - await daoMock.setHasPermissionReturnValueMock(true); + // Grant deployer all required permissions + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_UNINSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); // Install the current build. const results = await installPLugin( deployer, psp, - daoMock, + dao, pluginSetupRef, ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareInstallation.inputs ), - [123] + [alice.address] ) ); - const plugin = MyPlugin__factory.connect( + const plugin = Admin__factory.connect( results.preparedEvent.args.plugin, deployer ); - // Check implementation. - expect(await plugin.implementation()).to.be.eq( - await pluginSetup.implementation() - ); - - // Check state. - expect(await plugin.number()).to.eq(123); + // Check that the setup worked + expect(await plugin.isMember(alice.address)).to.be.true; // Uninstall the current build. await uninstallPLugin( deployer, psp, - daoMock, + dao, plugin, pluginSetupRef, ethers.utils.defaultAbiCoder.encode( @@ -87,10 +96,10 @@ type FixtureResult = { deployer: SignerWithAddress; alice: SignerWithAddress; bob: SignerWithAddress; - daoMock: DAOMock; + dao: DAO; psp: PluginSetupProcessor; pluginRepo: PluginRepo; - pluginSetup: MyPluginSetup; + pluginSetup: AdminSetup; pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct; }; @@ -100,7 +109,10 @@ async function fixture(): Promise { await deployments.fixture(tags); const [deployer, alice, bob] = await ethers.getSigners(); - const daoMock = await new DAOMock__factory(deployer).deploy(); + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + const dao = await createDaoProxy(deployer, dummyMetadata); const network = getNetworkNameByAlias(productionNetworkName); if (network === null) { @@ -124,15 +136,15 @@ async function fixture(): Promise { } const release = 1; - const pluginSetup = MyPluginSetup__factory.connect( + const pluginSetup = AdminSetup__factory.connect( (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, deployer ); const pluginSetupRef = { versionTag: { - release: BigNumber.from(1), - build: BigNumber.from(1), + release: VERSION.release, + build: VERSION.build, }, pluginSetupRepo: pluginRepo.address, }; @@ -142,7 +154,7 @@ async function fixture(): Promise { alice, bob, psp, - daoMock, + dao, pluginRepo, pluginSetup, pluginSetupRef, diff --git a/packages/contracts/test/20_integration-testing/test-helpers.ts b/packages/contracts/test/20_integration-testing/test-helpers.ts index a8712834..bdb09c6d 100644 --- a/packages/contracts/test/20_integration-testing/test-helpers.ts +++ b/packages/contracts/test/20_integration-testing/test-helpers.ts @@ -1,4 +1,4 @@ -import {DAOMock, IPlugin} from '../../typechain'; +import {IPlugin} from '../../typechain'; import {hashHelpers} from '../../utils/helpers'; import { DAO_PERMISSIONS, @@ -10,6 +10,7 @@ import { PluginSetupProcessorStructs, PluginSetupProcessor, DAOStructs, + DAO, } from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; @@ -18,7 +19,7 @@ import {ContractTransaction} from 'ethers'; export async function installPLugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct, data: string ): Promise<{ @@ -68,7 +69,7 @@ export async function installPLugin( export async function uninstallPLugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, plugin: IPlugin, pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct, data: string, @@ -123,7 +124,7 @@ export async function uninstallPLugin( export async function updatePlugin( signer: SignerWithAddress, psp: PluginSetupProcessor, - dao: DAOMock, + dao: DAO, plugin: IPlugin, currentHelpers: string[], pluginSetupRefCurrent: PluginSetupProcessorStructs.PluginSetupRefStruct, @@ -183,7 +184,7 @@ export async function updatePlugin( async function checkPermissions( preparedPermissions: DAOStructs.MultiTargetPermissionStruct[], - dao: DAOMock, + dao: DAO, psp: PluginSetupProcessor, signer: SignerWithAddress, applyPermissionId: string From b739c77236a664bfa6c65a9f50258a42321edf98 Mon Sep 17 00:00:00 2001 From: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:06:01 +0100 Subject: [PATCH 04/17] feat: move contracts (#1) * chore: adapt version info * doc: adapt readme title * style: fixed linter errors and warnings * build: add legacy osx contracts as dev dependencies * feat: add legacy contracts to migration file * refactor: remove DAOMock * test: adapted tests * style: fix linter * chore: added changelog * fix: wrong version * doc: improved README * doc: clarified comment * refactor: move constant * doc: imprive TODO comment * chore: maintain build-metadata * fix: typo * refactor: silence max-line-length linter warnings for imports * doc: reference task id in todo note * doc: remove deprecated comment --- README.md | 35 +- packages/contracts/CHANGELOG.md | 17 + .../20_new_version/22_setup_conclude.ts | 9 +- packages/contracts/hardhat.config.ts | 2 +- packages/contracts/package.json | 3 +- packages/contracts/plugin-settings.ts | 9 +- packages/contracts/src/IMultisig.sol | 18 +- packages/contracts/src/Multisig.sol | 35 +- packages/contracts/src/MultisigSetup.sol | 50 +- packages/contracts/src/build-metadata.json | 8 +- packages/contracts/src/mocks/DAOMock.sol | 148 - packages/contracts/src/mocks/Migration.sol | 27 + .../test/10_unit-testing/11_plugin.ts | 151 +- .../test/10_unit-testing/12_plugin-setup.ts | 94 +- .../22_setup-processing.ts | 27 +- .../20_integration-testing/test-helpers.ts | 33 +- packages/contracts/test/multisig-constants.ts | 4 +- .../test/test-utils/protocol-version.ts | 11 + packages/contracts/test/test-utils/storage.ts | 18 + .../test/test-utils/typechain-versions.ts | 9 + .../test/test-utils/uups-upgradeable.ts | 189 ++ packages/contracts/yarn.lock | 2883 ++++++++++++++++- 22 files changed, 3386 insertions(+), 394 deletions(-) create mode 100644 packages/contracts/CHANGELOG.md delete mode 100644 packages/contracts/src/mocks/DAOMock.sol create mode 100644 packages/contracts/src/mocks/Migration.sol create mode 100644 packages/contracts/test/test-utils/protocol-version.ts create mode 100644 packages/contracts/test/test-utils/storage.ts create mode 100644 packages/contracts/test/test-utils/typechain-versions.ts create mode 100644 packages/contracts/test/test-utils/uups-upgradeable.ts diff --git a/README.md b/README.md index ae1c72e1..0fba72a0 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,13 @@ -# Aragon OSX Plugin Template [![Hardhat][hardhat-badge]][hardhat] [![License: AGPL v3][license-badge]][license] +# Multisig Plugin [![Hardhat][hardhat-badge]][hardhat] [![License: AGPL v3][license-badge]][license] [hardhat]: https://hardhat.org/ [hardhat-badge]: https://img.shields.io/badge/Built%20with-Hardhat-FFDB1C.svg [license]: https://opensource.org/licenses/AGPL-v3 [license-badge]: https://img.shields.io/badge/License-AGPL_v3-blue.svg -## Quickstart - -After [creating a new repository from this template](https://github.com/new?template_name=osx-plugin-template-hardhat&template_owner=aragon), cloning, and opening it in your IDE, run - -```sh -yarn install && cd packages/contracts && yarn install && yarn build && yarn typechain -``` - -Meanwhile, create an `.env` file from the `.env.example` file and put in the API keys for the services that you want to use. -You can now develop a plugin by changing the `src/MyPlugin.sol` and `src/MyPluginSetup.sol` files. You can directly import contracts from [Aragon OSx](https://github.com/aragon/osx) as well as OpenZeppelin's [openzeppelin-contracts](https://github.com/OpenZeppelin/openzeppelin-contracts) and [openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable) that are already set up for you. - -```sol -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.17; - -import {IDAO, PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; -import {SafeCastUpgradeable} from '@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol'; - -contract MyPlugin is PluginUUPSUpgradeable { - //... -}; -``` - -The initial `MyPlugin` and `MyPluginSetup` example comes with unit test, integration test, and test helpers in the `package/contracts/test` folder that you can reuse. - -To build and test your contracts, run - -```sh -yarn clean && yarn build && yarn test -``` - ## Project -The root folder of the repo includes three subfolders: +The root folder of the repo includes two subfolders: ```markdown . diff --git a/packages/contracts/CHANGELOG.md b/packages/contracts/CHANGELOG.md new file mode 100644 index 00000000..2f604829 --- /dev/null +++ b/packages/contracts/CHANGELOG.md @@ -0,0 +1,17 @@ +# Multisig Plugin + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to the [Aragon OSx Plugin Versioning Convention](https://devs.aragon.org/docs/osx/how-to-guides/plugin-development/publication/versioning). + +## v1.3 + +### Added + +- Copied files from [aragon/osx commit 1130df](https://github.com/aragon/osx/commit/1130dfce94fd294c4341e91a8f3faccc54cf43b7) + +### Changed + +- Used `ProxyLib` from `osx-commons-contracts` for the minimal proxy deployment in `MultisigSetup`. +- Hard-coded the `bytes32 internal constant EXECUTE_PERMISSION_ID` constant in `MultisigSetup` until it is available in `PermissionLib`. diff --git a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts index 8639c348..e4d3dc39 100644 --- a/packages/contracts/deploy/20_new_version/22_setup_conclude.ts +++ b/packages/contracts/deploy/20_new_version/22_setup_conclude.ts @@ -1,5 +1,5 @@ import {PLUGIN_SETUP_CONTRACT_NAME} from '../../plugin-settings'; -import {AdminSetup__factory, Admin__factory} from '../../typechain'; +import {MultisigSetup__factory, Multisig__factory} from '../../typechain'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; import path from 'path'; @@ -17,9 +17,12 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Get the plugin setup address const setupDeployment = await deployments.get(PLUGIN_SETUP_CONTRACT_NAME); - const setup = AdminSetup__factory.connect(setupDeployment.address, deployer); + const setup = MultisigSetup__factory.connect( + setupDeployment.address, + deployer + ); // Get the plugin implementation address - const implementation = Admin__factory.connect( + const implementation = Multisig__factory.connect( await setup.implementation(), deployer ); diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 3608e253..28e7f77a 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -104,7 +104,7 @@ const config: HardhatUserConfig = { hardhat: { throwOnTransactionFailures: true, throwOnCallFailures: true, - blockGasLimit: BigNumber.from(10).pow(6).mul(30).toNumber(), // 30 million, really high to test some things that are only possible with a higher block gas limit + blockGasLimit: BigNumber.from(10).pow(6).mul(300).toNumber(), // 300 million, really high to test some things that are only possible with a higher block gas limit gasPrice: BigNumber.from(10).pow(9).mul(150).toNumber(), // 150 gwei accounts: getHardhatNetworkAccountsConfig( Object.keys(namedAccounts).length diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 4587efee..06332257 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -22,7 +22,8 @@ "@aragon/osx-commons-configs": "0.1.0", "@aragon/osx-ethers": "1.4.0-alpha.0", "@aragon/osx-commons-sdk": "0.0.1-alpha.5", - "@aragon/osx-artifacts": "1.3.1", + "@aragon/osx-v1.0.0": "npm:@aragon/osx@1.0.1", + "@aragon/osx-v1.3.0": "npm:@aragon/osx@1.3.0", "@ethersproject/abi": "^5.7.0", "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/bignumber": "^5.7.0", diff --git a/packages/contracts/plugin-settings.ts b/packages/contracts/plugin-settings.ts index f19ee5bf..18e3ce5a 100644 --- a/packages/contracts/plugin-settings.ts +++ b/packages/contracts/plugin-settings.ts @@ -2,16 +2,15 @@ import buildMetadata from './src/build-metadata.json'; import releaseMetadata from './src/release-metadata.json'; import {VersionTag} from '@aragon/osx-commons-sdk'; -export const PLUGIN_CONTRACT_NAME = 'MyPlugin'; // This must match the filename `packages/contracts/src/MyPlugin.sol` and the contract name `MyPlugin` within. -export const PLUGIN_SETUP_CONTRACT_NAME = 'MyPluginSetup'; // This must match the filename `packages/contracts/src/MyPluginSetup.sol` and the contract name `MyPluginSetup` within. -export const PLUGIN_REPO_ENS_SUBDOMAIN_NAME = 'my'; // This will result in the ENS domain name 'my.plugin.dao.eth' +export const PLUGIN_CONTRACT_NAME = 'Multisig'; +export const PLUGIN_SETUP_CONTRACT_NAME = 'MultisigSetup'; +export const PLUGIN_REPO_ENS_SUBDOMAIN_NAME = 'multisig'; // 'multisig.plugin.dao.eth' export const VERSION: VersionTag = { release: 1, // Increment this number ONLY if breaking/incompatible changes were made. Updates between releases are NOT possible. - build: 1, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. + build: 3, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. }; -/* DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING */ export const METADATA = { build: buildMetadata, release: releaseMetadata, diff --git a/packages/contracts/src/IMultisig.sol b/packages/contracts/src/IMultisig.sol index ec464d1c..ee143932 100644 --- a/packages/contracts/src/IMultisig.sol +++ b/packages/contracts/src/IMultisig.sol @@ -2,24 +2,27 @@ pragma solidity ^0.8.8; -import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; - /// @title IMultisig /// @author Aragon Association - 2023 -/// @notice An interface for an on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. +/// @notice An interface for an on-chain multisig governance plugin in which a proposal passes +/// if X out of Y approvals are met. /// @custom:security-contact sirt@aragon.org interface IMultisig { - /// @notice Adds new members to the address list. Previously, it checks if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + /// @notice Adds new members to the address list. Previously, it checks if the new address + /// list length would be greater than `type(uint16).max`, the maximal number of approvals. /// @param _members The addresses of the members to be added. function addAddresses(address[] calldata _members) external; - /// @notice Removes existing members from the address list. Previously, it checks if the new address list length is at least as long as the minimum approvals parameter requires. Note that `minApprovals` is must be at least 1 so the address list cannot become empty. + /// @notice Removes existing members from the address list. Previously, it checks if the + /// new address list length is at least as long as the minimum approvals parameter requires. + /// Note that `minApprovals` is must be at least 1 so the address list cannot become empty. /// @param _members The addresses of the members to be removed. function removeAddresses(address[] calldata _members) external; /// @notice Approves and, optionally, executes the proposal. /// @param _proposalId The ID of the proposal. - /// @param _tryExecution If `true`, execution is tried after the approval cast. The call does not revert if execution is not possible. + /// @param _tryExecution If `true`, execution is tried after the approval cast. + /// The call does not revert if execution is not possible. function approve(uint256 _proposalId, bool _tryExecution) external; /// @notice Checks if an account can participate on a proposal vote. This can be because the vote @@ -36,7 +39,8 @@ interface IMultisig { /// @return True if the proposal can be executed, false otherwise. function canExecute(uint256 _proposalId) external view returns (bool); - /// @notice Returns whether the account has approved the proposal. Note, that this does not check if the account is listed. + /// @notice Returns whether the account has approved the proposal. Note, that this does not check + // if the account is listed. /// @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. diff --git a/packages/contracts/src/Multisig.sol b/packages/contracts/src/Multisig.sol index 84bac939..c941cf2e 100644 --- a/packages/contracts/src/Multisig.sol +++ b/packages/contracts/src/Multisig.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.8; +/* solhint-disable max-line-length */ import {SafeCastUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import {IMembership} from "@aragon/osx-commons-contracts/src/plugin/extensions/membership/IMembership.sol"; @@ -12,10 +13,12 @@ import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; import {IMultisig} from "./IMultisig.sol"; +/* solhint-enable max-line-length */ + /// @title Multisig /// @author Aragon Association - 2022-2023 /// @notice The on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. -/// @dev v1.2 (Release 1, Build 2) +/// @dev v1.3 (Release 1, Build 3) /// @custom:security-contact sirt@aragon.org contract Multisig is IMultisig, @@ -32,7 +35,9 @@ contract Multisig is /// @param parameters The proposal-specific approve settings at the time of the proposal creation. /// @param approvers The approves casted by the approvers. /// @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. + /// @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; uint16 approvals; @@ -74,13 +79,15 @@ contract Multisig is keccak256("UPDATE_MULTISIG_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 current plugin settings. MultisigSettings public multisigSettings; /// @notice Keeps track at which block number the multisig settings have been changed the last time. - /// @dev This variable prevents a proposal from being created in the same block in which the multisig settings change. + /// @dev This variable prevents a proposal from being created in the same block in which the multisig + /// settings change. uint64 public lastMultisigSettingsChange; /// @notice Thrown when a sender is not allowed to create a proposal. @@ -99,7 +106,8 @@ contract Multisig is /// @param proposalId The ID of the proposal. error ProposalExecutionForbidden(uint256 proposalId); - /// @notice Thrown if the minimal approvals value is out of bounds (less than 1 or greater than the number of members in the address list). + /// @notice Thrown if the minimal approvals value is out of bounds (less than 1 or greater than the number of + /// members in the address list). /// @param limit The maximal value. /// @param actual The actual value. error MinApprovalsOutOfBounds(uint16 limit, uint16 actual); @@ -166,7 +174,8 @@ contract Multisig is ) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { uint256 newAddresslistLength = addresslistLength() + _members.length; - // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of + // approvals. if (newAddresslistLength > type(uint16).max) { revert AddresslistLengthOutOfBounds({ limit: type(uint16).max, @@ -209,12 +218,16 @@ contract Multisig is /// @notice Creates a new multisig proposal. /// @param _metadata The metadata of the proposal. /// @param _actions The actions that will be executed after 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. + /// @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. /// @param _approveProposal If `true`, the sender will approve the proposal. - /// @param _tryExecution If `true`, execution is tried after the vote cast. The call does not revert if early execution is not possible. + /// @param _tryExecution If `true`, execution is tried after the vote cast. The call does not revert if early + /// execution is not possible. /// @param _startDate The start date of the proposal. /// @param _endDate The end date of the proposal. /// @return proposalId The ID of the proposal. + // solhint-disable-next-line code-complexity function createProposal( bytes calldata _metadata, IDAO.Action[] calldata _actions, @@ -230,7 +243,9 @@ contract Multisig is uint64 snapshotBlock; unchecked { - snapshotBlock = block.number.toUint64() - 1; // The snapshot block must be mined already to protect the transaction against backrunning transactions causing census changes. + // The snapshot block must be mined already to protect the transaction against backrunning transactions + // causing census changes. + snapshotBlock = block.number.toUint64() - 1; } // Revert if the settings have been changed in the same block as this proposal should be created in. @@ -323,7 +338,9 @@ contract Multisig is /// @return approvals The number of approvals casted. /// @return parameters The parameters of the proposal vote. /// @return actions The actions to be executed in the associated DAO after the proposal has passed. - /// @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. + /// @param allowFailureMap A bitmap allowing the proposal to succeed, even if individual actions might revert. + /// If the bit at index `i` is 1, the proposal succeeds even if the `i`th action reverts. + /// A failure map value of 0 requires every action to not revert. function getProposal( uint256 _proposalId ) diff --git a/packages/contracts/src/MultisigSetup.sol b/packages/contracts/src/MultisigSetup.sol index 631165dd..59f36481 100644 --- a/packages/contracts/src/MultisigSetup.sol +++ b/packages/contracts/src/MultisigSetup.sol @@ -2,27 +2,31 @@ pragma solidity ^0.8.8; +/* solhint-disable max-line-length */ import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; import {IPluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/IPluginSetup.sol"; -import {PluginSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/PluginSetup.sol"; +import {PluginUpgradeableSetup} from "@aragon/osx-commons-contracts/src/plugin/setup/PluginUpgradeableSetup.sol"; +import {ProxyLib} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyLib.sol"; import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; -import {DAO} from "../../../core/dao/DAO.sol"; import {Multisig} from "./Multisig.sol"; +/* solhint-enable max-line-length */ + /// @title MultisigSetup /// @author Aragon Association - 2022-2023 /// @notice The setup contract of the `Multisig` plugin. -/// @dev v1.2 (Release 1, Build 2) +/// @dev v1.3 (Release 1, Build 3) /// @custom:security-contact sirt@aragon.org -contract MultisigSetup is PluginSetup { - /// @notice The address of `Multisig` plugin logic contract to be used in creating proxy contracts. - Multisig private immutable multisigBase; +contract MultisigSetup is PluginUpgradeableSetup { + using ProxyLib for address; + + // TODO This permission identifier will be moved into a library in task OS-954. + /// @notice The ID of the permission required to call the `execute` function. + bytes32 internal constant EXECUTE_PERMISSION_ID = keccak256("EXECUTE_PERMISSION"); /// @notice The contract constructor, that deploys the `Multisig` plugin logic contract. - constructor() { - multisigBase = new Multisig(); - } + constructor() PluginUpgradeableSetup(address(new Multisig())) {} /// @inheritdoc IPluginSetup function prepareInstallation( @@ -35,9 +39,8 @@ contract MultisigSetup is PluginSetup { (address[], Multisig.MultisigSettings) ); - // Prepare and Deploy the plugin proxy. - plugin = createERC1967Proxy( - address(multisigBase), + // Deploy and initialize the plugin UUPS proxy. + plugin = IMPLEMENTATION.deployUUPSProxy( abi.encodeCall(Multisig.initialize, (IDAO(_dao), members, multisigSettings)) ); @@ -52,7 +55,7 @@ contract MultisigSetup is PluginSetup { where: plugin, who: _dao, condition: PermissionLib.NO_CONDITION, - permissionId: multisigBase.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + permissionId: Multisig(IMPLEMENTATION).UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() }); permissions[1] = PermissionLib.MultiTargetPermission({ @@ -60,7 +63,7 @@ contract MultisigSetup is PluginSetup { where: plugin, who: _dao, condition: PermissionLib.NO_CONDITION, - permissionId: multisigBase.UPGRADE_PLUGIN_PERMISSION_ID() + permissionId: Multisig(IMPLEMENTATION).UPGRADE_PLUGIN_PERMISSION_ID() }); // Grant `EXECUTE_PERMISSION` of the DAO to the plugin. @@ -69,13 +72,14 @@ contract MultisigSetup is PluginSetup { where: _dao, who: plugin, condition: PermissionLib.NO_CONDITION, - permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() + permissionId: EXECUTE_PERMISSION_ID }); preparedSetupData.permissions = permissions; } /// @inheritdoc IPluginSetup + /// @dev Nothing needs to happen for the update. function prepareUpdate( address _dao, uint16 _currentBuild, @@ -85,7 +89,10 @@ contract MultisigSetup is PluginSetup { pure override returns (bytes memory initData, PreparedSetupData memory preparedSetupData) - {} + // solhint-disable-next-line no-empty-blocks + { + + } /// @inheritdoc IPluginSetup function prepareUninstallation( @@ -101,7 +108,7 @@ contract MultisigSetup is PluginSetup { where: _payload.plugin, who: _dao, condition: PermissionLib.NO_CONDITION, - permissionId: multisigBase.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + permissionId: Multisig(IMPLEMENTATION).UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() }); permissions[1] = PermissionLib.MultiTargetPermission({ @@ -109,7 +116,7 @@ contract MultisigSetup is PluginSetup { where: _payload.plugin, who: _dao, condition: PermissionLib.NO_CONDITION, - permissionId: multisigBase.UPGRADE_PLUGIN_PERMISSION_ID() + permissionId: Multisig(IMPLEMENTATION).UPGRADE_PLUGIN_PERMISSION_ID() }); permissions[2] = PermissionLib.MultiTargetPermission({ @@ -117,12 +124,7 @@ contract MultisigSetup is PluginSetup { where: _dao, who: _payload.plugin, condition: PermissionLib.NO_CONDITION, - permissionId: DAO(payable(_dao)).EXECUTE_PERMISSION_ID() + permissionId: EXECUTE_PERMISSION_ID }); } - - /// @inheritdoc IPluginSetup - function implementation() external view returns (address) { - return address(multisigBase); - } } diff --git a/packages/contracts/src/build-metadata.json b/packages/contracts/src/build-metadata.json index c7b8cbec..21182af0 100644 --- a/packages/contracts/src/build-metadata.json +++ b/packages/contracts/src/build-metadata.json @@ -1,6 +1,6 @@ { "ui": {}, - "change": "- The ability to create a proposal now depends on the membership status of the current instead of the snapshot block.\n- Added a check ensuring that the initial member list cannot overflow.", + "change": "v1.3\n - TODO TODO.", "pluginSetup": { "prepareInstallation": { "description": "The information required for the installation.", @@ -29,7 +29,7 @@ "internalType": "struct Multisig.MultisigSettings", "name": "multisigSettings", "type": "tuple", - "description": "The inital multisig settings." + "description": "The initial multisig settings." } ] }, @@ -37,6 +37,10 @@ "1": { "description": "No input is required for the update.", "inputs": [] + }, + "2": { + "description": "No input is required for the update.", + "inputs": [] } }, "prepareUninstallation": { diff --git a/packages/contracts/src/mocks/DAOMock.sol b/packages/contracts/src/mocks/DAOMock.sol deleted file mode 100644 index 008fcecf..00000000 --- a/packages/contracts/src/mocks/DAOMock.sol +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -pragma solidity ^0.8.8; - -import {IDAO} from "@aragon/osx-commons-contracts/src/dao/IDAO.sol"; -import {IPermissionCondition} from "@aragon/osx-commons-contracts/src/permission/condition/IPermissionCondition.sol"; -import {PermissionLib} from "@aragon/osx-commons-contracts/src/permission/PermissionLib.sol"; - -contract DAOMock is IDAO { - address internal constant NO_CONDITION = address(0); - - event Granted( - bytes32 indexed permissionId, - address indexed here, - address where, - address indexed who, - address condition - ); - - event Revoked( - bytes32 indexed permissionId, - address indexed here, - address where, - address indexed who - ); - - bool public hasPermissionReturnValueMock; - - function setHasPermissionReturnValueMock(bool _hasPermissionReturnValueMock) external { - hasPermissionReturnValueMock = _hasPermissionReturnValueMock; - } - - function hasPermission( - address _where, - address _who, - bytes32 _permissionId, - bytes memory _data - ) external view override returns (bool) { - (_where, _who, _permissionId, _data); - return hasPermissionReturnValueMock; - } - - function applyMultiTargetPermissions( - PermissionLib.MultiTargetPermission[] calldata _items - ) external { - for (uint256 i; i < _items.length; ) { - PermissionLib.MultiTargetPermission memory item = _items[i]; - - if (item.operation == PermissionLib.Operation.Grant) { - grant({_where: item.where, _who: item.who, _permissionId: item.permissionId}); - } else if (item.operation == PermissionLib.Operation.Revoke) { - revoke({_where: item.where, _who: item.who, _permissionId: item.permissionId}); - } else if (item.operation == PermissionLib.Operation.GrantWithCondition) { - grantWithCondition({ - _where: item.where, - _who: item.who, - _permissionId: item.permissionId, - _condition: IPermissionCondition(item.condition) - }); - } - - unchecked { - ++i; - } - } - } - - function grant(address _where, address _who, bytes32 _permissionId) public { - (_where, _who, _permissionId); - - emit Granted({ - permissionId: _permissionId, - here: msg.sender, - where: _where, - who: _who, - condition: NO_CONDITION - }); - } - - function revoke(address _where, address _who, bytes32 _permissionId) public { - (_where, _who, _permissionId); - - emit Revoked({permissionId: _permissionId, here: msg.sender, where: _where, who: _who}); - } - - function grantWithCondition( - address _where, - address _who, - bytes32 _permissionId, - IPermissionCondition _condition - ) public { - emit Granted({ - permissionId: _permissionId, - here: msg.sender, - where: _where, - who: _who, - condition: address(_condition) - }); - } - - function getTrustedForwarder() public pure override returns (address) { - return address(0); - } - - function setTrustedForwarder(address _trustedForwarder) external pure override { - (_trustedForwarder); - } - - function setMetadata(bytes calldata _metadata) external pure override { - (_metadata); - } - - function execute( - bytes32 callId, - Action[] memory _actions, - uint256 allowFailureMap - ) external override returns (bytes[] memory execResults, uint256 failureMap) { - emit Executed(msg.sender, callId, _actions, allowFailureMap, failureMap, execResults); - } - - function deposit( - address _token, - uint256 _amount, - string calldata _reference - ) external payable override { - (_token, _amount, _reference); - } - - function setSignatureValidator(address _signatureValidator) external pure override { - (_signatureValidator); - } - - function isValidSignature( - bytes32 _hash, - bytes memory _signature - ) external pure override returns (bytes4) { - (_hash, _signature); - return 0x0; - } - - function registerStandardCallback( - bytes4 _interfaceId, - bytes4 _callbackSelector, - bytes4 _magicNumber - ) external pure override { - (_interfaceId, _callbackSelector, _magicNumber); - } -} diff --git a/packages/contracts/src/mocks/Migration.sol b/packages/contracts/src/mocks/Migration.sol new file mode 100644 index 00000000..937651a2 --- /dev/null +++ b/packages/contracts/src/mocks/Migration.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.8; + +/** + * @title Migration + * + * @dev This file allows importing contracts to obtain compiler artifacts for testing purposes. + * + * After a contract is imported here and the project is compiled, an associated artifact will be + * generated inside artifacts/@aragon/{version-name}/*, + * and TypeChain typings will be generated inside typechain/osx-version/{version-name}/* + * for type-safe interactions with the contract in our tests. + */ + +/* solhint-disable no-unused-import */ +/* solhint-disable max-line-length */ +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +// Regression Testing +import {Multisig as Multisig_v1_0_0} from "@aragon/osx-v1.0.0/plugins/governance/multisig/Multisig.sol"; +import {Multisig as Multisig_v1_3_0} from "@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig.sol"; + +import {ProxyFactory} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory.sol"; + +/* solhint-enable max-line-length */ +/* solhint-enable no-unused-import */ diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index cf6ead5c..8bbdd861 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -1,38 +1,37 @@ -import {MULTISIG_INTERFACE_ID} from '../../../../../subgraph/src/utils/constants'; +import {createDaoProxy} from '../20_integration-testing/test-helpers'; import { Addresslist__factory, - DAO, - DAO__factory, IERC165Upgradeable__factory, IMembership__factory, IMultisig__factory, IPlugin__factory, IProposal__factory, IProtocolVersion__factory, - Multisig, - Multisig__factory, -} from '../../../../typechain'; -import {Multisig__factory as Multisig_V1_0_0__factory} from '../../../../typechain/@aragon/osx-v1.0.1/plugins/governance/multisig/Multisig.sol'; -import {Multisig__factory as Multisig_V1_3_0__factory} from '../../../../typechain/@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig.sol'; -import {ExecutedEvent} from '../../../../typechain/DAO'; -import {ProposalCreatedEvent} from '../../../../typechain/IProposal'; + ProxyFactory__factory, +} from '../../typechain'; +import {ExecutedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/dao/IDAO'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; import { ApprovedEvent, + ProposalCreatedEvent, ProposalExecutedEvent, -} from '../../../../typechain/Multisig'; -import {deployNewDAO} from '../../../test-utils/dao'; -import {osxContractsVersion} from '../../../test-utils/protocol-version'; -import {deployWithProxy} from '../../../test-utils/proxy'; -import { - getProtocolVersion, - deployAndUpgradeFromToCheck, - deployAndUpgradeSelfCheck, -} from '../../../test-utils/uups-upgradeable'; +} from '../../typechain/src/Multisig'; import { MULTISIG_EVENTS, MULTISIG_INTERFACE, MultisigSettings, -} from './multisig-constants'; +} from '../multisig-constants'; +import { + Multisig_V1_0_0__factory, + Multisig_V1_3_0__factory, + Multisig__factory, + Multisig, +} from '../test-utils/typechain-versions'; +import { + deployAndUpgradeFromToCheck, + deployAndUpgradeSelfCheck, + getProtocolVersion, +} from '../test-utils/uups-upgradeable'; import { getInterfaceId, proposalIdToBytes32, @@ -44,10 +43,11 @@ import { findEventTopicLog, TIME, } from '@aragon/osx-commons-sdk'; +import {DAO, DAO__factory} from '@aragon/osx-ethers'; import {time} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {Contract, ContractFactory} from 'ethers'; +import {BigNumber, Contract, ContractFactory} from 'ethers'; import {ethers} from 'hardhat'; export async function approveWithSigners( @@ -56,7 +56,7 @@ export async function approveWithSigners( signers: SignerWithAddress[], signerIds: number[] ) { - let promises = signerIds.map(i => + const promises = signerIds.map(i => multisigContract.connect(signers[i]).approve(proposalId, false) ); @@ -88,7 +88,7 @@ describe('Multisig', function () { ethers.utils.toUtf8Bytes('0x123456789') ); - dao = await deployNewDAO(signers[0]); + dao = await createDaoProxy(signers[0], dummyMetadata); }); beforeEach(async function () { @@ -100,22 +100,33 @@ describe('Multisig', function () { onlyListed: true, }; - const MultisigFactory = new Multisig__factory(signers[0]); - multisig = await deployWithProxy(MultisigFactory); + const multisigImplementation = await new Multisig__factory( + signers[0] + ).deploy(); + const multisigProxyFactory = await new ProxyFactory__factory( + signers[0] + ).deploy(multisigImplementation.address); + + const tx = await multisigProxyFactory.deployUUPSProxy([]); + const event = await findEvent( + tx, + multisigProxyFactory.interface.getEvent('ProxyCreated').name + ); + multisig = Multisig__factory.connect(event.args.proxy, signers[0]); - dao.grant( + await dao.grant( dao.address, multisig.address, ethers.utils.id('EXECUTE_PERMISSION') ); - dao.grant( + await dao.grant( multisig.address, signers[0].address, ethers.utils.id('UPDATE_MULTISIG_SETTINGS_PERMISSION') ); }); - describe('initialize:', async () => { + describe('initialize', async () => { it('reverts if trying to re-initialize', async () => { await multisig.initialize( dao.address, @@ -177,14 +188,17 @@ describe('Multisig', function () { multisigSettings ) ) - .to.emit(multisig, MULTISIG_EVENTS.MULTISIG_SETTINGS_UPDATED) + .to.emit(multisig, MULTISIG_EVENTS.MultisigSettingsUpdated) .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); }); it('should revert if members list is longer than uint16 max', async () => { - const megaMember = signers[1]; - const members: string[] = new Array(65537).fill(megaMember.address); - await expect(multisig.initialize(dao.address, members, multisigSettings)) + const members = new Array(65536).fill(signers[1].address); + await expect( + multisig.initialize(dao.address, members, multisigSettings, { + gasLimit: BigNumber.from(10).pow(8).toNumber(), + }) + ) .to.revertedWithCustomError(multisig, 'AddresslistLengthOutOfBounds') .withArgs(65535, members.length); }); @@ -244,7 +258,7 @@ describe('Multisig', function () { expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); - expect(toProtocolVersion).to.deep.equal(osxContractsVersion()); + expect(toProtocolVersion).to.deep.equal([1, 4, 0]); // TODO Check this automatically }); it('from v1.3.0', async () => { @@ -272,7 +286,7 @@ describe('Multisig', function () { expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); - expect(toProtocolVersion).to.deep.equal(osxContractsVersion()); + expect(toProtocolVersion).to.deep.equal([1, 4, 0]); // TODO Check this automatically }); }); @@ -324,13 +338,12 @@ describe('Multisig', function () { }); it('supports the `Multisig` interface', async () => { - const iface = getInterfaceId(MULTISIG_INTERFACE); - expect(iface).to.equal(MULTISIG_INTERFACE_ID); // checks that it didn't change - expect(await multisig.supportsInterface(iface)).to.be.true; + const interfaceId = getInterfaceId(MULTISIG_INTERFACE); + expect(await multisig.supportsInterface(interfaceId)).to.be.true; }); }); - describe('updateMultisigSettings:', async () => { + describe('updateMultisigSettings', async () => { beforeEach(async () => { await multisig.initialize( dao.address, @@ -340,7 +353,7 @@ describe('Multisig', function () { }); it('should not allow to set minApprovals larger than the address list length', async () => { - let addresslistLength = (await multisig.addresslistLength()).toNumber(); + const addresslistLength = (await multisig.addresslistLength()).toNumber(); multisigSettings.minApprovals = addresslistLength + 1; @@ -358,12 +371,12 @@ describe('Multisig', function () { it('should emit `MultisigSettingsUpdated` when `updateMutlsigSettings` gets called', async () => { await expect(multisig.updateMultisigSettings(multisigSettings)) - .to.emit(multisig, MULTISIG_EVENTS.MULTISIG_SETTINGS_UPDATED) + .to.emit(multisig, MULTISIG_EVENTS.MultisigSettingsUpdated) .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); }); }); - describe('isListed:', async () => { + describe('isListed', async () => { it('should return false, if a user is not listed', async () => { multisigSettings.minApprovals = 1; await multisig.initialize( @@ -392,7 +405,7 @@ describe('Multisig', function () { }); }); - describe('addAddresses:', async () => { + describe('addAddresses', async () => { it('should add new members to the address list and emit the `MembersAdded` event', async () => { multisigSettings.minApprovals = 1; await multisig.initialize( @@ -406,7 +419,7 @@ describe('Multisig', function () { // add a new member await expect(multisig.addAddresses([signers[1].address])) - .to.emit(multisig, IMEMBERSHIP_EVENTS.MEMBERS_ADDED) + .to.emit(multisig, IMEMBERSHIP_EVENTS.MembersAdded) .withArgs([signers[1].address]); expect(await multisig.isListed(signers[0].address)).to.equal(true); @@ -414,7 +427,7 @@ describe('Multisig', function () { }); }); - describe('removeAddresses:', async () => { + describe('removeAddresses', async () => { it('should remove users from the address list and emit the `MembersRemoved` event', async () => { multisigSettings.minApprovals = 1; await multisig.initialize( @@ -428,7 +441,7 @@ describe('Multisig', function () { // remove an existing member await expect(multisig.removeAddresses([signers[1].address])) - .to.emit(multisig, IMEMBERSHIP_EVENTS.MEMBERS_REMOVED) + .to.emit(multisig, IMEMBERSHIP_EVENTS.MembersRemoved) .withArgs([signers[1].address]); expect(await multisig.isListed(signers[0].address)).to.equal(true); @@ -471,7 +484,7 @@ describe('Multisig', function () { }); }); - describe('createProposal:', async () => { + describe('createProposal', async () => { beforeEach(async () => { multisigSettings.minApprovals = 1; }); @@ -568,7 +581,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[0].address, @@ -628,7 +641,7 @@ describe('Multisig', function () { await ethers.provider.send('evm_setAutomine', [true]); }); - context('`onlyListed` is set to `false`:', async () => { + context('`onlyListed` is set to `false`', async () => { beforeEach(async () => { multisigSettings.onlyListed = false; @@ -653,7 +666,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[1].address, @@ -666,7 +679,7 @@ describe('Multisig', function () { }); }); - context('`onlyListed` is set to `true`:', async () => { + context('`onlyListed` is set to `true`', async () => { beforeEach(async () => { multisigSettings.onlyListed = true; @@ -767,7 +780,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[0].address, @@ -812,7 +825,7 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_CREATED) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( id, signers[0].address, @@ -822,7 +835,7 @@ describe('Multisig', function () { [], allowFailureMap ) - .to.emit(multisig, MULTISIG_EVENTS.APPROVED) + .to.emit(multisig, MULTISIG_EVENTS.Approved) .withArgs(id, signers[0].address); const block = await ethers.provider.getBlock('latest'); @@ -940,7 +953,7 @@ describe('Multisig', function () { ); }); - describe('canApprove:', async () => { + describe('canApprove', async () => { it('returns `false` if the proposal is already executed', async () => { await approveWithSigners(multisig, id, signers, [0, 1]); @@ -1013,7 +1026,7 @@ describe('Multisig', function () { }); }); - describe('approve:', async () => { + describe('approve', async () => { it('reverts when approving multiple times', async () => { await multisig.approve(id, true); @@ -1088,7 +1101,7 @@ describe('Multisig', function () { }); }); - describe('canExecute:', async () => { + describe('canExecute', async () => { it('returns `false` if the proposal has not reached the minimum approval yet', async () => { const proposal = await multisig.getProposal(id); expect(proposal.approvals).to.be.lt(proposal.parameters.minApprovals); @@ -1157,7 +1170,7 @@ describe('Multisig', function () { }); }); - describe('execute:', async () => { + describe('execute', async () => { it('reverts if the minimum approval is not met', async () => { await expect(multisig.execute(id)) .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') @@ -1205,10 +1218,10 @@ describe('Multisig', function () { findEventTopicLog( tx, DAO__factory.createInterface(), - IDAO_EVENTS.EXECUTED + IDAO_EVENTS.Executed ) ).to.rejectedWith( - `Event "${IDAO_EVENTS.EXECUTED}" could not be found in transaction ${tx.hash}.` + `Event "${IDAO_EVENTS.Executed}" could not be found in transaction ${tx.hash}.` ); expect(await multisig.canExecute(id)).to.equal(false); @@ -1219,10 +1232,10 @@ describe('Multisig', function () { findEventTopicLog( tx, DAO__factory.createInterface(), - IDAO_EVENTS.EXECUTED + IDAO_EVENTS.Executed ) ).to.rejectedWith( - `Event "${IDAO_EVENTS.EXECUTED}" could not be found in transaction ${tx.hash}.` + `Event "${IDAO_EVENTS.Executed}" could not be found in transaction ${tx.hash}.` ); // `tryEarlyExecution` is turned on and the vote is decided @@ -1231,7 +1244,7 @@ describe('Multisig', function () { const event = await findEventTopicLog( tx, DAO__factory.createInterface(), - IDAO_EVENTS.EXECUTED + IDAO_EVENTS.Executed ); expect(event.args.actor).to.equal(multisig.address); @@ -1250,7 +1263,7 @@ describe('Multisig', function () { { const event = await findEvent( tx, - IPROPOSAL_EVENTS.PROPOSAL_EXECUTED + IPROPOSAL_EVENTS.ProposalExecuted ); expect(event.args.proposalId).to.equal(id); } @@ -1265,18 +1278,18 @@ describe('Multisig', function () { await approveWithSigners(multisig, id, signers, [0, 1, 2]); await expect(multisig.connect(signers[3]).execute(id)) - .to.emit(dao, IDAO_EVENTS.EXECUTED) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_EXECUTED) - .to.not.emit(multisig, MULTISIG_EVENTS.APPROVED); + .to.emit(dao, IDAO_EVENTS.Executed) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalExecuted) + .to.not.emit(multisig, MULTISIG_EVENTS.Approved); }); it('emits the `Approved`, `ProposalExecuted`, and `Executed` events if execute is called inside the `approve` method', async () => { await approveWithSigners(multisig, id, signers, [0, 1]); await expect(multisig.connect(signers[2]).approve(id, true)) - .to.emit(dao, IDAO_EVENTS.EXECUTED) - .to.emit(multisig, IPROPOSAL_EVENTS.PROPOSAL_EXECUTED) - .to.emit(multisig, MULTISIG_EVENTS.APPROVED); + .to.emit(dao, IDAO_EVENTS.Executed) + .to.emit(multisig, IPROPOSAL_EVENTS.ProposalExecuted) + .to.emit(multisig, MULTISIG_EVENTS.Approved); }); it("reverts if the proposal hasn't started yet", async () => { diff --git a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts index fc5dbfed..62d87bee 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -1,30 +1,18 @@ -import metadata from '../../../../src/plugins/governance/multisig/build-metadata.json'; +import {createDaoProxy} from '../20_integration-testing/test-helpers'; +import metadata from '../../src/build-metadata.json'; import { - DAO, - InterfaceBasedRegistryMock, - InterfaceBasedRegistryMock__factory, - IPluginRepo__factory, - Multisig, MultisigSetup, MultisigSetup__factory, - Multisig__factory, - PluginRepo, - PluginRepo__factory, - PluginSetupProcessor, - PluginSetupProcessor__factory, -} from '../../../../typechain'; -import { - InstallationPreparedEvent, - UpdatePreparedEvent, -} from '../../../../typechain/PluginSetupProcessor'; -import {hashHelpers} from '../../../../utils/psp'; -import {deployNewDAO} from '../../../test-utils/dao'; -import {deployWithProxy} from '../../../test-utils/proxy'; + ProxyFactory__factory, +} from '../../typechain'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; +import {hashHelpers} from '../../utils/helpers'; import { MULTISIG_INTERFACE, MultisigSettings, UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, -} from './multisig-constants'; +} from '../multisig-constants'; +import {Multisig__factory, Multisig} from '../test-utils/typechain-versions'; import { getInterfaceId, findEvent, @@ -33,6 +21,17 @@ import { PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; +import { + DAO, + IPluginRepo__factory, + InterfaceBasedRegistryMock, + InterfaceBasedRegistryMock__factory, + PluginRepo, + PluginRepo__factory, + PluginSetupProcessor, + PluginSetupProcessorEvents, + PluginSetupProcessor__factory, +} from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ethers} from 'hardhat'; @@ -53,7 +52,7 @@ describe('MultisigSetup', function () { before(async () => { signers = await ethers.getSigners(); - targetDao = await deployNewDAO(signers[0]); + targetDao = await createDaoProxy(signers[0], EMPTY_DATA); defaultMultisigSettings = { onlyListed: true, @@ -335,7 +334,6 @@ describe('MultisigSetup', function () { }); }); - // TODO: Improve checks by using smock with the proxy (We don't know how yet) describe('Updates', async () => { let psp: PluginSetupProcessor; let setup1: MultisigSetup; @@ -348,11 +346,22 @@ describe('MultisigSetup', function () { before(async () => { [owner] = await ethers.getSigners(); - managingDAO = await deployNewDAO(owner); + managingDAO = await createDaoProxy(owner, EMPTY_DATA); // Create the PluginRepo - const pluginRepoFactory = new PluginRepo__factory(owner); - pluginRepo = await deployWithProxy(pluginRepoFactory); + const pluginRepoImplementation = await new PluginRepo__factory( + signers[0] + ).deploy(); + const pluginRepoProxyFactory = await new ProxyFactory__factory( + signers[0] + ).deploy(pluginRepoImplementation.address); + const tx = await pluginRepoProxyFactory.deployUUPSProxy([]); + const event = await findEvent( + tx, + pluginRepoProxyFactory.interface.getEvent('ProxyCreated').name + ); + pluginRepo = PluginRepo__factory.connect(event.args.proxy, signers[0]); + await pluginRepo.initialize(owner.address); // Create the PluginRepoRegistry @@ -360,7 +369,7 @@ describe('MultisigSetup', function () { owner ); pluginRepoRegistry = await pluginRepoRegistryFactory.deploy(); - pluginRepoRegistry.initialize( + await pluginRepoRegistry.initialize( managingDAO.address, getInterfaceId(IPluginRepo__factory.createInterface()) ); @@ -398,7 +407,7 @@ describe('MultisigSetup', function () { let helpers: string[]; before(async () => { - dao = await deployNewDAO(owner); + dao = await createDaoProxy(owner, EMPTY_DATA); // grant the owner full permission for plugins await dao.applySingleTargetPermissions(psp.address, [ { @@ -439,10 +448,11 @@ describe('MultisigSetup', function () { [[owner.address], [true, 1]] ), }); - const preparedEvent = await findEvent( - tx, - 'InstallationPrepared' - ); + const preparedEvent = + await findEvent( + tx, + 'InstallationPrepared' + ); await expect( psp.applyInstallation(dao.address, { @@ -493,10 +503,11 @@ describe('MultisigSetup', function () { data: '0x00', }, }); - const preparedEvent = await findEvent( - tx, - 'UpdatePrepared' - ); + const preparedEvent = + await findEvent( + tx, + 'UpdatePrepared' + ); await expect( psp.applyUpdate(dao.address, { @@ -524,7 +535,7 @@ describe('MultisigSetup', function () { describe('Release 1 Build 2', () => { before(async () => { - dao = await deployNewDAO(owner); + dao = await createDaoProxy(owner, EMPTY_DATA); // grant the owner full permission for plugins await dao.applySingleTargetPermissions(psp.address, [ { @@ -565,10 +576,11 @@ describe('MultisigSetup', function () { [[owner.address], [true, 1]] ), }); - const preparedEvent = await findEvent( - tx, - 'InstallationPrepared' - ); + const preparedEvent = + await findEvent( + tx, + 'InstallationPrepared' + ); await expect( psp.applyInstallation(dao.address, { @@ -587,7 +599,7 @@ describe('MultisigSetup', function () { }) ).to.emit(psp, 'InstallationApplied'); - let plugin = Multisig__factory.connect( + const plugin = Multisig__factory.connect( preparedEvent.args.plugin, owner ); diff --git a/packages/contracts/test/20_integration-testing/22_setup-processing.ts b/packages/contracts/test/20_integration-testing/22_setup-processing.ts index 7a84dd6c..87eac0ed 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -1,8 +1,8 @@ -import {createDaoProxy} from '../10_unit-testing/11_plugin'; import {METADATA, VERSION} from '../../plugin-settings'; -import {AdminSetup, AdminSetup__factory, Admin__factory} from '../../typechain'; +import {MultisigSetup, Multisig__factory} from '../../typechain'; import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; -import {installPLugin, uninstallPLugin} from './test-helpers'; +import {MultisigSettings} from '../multisig-constants'; +import {createDaoProxy, installPLugin, uninstallPLugin} from './test-helpers'; import { getLatestNetworkDeployment, getNetworkNameByAlias, @@ -19,6 +19,7 @@ import { PluginSetupProcessorStructs, PluginSetupProcessor__factory, DAO, + MultisigSetup__factory, } from '@aragon/osx-ethers'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; @@ -29,7 +30,7 @@ const productionNetworkName = getProductionNetworkName(env); describe(`PluginSetup processing on network '${productionNetworkName}'`, function () { it('installs & uninstalls the current build', async () => { - const {alice, deployer, psp, dao, pluginSetupRef} = await loadFixture( + const {alice, bob, deployer, psp, dao, pluginSetupRef} = await loadFixture( fixture ); @@ -53,6 +54,13 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); // Install the current build. + + const initialMembers = [alice.address, bob.address]; + const multisigSettings: MultisigSettings = { + onlyListed: true, + minApprovals: 2, + }; + const results = await installPLugin( deployer, psp, @@ -62,11 +70,11 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareInstallation.inputs ), - [alice.address] + [initialMembers, multisigSettings] ) ); - const plugin = Admin__factory.connect( + const plugin = Multisig__factory.connect( results.preparedEvent.args.plugin, deployer ); @@ -90,6 +98,9 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio [] ); }); + it.skip('updates to the current build', async () => { + expect(false).to.be.true; + }); }); type FixtureResult = { @@ -99,7 +110,7 @@ type FixtureResult = { dao: DAO; psp: PluginSetupProcessor; pluginRepo: PluginRepo; - pluginSetup: AdminSetup; + pluginSetup: MultisigSetup; pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct; }; @@ -136,7 +147,7 @@ async function fixture(): Promise { } const release = 1; - const pluginSetup = AdminSetup__factory.connect( + const pluginSetup = MultisigSetup__factory.connect( (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, deployer ); diff --git a/packages/contracts/test/20_integration-testing/test-helpers.ts b/packages/contracts/test/20_integration-testing/test-helpers.ts index bdb09c6d..a9bac891 100644 --- a/packages/contracts/test/20_integration-testing/test-helpers.ts +++ b/packages/contracts/test/20_integration-testing/test-helpers.ts @@ -1,4 +1,5 @@ -import {IPlugin} from '../../typechain'; +import {IPlugin, ProxyFactory__factory} from '../../typechain'; +import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; import {hashHelpers} from '../../utils/helpers'; import { DAO_PERMISSIONS, @@ -11,10 +12,12 @@ import { PluginSetupProcessor, DAOStructs, DAO, + DAO__factory, } from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ContractTransaction} from 'ethers'; +import {ethers} from 'hardhat'; export async function installPLugin( signer: SignerWithAddress, @@ -213,3 +216,31 @@ async function checkPermissions( throw `The used signer does not have the permission with ID '${applyPermissionId}' granted and thus cannot apply the setup`; } } + +// TODO Move into OSX commons as part of Task OS-928. +export async function createDaoProxy( + deployer: SignerWithAddress, + dummyMetadata: string +): Promise { + const daoImplementation = await new DAO__factory(deployer).deploy(); + const daoProxyFactory = await new ProxyFactory__factory(deployer).deploy( + daoImplementation.address + ); + + const daoInitData = daoImplementation.interface.encodeFunctionData( + 'initialize', + [ + dummyMetadata, + deployer.address, + ethers.constants.AddressZero, + dummyMetadata, + ] + ); + const tx = await daoProxyFactory.deployUUPSProxy(daoInitData); + const event = await findEvent( + tx, + daoProxyFactory.interface.getEvent('ProxyCreated').name + ); + const dao = DAO__factory.connect(event.args.proxy, deployer); + return dao; +} diff --git a/packages/contracts/test/multisig-constants.ts b/packages/contracts/test/multisig-constants.ts index 7175b09c..b908bdcf 100644 --- a/packages/contracts/test/multisig-constants.ts +++ b/packages/contracts/test/multisig-constants.ts @@ -1,8 +1,8 @@ import {ethers} from 'hardhat'; export const MULTISIG_EVENTS = { - MULTISIG_SETTINGS_UPDATED: 'MultisigSettingsUpdated', - APPROVED: 'Approved', + MultisigSettingsUpdated: 'MultisigSettingsUpdated', + Approved: 'Approved', }; export const MULTISIG_INTERFACE = new ethers.utils.Interface([ diff --git a/packages/contracts/test/test-utils/protocol-version.ts b/packages/contracts/test/test-utils/protocol-version.ts new file mode 100644 index 00000000..5ddbe0c3 --- /dev/null +++ b/packages/contracts/test/test-utils/protocol-version.ts @@ -0,0 +1,11 @@ +import {version} from '../../package.json'; + +/** + * Returns the NPM version number from the `osx` package.json file + */ +// TODO This will be refactored as part of Task OS-1093. +export function osxContractsVersion(): [number, number, number] { + const trimmedVersion = version.split('-')[0]; + const semver = trimmedVersion.split('.'); + return [Number(semver[0]), Number(semver[1]), Number(semver[2])]; +} diff --git a/packages/contracts/test/test-utils/storage.ts b/packages/contracts/test/test-utils/storage.ts new file mode 100644 index 00000000..543c0fa1 --- /dev/null +++ b/packages/contracts/test/test-utils/storage.ts @@ -0,0 +1,18 @@ +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 ethers.provider + .getStorageAt(contractAddress, location) + .then(encoded => defaultAbiCoder.decode(types, encoded)[0]); +} diff --git a/packages/contracts/test/test-utils/typechain-versions.ts b/packages/contracts/test/test-utils/typechain-versions.ts new file mode 100644 index 00000000..e9f430bd --- /dev/null +++ b/packages/contracts/test/test-utils/typechain-versions.ts @@ -0,0 +1,9 @@ +/// Typechain will sometimes by default link to the wrong version of the contract, when we have name collisions +/// The version specified in src is the factory and contract without the version number. +/// Import as needed in the test files, and use the correct version of the contract. + +export {Multisig__factory as Multisig_V1_0_0__factory} from '../../typechain/factories/@aragon/osx-v1.0.0/plugins/governance/multisig/Multisig__factory'; +export {Multisig__factory as Multisig_V1_3_0__factory} from '../../typechain/factories/@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig__factory'; +export {Multisig__factory} from '../../typechain/factories/src/Multisig__factory'; +export {Multisig} from '../../typechain/src/Multisig'; +export {IMultisig} from '../../typechain/src/IMultisig'; diff --git a/packages/contracts/test/test-utils/uups-upgradeable.ts b/packages/contracts/test/test-utils/uups-upgradeable.ts new file mode 100644 index 00000000..a3b43293 --- /dev/null +++ b/packages/contracts/test/test-utils/uups-upgradeable.ts @@ -0,0 +1,189 @@ +import {readStorage, ERC1967_IMPLEMENTATION_SLOT} from './storage'; +import {DAO, PluginRepo} from '@aragon/osx-ethers'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {expect} from 'chai'; +import {Contract, ContractFactory, errors} from 'ethers'; +import {upgrades} from 'hardhat'; + +// The protocol version number of contracts not having a `getProtocolVersion()` function because they don't inherit from `ProtocolVersion.sol` yet. +export const IMPLICIT_INITIAL_PROTOCOL_VERSION: [number, number, number] = [ + 1, 0, 0, +]; + +// Deploys a proxy and a new implementation from the same factory and checks that the upgrade works. +export async function deployAndUpgradeSelfCheck( + deployer: SignerWithAddress, + upgrader: SignerWithAddress, + initArgs: any, + initializerName: string, + factory: ContractFactory, + upgradePermissionId: string, + managingContract?: DAO | PluginRepo | undefined +) { + // Deploy proxy and implementation + const proxy = await upgrades.deployProxy( + factory.connect(deployer), + Object.values(initArgs), + { + kind: 'uups', + initializer: initializerName, + unsafeAllow: ['constructor'], + constructorArgs: [], + } + ); + + // Grant the upgrade permission + const grantArgs: [string, string, string] = [ + proxy.address, + upgrader.address, + upgradePermissionId, + ]; + + // Check if the contract is a permission manager itself + if (managingContract === undefined) { + await expect( + upgrades.upgradeProxy(proxy.address, factory.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'Unauthorized') + .withArgs(...grantArgs); + + await proxy.connect(deployer).grant(...grantArgs); + } + // Or if the permission manager is located in a different contract + else { + await expect( + upgrades.upgradeProxy(proxy.address, factory.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'DaoUnauthorized') + .withArgs(managingContract.address, ...grantArgs); + + await managingContract.connect(deployer).grant(...grantArgs); + } + + // Deploy a new implementation (the same contract at a different address) + const toImplementation = (await factory.deploy()).address; + + // Confirm that the two implementations are different + const fromImplementation = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + expect(toImplementation).to.not.equal(fromImplementation); + + // Upgrade from the old to the new implementation + await proxy.connect(upgrader).upgradeTo(toImplementation); + + // Confirm that the proxy points to the new implementation + const implementationAfterUpgrade = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + expect(implementationAfterUpgrade).to.equal(toImplementation); +} + +// Deploys a proxy and a new implementation via two different factories and checks that the upgrade works. +export async function deployAndUpgradeFromToCheck( + deployer: SignerWithAddress, + upgrader: SignerWithAddress, + initArgs: any, + initializerName: string, + from: ContractFactory, + to: ContractFactory, + upgradePermissionId: string, + managingDao?: DAO | PluginRepo +): Promise<{ + proxy: Contract; + fromImplementation: string; + toImplementation: string; +}> { + // Deploy proxy and implementation + const proxy = await upgrades.deployProxy( + from.connect(deployer), + Object.values(initArgs), + { + kind: 'uups', + initializer: initializerName, + unsafeAllow: ['constructor'], + constructorArgs: [], + } + ); + + const fromImplementation = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + + // Grant the upgrade permission + const grantArgs: [string, string, string] = [ + proxy.address, + upgrader.address, + upgradePermissionId, + ]; + + if (managingDao === undefined) { + await expect( + upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'Unauthorized') + .withArgs(...grantArgs); + + await proxy.connect(deployer).grant(...grantArgs); + } else { + await expect( + upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }) + ) + .to.be.revertedWithCustomError(proxy, 'DaoUnauthorized') + .withArgs(managingDao.address, ...grantArgs); + + await managingDao.connect(deployer).grant(...grantArgs); + } + + // Upgrade the proxy to a new implementation from a different factory + await upgrades.upgradeProxy(proxy.address, to.connect(upgrader), { + unsafeAllow: ['constructor'], + constructorArgs: [], + }); + + const toImplementation = await readStorage( + proxy.address, + ERC1967_IMPLEMENTATION_SLOT, + ['address'] + ); + return {proxy, fromImplementation, toImplementation}; +} + +export async function getProtocolVersion( + contract: Contract +): Promise<[number, number, number]> { + let protocolVersion: [number, number, number]; + try { + contract.interface.getFunction('protocolVersion'); + protocolVersion = await contract.protocolVersion(); + } catch (error: unknown) { + if ( + error instanceof Error && + 'code' in error && + error.code === errors.INVALID_ARGUMENT + ) { + protocolVersion = IMPLICIT_INITIAL_PROTOCOL_VERSION; + } else { + throw error; + } + } + return protocolVersion; +} diff --git a/packages/contracts/yarn.lock b/packages/contracts/yarn.lock index 43944771..1a23906f 100644 --- a/packages/contracts/yarn.lock +++ b/packages/contracts/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@aragon/osx-artifacts@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@aragon/osx-artifacts/-/osx-artifacts-1.3.1.tgz#68fa04844086a92d74351df2e9392ade3c8696dc" - integrity sha512-u6IFP8fQZIS65Ks5Sl1DKlw8Qp9s5I7DSn9n/odQohWnN65A17HwHaCPTEcXl2AL3r71rFawldQ8i5/2yU3UGA== - "@aragon/osx-commons-configs@0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.1.0.tgz#21bbc5a964eb144e30033a44cc352d35c62982f9" @@ -63,6 +58,20 @@ dependencies: ethers "^5.6.2" +"@aragon/osx-v1.0.0@npm:@aragon/osx@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@aragon/osx/-/osx-1.0.1.tgz#b758ba87db93a46a8ddabfaefc99ac8e44c46c78" + integrity sha512-TiP5/1AGv/hth+V8PoDVFlwMzmLazYxzp//jiepAZ0WJkx9EnQNYafo+M7+pjAqRPG005liQjmFZNiK6ARLULg== + +"@aragon/osx-v1.3.0@npm:@aragon/osx@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@aragon/osx/-/osx-1.3.0.tgz#eee59963546016bb3b41b7c7a9b7c41d33b37de2" + integrity sha512-ziLmnhWEoFS/uthxAYfI9tSylesMLTDe69XggKP9LK/tIOKAhyYjfAJ2mbhWZcH558c9o0gzAEErkDhqh/wdog== + dependencies: + "@ensdomains/ens-contracts" "0.0.11" + "@openzeppelin/contracts" "4.8.1" + "@openzeppelin/contracts-upgradeable" "4.8.1" + "@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" @@ -106,6 +115,13 @@ dependencies: tslib "^2.3.1" +"@babel/runtime@^7.4.4": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + "@chainsafe/as-sha256@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz#3639df0e1435cab03f4d9870cc3ac079e57a6fc9" @@ -149,11 +165,110 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@ensdomains/address-encoder@^0.1.7": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@ensdomains/address-encoder/-/address-encoder-0.1.9.tgz#f948c485443d9ef7ed2c0c4790e931c33334d02d" + integrity sha512-E2d2gP4uxJQnDu2Kfg1tHNspefzbLT8Tyjrm5sEuim32UkU2sm5xL4VXtgc2X33fmPEw9+jUMpGs4veMbf+PYg== + dependencies: + bech32 "^1.1.3" + blakejs "^1.1.0" + bn.js "^4.11.8" + bs58 "^4.0.1" + crypto-addr-codec "^0.1.7" + nano-base32 "^1.0.1" + ripemd160 "^2.0.2" + +"@ensdomains/buffer@^0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@ensdomains/buffer/-/buffer-0.0.13.tgz#b9f60defb78fc5f2bee30faca17e63dfbef19253" + integrity sha512-8aA+U/e4S54ebPwcge1HHvvpgXLKxPd6EOSegMlrTvBnKB8RwB3OpNyflaikD6KqzIwDaBaH8bvnTrMcfpV7oQ== + dependencies: + "@nomiclabs/hardhat-truffle5" "^2.0.0" + +"@ensdomains/ens-contracts@0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@ensdomains/ens-contracts/-/ens-contracts-0.0.11.tgz#a1cd87af8c454b694acba5be1a44c1b20656a9be" + integrity sha512-b74OlFcds9eyHy26uE2fGcM+ZCSFtPeRGVbUYWq3NRlf+9t8TIgPwF3rCNwpAFQG0B/AHb4C4hYX2BBJYR1zPg== + dependencies: + "@ensdomains/buffer" "^0.0.13" + "@ensdomains/solsha1" "0.0.3" + "@openzeppelin/contracts" "^4.1.0" + dns-packet "^5.3.0" + +"@ensdomains/ens@0.4.5": + version "0.4.5" + resolved "https://registry.yarnpkg.com/@ensdomains/ens/-/ens-0.4.5.tgz#e0aebc005afdc066447c6e22feb4eda89a5edbfc" + integrity sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw== + dependencies: + bluebird "^3.5.2" + eth-ens-namehash "^2.0.8" + solc "^0.4.20" + testrpc "0.0.1" + web3-utils "^1.0.0-beta.31" + +"@ensdomains/ensjs@^2.0.1": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@ensdomains/ensjs/-/ensjs-2.1.0.tgz#0a7296c1f3d735ef019320d863a7846a0760c460" + integrity sha512-GRbGPT8Z/OJMDuxs75U/jUNEC0tbL0aj7/L/QQznGYKm/tiasp+ndLOaoULy9kKJFC0TBByqfFliEHDgoLhyog== + dependencies: + "@babel/runtime" "^7.4.4" + "@ensdomains/address-encoder" "^0.1.7" + "@ensdomains/ens" "0.4.5" + "@ensdomains/resolver" "0.2.4" + content-hash "^2.5.2" + eth-ens-namehash "^2.0.8" + ethers "^5.0.13" + js-sha3 "^0.8.0" + +"@ensdomains/resolver@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@ensdomains/resolver/-/resolver-0.2.4.tgz#c10fe28bf5efbf49bff4666d909aed0265efbc89" + integrity sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA== + +"@ensdomains/solsha1@0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@ensdomains/solsha1/-/solsha1-0.0.3.tgz#fd479da9d40aadb59ff4fb4ec50632e7d2275a83" + integrity sha512-uhuG5LzRt/UJC0Ux83cE2rCKwSleRePoYdQVcqPN1wyf3/ekMzT/KZUF9+v7/AG5w9jlMLCQkUM50vfjr0Yu9Q== + dependencies: + hash-test-vectors "^1.3.2" + +"@ethereumjs/common@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.5.0.tgz#ec61551b31bef7a69d1dc634d8932468866a4268" + integrity sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg== + dependencies: + crc-32 "^1.2.0" + ethereumjs-util "^7.1.1" + +"@ethereumjs/common@2.6.5", "@ethereumjs/common@^2.5.0", "@ethereumjs/common@^2.6.4": + version "2.6.5" + resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.5.tgz#0a75a22a046272579d91919cb12d84f2756e8d30" + integrity sha512-lRyVQOeCDaIVtgfbowla32pzeDv2Obr8oR8Put5RdUBNRGr1VGPGQNGP6elWIpgK3YdpzqTOh4GyUGOureVeeA== + dependencies: + crc-32 "^1.2.0" + ethereumjs-util "^7.1.5" + "@ethereumjs/rlp@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41" integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw== +"@ethereumjs/tx@3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.3.2.tgz#348d4624bf248aaab6c44fec2ae67265efe3db00" + integrity sha512-6AaJhwg4ucmwTvw/1qLaZUX5miWrwZ4nLOUsKyb/HtzS3BMw/CasKhdi1ims9mBKeK9sOJCH4qGKOBGyJCeeog== + dependencies: + "@ethereumjs/common" "^2.5.0" + ethereumjs-util "^7.1.2" + +"@ethereumjs/tx@3.5.2": + version "3.5.2" + resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.5.2.tgz#197b9b6299582ad84f9527ca961466fce2296c1c" + integrity sha512-gQDNJWKrSDGu2w7w0PzVXVBNMzb7wwdDOmOqczmhNjqFxFuIbhVJDwiGEnxFNC2/b8ifcZzY7MLcluizohRzNw== + dependencies: + "@ethereumjs/common" "^2.6.4" + ethereumjs-util "^7.1.5" + "@ethereumjs/util@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.1.0.tgz#299df97fb6b034e0577ce9f94c7d9d1004409ed4" @@ -438,7 +553,7 @@ "@ethersproject/constants" "^5.7.0" "@ethersproject/logger" "^5.7.0" -"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": +"@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.6.2", "@ethersproject/transactions@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== @@ -555,6 +670,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + "@metamask/eth-sig-util@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088" @@ -858,12 +978,49 @@ table "^6.8.0" undici "^5.14.0" +"@nomiclabs/hardhat-truffle5@^2.0.0": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-truffle5/-/hardhat-truffle5-2.0.7.tgz#7519eadd2c6c460c2addc3d4d6efda7a8883361e" + integrity sha512-Pw8451IUZp1bTp0QqCHCYfCHs66sCnyxPcaorapu9mfOV9xnZsVaFdtutnhNEiXdiZwbed7LFKpRsde4BjFwig== + dependencies: + "@nomiclabs/truffle-contract" "^4.2.23" + "@types/chai" "^4.2.0" + chai "^4.2.0" + ethereumjs-util "^7.1.4" + fs-extra "^7.0.1" + +"@nomiclabs/truffle-contract@^4.2.23": + version "4.5.10" + resolved "https://registry.yarnpkg.com/@nomiclabs/truffle-contract/-/truffle-contract-4.5.10.tgz#52adcca1068647e1c2b44bf0e6a89fc4ad7f9213" + integrity sha512-nF/6InFV+0hUvutyFgsdOMCoYlr//2fJbRER4itxYtQtc4/O1biTwZIKRu+5l2J5Sq6LU2WX7vZHtDgQdhWxIQ== + dependencies: + "@ensdomains/ensjs" "^2.0.1" + "@truffle/blockchain-utils" "^0.1.3" + "@truffle/contract-schema" "^3.4.7" + "@truffle/debug-utils" "^6.0.22" + "@truffle/error" "^0.1.0" + "@truffle/interface-adapter" "^0.5.16" + bignumber.js "^7.2.1" + ethereum-ens "^0.8.0" + ethers "^4.0.0-beta.1" + source-map-support "^0.5.19" + +"@openzeppelin/contracts-upgradeable@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.1.tgz#363f7dd08f25f8f77e16d374350c3d6b43340a7a" + integrity sha512-1wTv+20lNiC0R07jyIAbHU7TNHKRwGiTGRfiNnA8jOWjKT98g5OgLpYWOi40Vgpk8SPLA9EvfJAbAeIyVn+7Bw== + "@openzeppelin/contracts-upgradeable@4.9.5", "@openzeppelin/contracts-upgradeable@^4.9.5": version "4.9.5" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.5.tgz#572b5da102fc9be1d73f34968e0ca56765969812" integrity sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg== -"@openzeppelin/contracts@4.9.5", "@openzeppelin/contracts@^4.9.5": +"@openzeppelin/contracts@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.1.tgz#709cfc4bbb3ca9f4460d60101f15dac6b7a2d5e4" + integrity sha512-xQ6eUZl+RDyb/FiZe1h+U7qr/f4p/SrTSQcTPH2bjur3C5DbuW/zFgCU/b1P/xcIaEqJep+9ju4xDRi3rmChdQ== + +"@openzeppelin/contracts@4.9.5", "@openzeppelin/contracts@^4.1.0", "@openzeppelin/contracts@^4.9.5": version "4.9.5" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.5.tgz#1eed23d4844c861a1835b5d33507c1017fa98de8" integrity sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg== @@ -1081,6 +1238,11 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" +"@sindresorhus/is@^4.0.0", "@sindresorhus/is@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@smithy/types@^2.7.0": version "2.7.0" resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.7.0.tgz#6ed9ba5bff7c4d28c980cff967e6d8456840a4f3" @@ -1102,6 +1264,111 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + +"@szmarczak/http-timer@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" + integrity sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw== + dependencies: + defer-to-connect "^2.0.1" + +"@truffle/abi-utils@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@truffle/abi-utils/-/abi-utils-1.0.3.tgz#9f0df7a8aaf5e815bee47e0ad26bd4c91e4045f2" + integrity sha512-AWhs01HCShaVKjml7Z4AbVREr/u4oiWxCcoR7Cktm0mEvtT04pvnxW5xB/cI4znRkrbPdFQlFt67kgrAjesYkw== + dependencies: + change-case "3.0.2" + fast-check "3.1.1" + web3-utils "1.10.0" + +"@truffle/blockchain-utils@^0.1.3": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@truffle/blockchain-utils/-/blockchain-utils-0.1.9.tgz#d9b55bd23a134578e4217bae55a6dfbbb038d6dc" + integrity sha512-RHfumgbIVo68Rv9ofDYfynjnYZIfP/f1vZy4RoqkfYAO+fqfc58PDRzB1WAGq2U6GPuOnipOJxQhnqNnffORZg== + +"@truffle/codec@^0.17.3": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@truffle/codec/-/codec-0.17.3.tgz#94057e56e1a947594b35eba498d96915df3861d2" + integrity sha512-Ko/+dsnntNyrJa57jUD9u4qx9nQby+H4GsUO6yjiCPSX0TQnEHK08XWqBSg0WdmCH2+h0y1nr2CXSx8gbZapxg== + dependencies: + "@truffle/abi-utils" "^1.0.3" + "@truffle/compile-common" "^0.9.8" + big.js "^6.0.3" + bn.js "^5.1.3" + cbor "^5.2.0" + debug "^4.3.1" + lodash "^4.17.21" + semver "^7.5.4" + utf8 "^3.0.0" + web3-utils "1.10.0" + +"@truffle/compile-common@^0.9.8": + version "0.9.8" + resolved "https://registry.yarnpkg.com/@truffle/compile-common/-/compile-common-0.9.8.tgz#f91507c895852289a17bf401eefebc293c4c69f0" + integrity sha512-DTpiyo32t/YhLI1spn84D3MHYHrnoVqO+Gp7ZHrYNwDs86mAxtNiH5lsVzSb8cPgiqlvNsRCU9nm9R0YmKMTBQ== + dependencies: + "@truffle/error" "^0.2.2" + colors "1.4.0" + +"@truffle/contract-schema@^3.4.7": + version "3.4.16" + resolved "https://registry.yarnpkg.com/@truffle/contract-schema/-/contract-schema-3.4.16.tgz#c529c3f230db407b2f03290373b20b7366f2d37e" + integrity sha512-g0WNYR/J327DqtJPI70ubS19K1Fth/1wxt2jFqLsPmz5cGZVjCwuhiie+LfBde4/Mc9QR8G+L3wtmT5cyoBxAg== + dependencies: + ajv "^6.10.0" + debug "^4.3.1" + +"@truffle/debug-utils@^6.0.22": + version "6.0.57" + resolved "https://registry.yarnpkg.com/@truffle/debug-utils/-/debug-utils-6.0.57.tgz#4e9a1051221c5f467daa398b0ca638d8b6408a82" + integrity sha512-Q6oI7zLaeNLB69ixjwZk2UZEWBY6b2OD1sjLMGDKBGR7GaHYiw96GLR2PFgPH1uwEeLmV4N78LYaQCrDsHbNeA== + dependencies: + "@truffle/codec" "^0.17.3" + "@trufflesuite/chromafi" "^3.0.0" + bn.js "^5.1.3" + chalk "^2.4.2" + debug "^4.3.1" + highlightjs-solidity "^2.0.6" + +"@truffle/error@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@truffle/error/-/error-0.1.1.tgz#e52026ac8ca7180d83443dca73c03e07ace2a301" + integrity sha512-sE7c9IHIGdbK4YayH4BC8i8qMjoAOeg6nUXUDZZp8wlU21/EMpaG+CLx+KqcIPyR+GSWIW3Dm0PXkr2nlggFDA== + +"@truffle/error@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@truffle/error/-/error-0.2.2.tgz#1b4c4237c14dda792f20bd4f19ff4e4585b47796" + integrity sha512-TqbzJ0O8DHh34cu8gDujnYl4dUl6o2DE4PR6iokbybvnIm/L2xl6+Gv1VC+YJS45xfH83Yo3/Zyg/9Oq8/xZWg== + +"@truffle/interface-adapter@^0.5.16": + version "0.5.37" + resolved "https://registry.yarnpkg.com/@truffle/interface-adapter/-/interface-adapter-0.5.37.tgz#95d249c1912d2baaa63c54e8a138d3f476a1181a" + integrity sha512-lPH9MDgU+7sNDlJSClwyOwPCfuOimqsCx0HfGkznL3mcFRymc1pukAR1k17zn7ErHqBwJjiKAZ6Ri72KkS+IWw== + dependencies: + bn.js "^5.1.3" + ethers "^4.0.32" + web3 "1.10.0" + +"@trufflesuite/chromafi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@trufflesuite/chromafi/-/chromafi-3.0.0.tgz#f6956408c1af6a38a6ed1657783ce59504a1eb8b" + integrity sha512-oqWcOqn8nT1bwlPPfidfzS55vqcIDdpfzo3HbU9EnUmcSTX+I8z0UyUFI3tZQjByVJulbzxHxUGS3ZJPwK/GPQ== + dependencies: + camelcase "^4.1.0" + chalk "^2.3.2" + cheerio "^1.0.0-rc.2" + detect-indent "^5.0.0" + highlight.js "^10.4.1" + lodash.merge "^4.6.2" + strip-ansi "^4.0.0" + strip-indent "^2.0.0" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -1144,13 +1411,23 @@ dependencies: "@types/node" "*" -"@types/bn.js@^5.1.0": +"@types/bn.js@^5.1.0", "@types/bn.js@^5.1.1": version "5.1.5" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== dependencies: "@types/node" "*" +"@types/cacheable-request@^6.0.1", "@types/cacheable-request@^6.0.2": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/chai-as-promised@^7.1.3": version "7.1.8" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9" @@ -1163,6 +1440,11 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.11.tgz#e95050bf79a932cb7305dd130254ccdf9bde671c" integrity sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ== +"@types/chai@^4.2.0": + version "4.3.12" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.12.tgz#b192fe1c553b54f45d20543adc2ab88455a07d5e" + integrity sha512-zNKDHG/1yxm8Il6uCCVsm+dRdEsJlFoDu73X17y09bId6UwoYww+vFBsAcRzl8knM1sab3Dp1VRikFQwDOtDDw== + "@types/concat-stream@^1.6.0": version "1.6.1" resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74" @@ -1185,6 +1467,18 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/http-cache-semantics@*": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/long@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" @@ -1222,6 +1516,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== +"@types/node@^12.12.6": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + "@types/node@^18.11.9": version "18.19.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.3.tgz#e4723c4cb385641d61b983f6fe0b716abd5f8fc0" @@ -1259,6 +1558,13 @@ "@types/node" "*" safe-buffer "~5.1.1" +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + "@types/secp256k1@^4.0.1": version "4.0.6" resolved "https://registry.yarnpkg.com/@types/secp256k1/-/secp256k1-4.0.6.tgz#d60ba2349a51c2cbc5e816dcd831a42029d376bf" @@ -1343,6 +1649,11 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +abortcontroller-polyfill@^1.7.3, abortcontroller-polyfill@^1.7.5: + version "1.7.5" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed" + integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== + abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" @@ -1356,6 +1667,14 @@ abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: module-error "^1.0.1" queue-microtask "^1.2.3" +accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-walk@^8.1.1: version "8.3.1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" @@ -1396,6 +1715,16 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv@^6.10.0, ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^8.0.1: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" @@ -1439,6 +1768,11 @@ ansi-escapes@^4.3.0: dependencies: type-fest "^0.21.3" +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + ansi-regex@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" @@ -1529,6 +1863,11 @@ array-buffer-byte-length@^1.0.0: call-bind "^1.0.2" is-array-buffer "^3.0.1" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -1568,6 +1907,18 @@ asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +asn1@~0.2.3: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -1578,6 +1929,11 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" @@ -1605,6 +1961,16 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== + +aws4@^1.8.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.12.0.tgz#ce1c9d143389679e253b314241ea9aa5cec980d3" + integrity sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg== + axios@^0.21.1, axios@^0.21.2: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -1626,7 +1992,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-x@^3.0.2: +base-x@^3.0.2, base-x@^3.0.8: version "3.0.9" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== @@ -1638,16 +2004,43 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -bech32@1.1.4: +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +bech32@1.1.4, bech32@^1.1.3: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +big-integer@1.6.36: + version "1.6.36" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" + integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== + +big.js@^6.0.3: + version "6.2.1" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.1.tgz#7205ce763efb17c2e41f26f121c420c6a7c2744f" + integrity sha512-bCtHMwL9LeDIozFn+oNhhFoq+yQ3BNdnsLSASUxLciOb1vgvpHsIO1dsENiGMgbb4SkP5TrzWzRiLddn8ahVOQ== + bigint-crypto-utils@^3.0.23: version "3.3.0" resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.3.0.tgz#72ad00ae91062cf07f2b1def9594006c279c1d77" integrity sha512-jOTSb+drvEDxEq6OuUybOAv/xxoh3cuYRUIPyu8sSHQNKM303UQ2R1DAo45o1AkcIXw6fzbaFI1+xGGdaXs2lg== +bignumber.js@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" + integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== + +bignumber.js@^9.0.0, bignumber.js@^9.0.1: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -1674,21 +2067,49 @@ blob-to-it@^1.0.1: dependencies: browser-readablestream-to-it "^1.0.3" +bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + bn.js@4.11.6: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" integrity sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA== -bn.js@^4.11.0, bn.js@^4.11.8, bn.js@^4.11.9: +bn.js@^4.11.0, bn.js@^4.11.6, bn.js@^4.11.8, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: +bn.js@^5.1.2, bn.js@^5.1.3, bn.js@^5.2.0, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== +body-parser@1.20.2, body-parser@^1.16.0: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -1748,7 +2169,7 @@ browserify-aes@^1.2.0: inherits "^2.0.1" safe-buffer "^5.0.1" -bs58@^4.0.0: +bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== @@ -1769,6 +2190,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-to-arraybuffer@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" + integrity sha512-3dthu5CYiVB1DEJp61FtApNnNndTckcqe4pFcLdvHtrpG+kcyekCJKg4MRiDcFW7A6AODnXB9U4dwQiCW5kzJQ== + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1783,7 +2209,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^6.0.1, buffer@^6.0.3: +buffer@6.0.3, 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== @@ -1791,11 +2217,49 @@ buffer@^6.0.1, buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffer@^5.0.5, buffer@^5.5.0, buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bufferutil@^4.0.1: + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== + dependencies: + node-gyp-build "^4.3.0" + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-lookup@^6.0.4: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-6.1.0.tgz#0330a543471c61faa4e9035db583aad753b36385" + integrity sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" @@ -1805,6 +2269,24 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" +camel-case@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w== + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg== + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw== + camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -1825,6 +2307,14 @@ catering@^2.1.0, catering@^2.1.1: resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== +cbor@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.2.0.tgz#4cca67783ccd6de7b50ab4ed62636712f287a67c" + integrity sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A== + dependencies: + bignumber.js "^9.0.1" + nofilter "^1.0.4" + cbor@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/cbor/-/cbor-8.1.0.tgz#cfc56437e770b73417a2ecbfc9caf6b771af60d5" @@ -1851,6 +2341,19 @@ chai-as-promised@^7.1.1: dependencies: check-error "^1.0.2" +chai@^4.2.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" + integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.3" + deep-eql "^4.1.3" + get-func-name "^2.0.2" + loupe "^2.3.6" + pathval "^1.1.1" + type-detect "^4.0.8" + chai@^4.3.7: version "4.3.10" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" @@ -1864,7 +2367,7 @@ chai@^4.3.7: pathval "^1.1.1" type-detect "^4.0.8" -chalk@^2.4.2: +chalk@^2.3.2, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1881,6 +2384,30 @@ chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +change-case@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.2.tgz#fd48746cce02f03f0a672577d1d3a8dc2eceb037" + integrity sha512-Mww+SLF6MZ0U6kdg11algyKd5BARbyM4TbFBepwowYSR5ClfQGCGtxNXgykpN0uF/bstWeaGDT4JWaDh8zWAHA== + dependencies: + camel-case "^3.0.0" + constant-case "^2.0.0" + dot-case "^2.1.0" + header-case "^1.0.0" + is-lower-case "^1.1.0" + is-upper-case "^1.1.0" + lower-case "^1.1.1" + lower-case-first "^1.0.0" + no-case "^2.3.2" + param-case "^2.1.0" + pascal-case "^2.0.0" + path-case "^2.1.0" + sentence-case "^2.1.0" + snake-case "^2.1.0" + swap-case "^1.1.0" + title-case "^2.1.0" + upper-case "^1.1.1" + upper-case-first "^1.1.0" + "charenc@>= 0.0.1": version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" @@ -1893,6 +2420,31 @@ check-error@^1.0.2, check-error@^1.0.3: dependencies: get-func-name "^2.0.2" +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.0.1" + htmlparser2 "^8.0.1" + parse5 "^7.0.0" + parse5-htmlparser2-tree-adapter "^7.0.0" + chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -1908,11 +2460,27 @@ chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +cids@^0.7.1: + version "0.7.5" + resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.5.tgz#60a08138a99bfb69b6be4ceb63bfef7a396b28b2" + integrity sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA== + dependencies: + buffer "^5.5.0" + class-is "^1.1.0" + multibase "~0.6.0" + multicodec "^1.0.0" + multihashes "~0.4.15" + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -1921,6 +2489,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +class-is@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" + integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== + classic-level@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/classic-level/-/classic-level-1.3.0.tgz#5e36680e01dc6b271775c093f2150844c5edd5c8" @@ -1947,6 +2520,15 @@ cli-table3@^0.5.0: optionalDependencies: colors "^1.1.2" +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w== + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -1956,6 +2538,18 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1985,7 +2579,7 @@ colors@1.4.0, colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2042,16 +2636,68 @@ concat-stream@^1.6.0, concat-stream@^1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" +constant-case@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46" + integrity sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ== + dependencies: + snake-case "^2.1.0" + upper-case "^1.1.1" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-hash@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/content-hash/-/content-hash-2.5.2.tgz#bbc2655e7c21f14fd3bfc7b7d4bfe6e454c9e211" + integrity sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw== + dependencies: + cids "^0.7.1" + multicodec "^0.5.5" + multihashes "^0.4.15" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@^2.8.1: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + crc-32@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" @@ -2092,13 +2738,20 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-fetch@^3.1.5: +cross-fetch@^3.1.4, 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-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + 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" @@ -2113,6 +2766,50 @@ 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== +crypto-addr-codec@^0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/crypto-addr-codec/-/crypto-addr-codec-0.1.8.tgz#45c4b24e2ebce8e24a54536ee0ca25b65787b016" + integrity sha512-GqAK90iLLgP3FvhNmHbpT3wR6dEdaM8hZyZtLX29SPardh3OA13RFLHDR6sntGCgRWOfiHqW6sIyohpNqOtV/g== + dependencies: + base-x "^3.0.8" + big-integer "1.6.36" + blakejs "^1.1.0" + bs58 "^4.0.1" + ripemd160-min "0.0.6" + safe-buffer "^5.2.0" + sha3 "^2.1.1" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== + dependencies: + assert-plus "^1.0.0" + 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" @@ -2123,6 +2820,13 @@ death@^1.1.0: resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" integrity sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w== +debug@2.6.9, debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@4, debug@4.3.4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -2130,11 +2834,35 @@ debug@4, debug@4.3.4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3: dependencies: ms "2.1.2" +decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== +decode-uri-component@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== + dependencies: + mimic-response "^1.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-eql@^4.0.1, deep-eql@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-4.1.3.tgz#7c7775513092f7df98d8df9996dd085eb668cc6d" @@ -2152,6 +2880,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +defer-to-connect@^2.0.0, defer-to-connect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-data-property@^1.0.1, define-data-property@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" @@ -2180,6 +2913,16 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g== + detect-port@^1.3.0: version "1.5.1" resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.5.1.tgz#451ca9b6eaf20451acb0799b8ab40dff7718727b" @@ -2221,6 +2964,55 @@ dns-over-http-resolver@^1.2.3: native-fetch "^3.0.0" receptacle "^1.3.2" +dns-packet@^5.3.0: + version "5.6.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dot-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-2.1.1.tgz#34dcf37f50a8e93c2b3bca8bb7fb9155c7da3bee" + integrity sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug== + dependencies: + no-case "^2.2.0" + dotenv@^16.3.1: version "16.3.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" @@ -2231,6 +3023,19 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + electron-fetch@^1.7.2: version "1.9.1" resolved "https://registry.yarnpkg.com/electron-fetch/-/electron-fetch-1.9.1.tgz#e28bfe78d467de3f2dec884b1d72b8b05322f30f" @@ -2238,7 +3043,7 @@ electron-fetch@^1.7.2: dependencies: encoding "^0.1.13" -elliptic@6.5.4, elliptic@^6.5.2, elliptic@^6.5.4: +elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -2266,6 +3071,11 @@ encode-utf8@^1.0.2: resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -2273,6 +3083,13 @@ encoding@^0.1.13: 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" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + enquirer@^2.3.0, enquirer@^2.3.6: version "2.4.1" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" @@ -2281,6 +3098,11 @@ enquirer@^2.3.0, enquirer@^2.3.6: ansi-colors "^4.1.1" strip-ansi "^6.0.1" +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -2291,6 +3113,13 @@ err-code@^3.0.1: resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920" integrity sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA== +error-ex@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-abstract@^1.22.1: version "1.22.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" @@ -2361,11 +3190,48 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@^0.10.62, es5-ext@~0.10.14: + version "0.10.64" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" + integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + esniff "^2.0.1" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-promise@^4.2.8: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + escape-string-regexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -2388,6 +3254,16 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + esprima@2.7.x, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -2408,6 +3284,19 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.0, eth-ens-namehash@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" + integrity sha512-VWEI1+KJfz4Km//dadyvBBoBeSQ0MHTXPvr8UIXiLW6IanxvAV+DmlZAijZwAyggqGUfwQBeHf7tc9wzc1piSw== + dependencies: + idna-uts46-hx "^2.3.1" + js-sha3 "^0.5.7" + eth-gas-reporter@^0.2.25: version "0.2.27" resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz#928de8548a674ed64c7ba0bf5795e63079150d4e" @@ -2427,6 +3316,27 @@ eth-gas-reporter@^0.2.25: sha1 "^1.1.1" sync-request "^6.0.0" +eth-lib@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.8.tgz#b194058bef4b220ad12ea497431d6cb6aa0623c8" + integrity sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + xhr-request-promise "^0.1.2" + +eth-lib@^0.1.26: + version "0.1.29" + resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.29.tgz#0c11f5060d42da9f931eab6199084734f4dbd1d9" + integrity sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ== + dependencies: + bn.js "^4.11.6" + elliptic "^6.4.0" + nano-json-stream-parser "^0.1.2" + servify "^0.1.12" + ws "^3.0.0" + xhr-request-promise "^0.1.2" + ethereum-bloom-filters@^1.0.6: version "1.0.10" resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz#3ca07f4aed698e75bd134584850260246a5fed8a" @@ -2475,6 +3385,18 @@ ethereum-cryptography@^2.0.0, ethereum-cryptography@^2.1.2: "@scure/bip32" "1.3.1" "@scure/bip39" "1.2.1" +ethereum-ens@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/ethereum-ens/-/ethereum-ens-0.8.0.tgz#6d0f79acaa61fdbc87d2821779c4e550243d4c57" + integrity sha512-a8cBTF4AWw1Q1Y37V1LSCS9pRY4Mh3f8vCg5cbXCCEJ3eno1hbI/+Ccv9SZLISYpqQhaglP3Bxb/34lS4Qf7Bg== + dependencies: + bluebird "^3.4.7" + eth-ens-namehash "^2.0.0" + js-sha3 "^0.5.7" + pako "^1.0.4" + underscore "^1.8.3" + web3 "^1.0.0-beta.34" + ethereumjs-abi@^0.6.8: version "0.6.8" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz#71bc152db099f70e62f108b7cdfca1b362c6fcae" @@ -2496,7 +3418,7 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: ethjs-util "0.1.6" rlp "^2.2.3" -ethereumjs-util@^7.0.3, ethereumjs-util@^7.1.4: +ethereumjs-util@^7.0.3, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.2, ethereumjs-util@^7.1.4, ethereumjs-util@^7.1.5: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== @@ -2507,7 +3429,22 @@ ethereumjs-util@^7.0.3, ethereumjs-util@^7.1.4: ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethers@^5.6.2, ethers@^5.7.0, ethers@^5.7.1, ethers@^5.7.2: +ethers@^4.0.0-beta.1, ethers@^4.0.32: + version "4.0.49" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.49.tgz#0eb0e9161a0c8b4761be547396bbe2fb121a8894" + integrity sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg== + dependencies: + aes-js "3.0.0" + bn.js "^4.11.9" + elliptic "6.5.4" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.4" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + +ethers@^5.0.13, ethers@^5.6.2, ethers@^5.7.0, ethers@^5.7.1, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -2559,11 +3496,24 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventemitter3@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" + integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== + evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -2572,16 +3522,82 @@ evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +express@^4.14.0: + version "4.18.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4" + integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + 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== +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + 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" integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q== +fast-check@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-3.1.1.tgz#72c5ae7022a4e86504762e773adfb8a5b0b01252" + integrity sha512-3vtXinVyuUKCKFKYcwXhGE6NtGWkqF8Yh3rvMZNzmwz8EPrgoc/v4pDdLHyLnCyCI5MZpZZkDEwFyXyEONOxpA== + dependencies: + pure-rand "^5.0.1" + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -2603,6 +3619,11 @@ fast-glob@^3.0.3: merge2 "^1.3.0" micromatch "^4.0.4" +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -2622,6 +3643,19 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + find-replace@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" @@ -2637,6 +3671,14 @@ find-up@5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -2676,6 +3718,16 @@ foreground-child@^3.1.0: cross-spawn "^7.0.0" signal-exit "^4.0.1" +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== + +form-data-encoder@1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" + integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg== + form-data@^2.2.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" @@ -2703,6 +3755,20 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + fp-ts@1.19.3: version "1.19.3" resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.3.tgz#261a60d1088fbff01f91256f91d21d0caaaaa96f" @@ -2713,6 +3779,11 @@ fp-ts@^1.0.0: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.5.tgz#3da865e585dfa1fdfd51785417357ac50afc520a" integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A== +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -2733,6 +3804,15 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^7.0.0, fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -2761,6 +3841,13 @@ fs-extra@^9.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -2801,6 +3888,11 @@ functions-have-names@^1.2.3: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -2831,6 +3923,18 @@ get-port@^3.1.0: resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" integrity sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -2839,6 +3943,13 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== + dependencies: + assert-plus "^1.0.0" + ghost-testrpc@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz#c4de9557b1d1ae7b2d20bbe474a91378ca90ce92" @@ -2928,6 +4039,14 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" +global@~4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + globalthis@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" @@ -2956,6 +4075,42 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +got@12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-12.1.0.tgz#099f3815305c682be4fd6b0ee0726d8e4c6b0af4" + integrity sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig== + dependencies: + "@sindresorhus/is" "^4.6.0" + "@szmarczak/http-timer" "^5.0.1" + "@types/cacheable-request" "^6.0.2" + "@types/responselike" "^1.0.0" + cacheable-lookup "^6.0.4" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + form-data-encoder "1.7.1" + get-stream "^6.0.1" + http2-wrapper "^2.1.10" + lowercase-keys "^3.0.0" + p-cancelable "^3.0.0" + responselike "^2.0.0" + +got@^11.8.5: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -2987,6 +4142,19 @@ handlebars@^4.0.1: optionalDependencies: uglify-js "^3.1.4" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + hardhat-deploy@^0.11.26: version "0.11.44" resolved "https://registry.yarnpkg.com/hardhat-deploy/-/hardhat-deploy-0.11.44.tgz#a7a771a675a3837ce4c321f2c18d4b6fa1ed03a0" @@ -3133,6 +4301,19 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" +hash-test-vectors@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/hash-test-vectors/-/hash-test-vectors-1.3.2.tgz#f050fde1aff46ec28dcf4f70e4e3238cd5000f4c" + integrity sha512-PKd/fitmsrlWGh3OpKbgNLE04ZQZsvs1ZkuLoQpeIKuwx+6CYVNdW6LaPIS1QAdZvV40+skk0w4YomKnViUnvQ== + +hash.js@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" + hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -3153,11 +4334,29 @@ he@1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +header-case@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/header-case/-/header-case-1.0.1.tgz#9535973197c144b09613cd65d317ef19963bd02d" + integrity sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ== + dependencies: + no-case "^2.2.0" + upper-case "^1.1.3" + "heap@>= 0.2.0": version "0.2.7" resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc" integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg== +highlight.js@^10.4.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +highlightjs-solidity@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/highlightjs-solidity/-/highlightjs-solidity-2.0.6.tgz#e7a702a2b05e0a97f185e6ba39fd4846ad23a990" + integrity sha512-DySXWfQghjm2l6a/flF+cteroJqD4gI8GSdL4PtvxZSsAHie8m3yVe2JFoRg03ROKT6hp2Lc/BxXkqerNmtQYg== + hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -3167,6 +4366,21 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +htmlparser2@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + http-basic@^8.1.1: version "8.1.3" resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf" @@ -3177,6 +4391,11 @@ http-basic@^8.1.1: http-response-object "^3.0.1" parse-cache-control "^1.0.1" +http-cache-semantics@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -3188,6 +4407,11 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-https@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b" + integrity sha512-o0PWwVCSp3O0wS6FvNr6xfBCHgt0m1tvPLFOCc2iFDKTRAXhB7m8klDf7ErowFH8POa6dVdGatKU5I1YYwzUyg== + http-response-object@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810" @@ -3195,6 +4419,31 @@ http-response-object@^3.0.1: dependencies: "@types/node" "^10.0.3" +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + +http2-wrapper@^2.1.10: + version "2.2.1" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" + integrity sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.2.0" + https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -3217,7 +4466,14 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.4, ieee754@^1.2.1: +idna-uts46-hx@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9" + integrity sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA== + dependencies: + punycode "2.1.0" + +ieee754@^1.1.13, ieee754@^1.1.4, 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== @@ -3294,6 +4550,11 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ== + io-ts@1.10.4: version "1.10.4" resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2" @@ -3306,6 +4567,11 @@ ip-regex@^4.0.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + 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" @@ -3413,6 +4679,11 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: get-intrinsic "^1.2.0" is-typed-array "^1.1.10" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -3469,6 +4740,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw== + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -3479,6 +4757,11 @@ 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-function@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" + integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== + 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" @@ -3505,6 +4788,13 @@ is-ip@^3.1.0: dependencies: ip-regex "^4.0.0" +is-lower-case@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393" + integrity sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA== + dependencies: + lower-case "^1.1.0" + is-negative-zero@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" @@ -3563,11 +4853,28 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed- dependencies: which-typed-array "^1.1.11" +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +is-upper-case@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f" + integrity sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw== + dependencies: + upper-case "^1.1.0" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -3608,6 +4915,11 @@ isomorphic-unfetch@^3.0.0, isomorphic-unfetch@^3.1.0: node-fetch "^2.6.1" unfetch "^4.2.0" +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== + 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" @@ -3713,6 +5025,11 @@ js-sdsl@^4.1.4: resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.2.tgz#2e3c031b1f47d3aca8b775532e3ebb0818e7f847" integrity sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w== +js-sha3@0.5.7, js-sha3@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" + integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== + js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" @@ -3733,11 +5050,36 @@ js-yaml@4.1.0: dependencies: argparse "^2.0.1" +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" @@ -3766,6 +5108,16 @@ jsonschema@^1.2.4: resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.4.0" + verror "1.10.0" + keccak@^3.0.0, keccak@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" @@ -3775,6 +5127,13 @@ keccak@^3.0.0, keccak@^3.0.2: node-gyp-build "^4.2.0" readable-stream "^3.6.0" +keyv@^4.0.0: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -3787,6 +5146,13 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw== + dependencies: + invert-kv "^1.0.0" + level-supports@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/level-supports/-/level-supports-4.0.1.tgz#431546f9d81f10ff0fea0e74533a0e875c08c66a" @@ -3816,6 +5182,17 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -3831,11 +5208,21 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.assign@^4.0.3, lodash.assign@^4.0.6: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw== + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -3866,6 +5253,28 @@ loupe@^2.3.6: dependencies: get-func-name "^2.0.1" +lower-case-first@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1" + integrity sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA== + dependencies: + lower-case "^1.1.2" + +lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lowercase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" + integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -3919,6 +5328,11 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + memory-level@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/memory-level/-/memory-level-1.0.0.tgz#7323c3fd368f9af2f71c3cd76ba403a17ac41692" @@ -3933,6 +5347,11 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + merge-options@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" @@ -3945,6 +5364,11 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + micro-ftch@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f" @@ -3963,13 +5387,35 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== + dependencies: + dom-walk "^0.1.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -4006,12 +5452,39 @@ minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.7: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^2.6.0, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0": version "7.0.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== -mkdirp@0.5.x: +minizlib@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + +mkdirp-promise@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" + integrity sha512-Hepn5kb1lJPtVW84RFT40YG1OddBNTOVUZR2bzQUHc+Z03en8/3uX0+060JDhcEzyO08HmipsN9DcnFMxhIL9w== + dependencies: + mkdirp "*" + +mkdirp@*: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== + +mkdirp@0.5.x, mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -4057,6 +5530,11 @@ mocha@10.2.0, mocha@^10.0.0, mocha@^10.1.0, mocha@^10.2.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" +mock-fs@^4.1.0: + version "4.14.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.14.0.tgz#ce5124d2c601421255985e6e94da80a7357b1b18" + integrity sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw== + module-error@^1.0.1, module-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" @@ -4067,6 +5545,11 @@ mrmime@^1.0.0: resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -4096,11 +5579,51 @@ multiaddr@^10.0.0: uint8arrays "^3.0.0" varint "^6.0.0" +multibase@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b" + integrity sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg== + dependencies: + base-x "^3.0.8" + buffer "^5.5.0" + +multibase@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.6.1.tgz#b76df6298536cc17b9f6a6db53ec88f85f8cc12b" + integrity sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw== + dependencies: + base-x "^3.0.8" + buffer "^5.5.0" + +multicodec@^0.5.5: + version "0.5.7" + resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-0.5.7.tgz#1fb3f9dd866a10a55d226e194abba2dcc1ee9ffd" + integrity sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA== + dependencies: + varint "^5.0.0" + +multicodec@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/multicodec/-/multicodec-1.0.4.tgz#46ac064657c40380c28367c90304d8ed175a714f" + integrity sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg== + dependencies: + buffer "^5.6.0" + varint "^5.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== +multihashes@^0.4.15, multihashes@~0.4.15: + version "0.4.21" + resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.21.tgz#dc02d525579f334a7909ade8a122dabb58ccfcb5" + integrity sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw== + dependencies: + buffer "^5.5.0" + multibase "^0.7.0" + varint "^5.0.0" + murmur-128@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/murmur-128/-/murmur-128-0.2.1.tgz#a9f6568781d2350ecb1bf80c14968cadbeaa4b4d" @@ -4110,6 +5633,16 @@ murmur-128@^0.2.1: fmix "^0.1.0" imul "^1.0.0" +nano-base32@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/nano-base32/-/nano-base32-1.0.1.tgz#ba548c879efcfb90da1c4d9e097db4a46c9255ef" + integrity sha512-sxEtoTqAPdjWVGv71Q17koMFGsOMSiHsIFEvzOM7cNp8BXB4AnEwmDabm5dorusJf/v1z7QxaZYxUorU9RKaAw== + +nano-json-stream-parser@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" + integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew== + nanoid@3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" @@ -4135,11 +5668,28 @@ native-fetch@^3.0.0: resolved "https://registry.yarnpkg.com/native-fetch/-/native-fetch-3.0.0.tgz#06ccdd70e79e171c365c75117959cf4fe14a09bb" integrity sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +no-case@^2.2.0, no-case@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -4168,6 +5718,11 @@ node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.7.1.tgz#cd7d2eb48e594874053150a9418ac85af83ca8f7" integrity sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg== +nofilter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.4.tgz#78d6f4b6a613e7ced8b015cec534625f7667006e" + integrity sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA== + nofilter@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" @@ -4180,11 +5735,38 @@ nopt@3.x: dependencies: abbrev "1" +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== + number-to-bn@1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" @@ -4193,7 +5775,12 @@ number-to-bn@1.7.0: bn.js "4.11.6" strip-hex-prefix "1.0.0" -object-assign@^4.1.0: +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -4223,7 +5810,21 @@ obliterator@^2.0.0: resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" integrity sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ== -once@1.x, once@^1.3.0: +oboe@2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.5.tgz#5554284c543a2266d7a38f17e073821fbde393cd" + integrity sha512-zRFWiF+FoicxEs3jNI/WYUrVEgA7DeET/InK0XQuudGHRg8iIob3cNPrJTKaz4004uaA9Pbe+Dwa8iluhjLZWA== + dependencies: + http-https "^1.0.0" + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -4247,11 +5848,28 @@ ordinal@^1.0.3: resolved "https://registry.yarnpkg.com/ordinal/-/ordinal-1.0.3.tgz#1a3c7726a61728112f50944ad7c35c06ae3a0d4d" integrity sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ== -os-tmpdir@~1.0.2: - version "1.0.2" +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g== + dependencies: + lcid "^1.0.0" + +os-tmpdir@~1.0.2: + version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + +p-cancelable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050" + integrity sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw== + p-defer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83" @@ -4305,6 +5923,18 @@ p-try@^1.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== +pako@^1.0.4: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +param-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w== + dependencies: + no-case "^2.2.0" + parse-cache-control@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e" @@ -4315,6 +5945,60 @@ parse-duration@^1.0.0: resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-1.1.0.tgz#5192084c5d8f2a3fd676d04a451dbd2e05a1819c" integrity sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ== +parse-headers@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9" + integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA== + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== + dependencies: + error-ex "^1.2.0" + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" + integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== + dependencies: + domhandler "^5.0.2" + parse5 "^7.0.0" + +parse5@^7.0.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-2.0.1.tgz#2d578d3455f660da65eca18ef95b4e0de912761e" + integrity sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ== + dependencies: + camel-case "^3.0.0" + upper-case-first "^1.1.0" + +path-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5" + integrity sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q== + dependencies: + no-case "^2.2.0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== + dependencies: + pinkie-promise "^2.0.0" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -4348,6 +6032,20 @@ path-scurry@^1.10.1: lru-cache "^9.1.1 || ^10.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -4369,16 +6067,38 @@ pbkdf2@^3.0.17: safe-buffer "^5.0.1" sha.js "^2.4.8" +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -4394,6 +6114,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + promise@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" @@ -4434,16 +6159,54 @@ protobufjs@^6.10.2: "@types/node" ">=13.7.0" long "^4.0.0" +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -punycode@^2.1.0: +psl@^1.1.28: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + integrity sha512-Yxz2kRwT90aPiWEMHVYnEf4+rhwF1tBmmZ4KepCP+Wkium9JxtWnUm1nqGwpiAHr/tnTSeHqr3wb++jgSkXjhA== + +punycode@^2.1.0, punycode@^2.1.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +pure-rand@^5.0.1: + version "5.0.5" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-5.0.5.tgz#bda2a7f6a1fc0f284d78d78ca5902f26f2ad35cf" + integrity sha512-BwQpbqxSCBJVpamI6ydzcKqyFmnd5msMWUGvzXLm1aXvusbbgkbOto/EUPM00hjveJEaJtdbhUjKSzWRhQVkaw== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + qs@^6.4.0, qs@^6.9.4: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" @@ -4451,11 +6214,30 @@ qs@^6.4.0, qs@^6.9.4: dependencies: side-channel "^1.0.4" +qs@~6.5.2: + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== + +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw== + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + queue-microtask@^1.2.2, queue-microtask@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -4463,7 +6245,12 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" -raw-body@^2.4.1: +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2, raw-body@^2.4.1: version "2.5.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== @@ -4480,6 +6267,23 @@ react-native-fetch-api@^2.0.0: dependencies: p-defer "^3.0.0" +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A== + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ== + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + readable-stream@^2.2.2: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" @@ -4535,6 +6339,11 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" @@ -4558,16 +6367,57 @@ req-from@^2.0.0: dependencies: resolve-from "^3.0.0" +request@^2.79.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" + integrity sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q== + require-from-string@^2.0.0, require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug== + +resolve-alpn@^1.0.0, resolve-alpn@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -4585,7 +6435,7 @@ resolve@1.17.0: dependencies: path-parse "^1.0.6" -resolve@^1.1.6: +resolve@^1.1.6, resolve@^1.10.0: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -4594,6 +6444,13 @@ resolve@^1.1.6: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + retimer@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/retimer/-/retimer-2.0.0.tgz#e8bd68c5e5a8ec2f49ccb5c636db84c04063bbca" @@ -4635,7 +6492,12 @@ rimraf@^5.0.5: dependencies: glob "^10.3.7" -ripemd160@^2.0.0, ripemd160@^2.0.1: +ripemd160-min@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/ripemd160-min/-/ripemd160-min-0.0.6.tgz#a904b77658114474d02503e819dcc55853b67e62" + integrity sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A== + +ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== @@ -4679,7 +6541,7 @@ safe-array-concat@^1.0.1: has-symbols "^1.0.3" isarray "^2.0.5" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4698,7 +6560,7 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -4723,7 +6585,12 @@ sc-istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" -scrypt-js@3.0.1, scrypt-js@^3.0.0: +scrypt-js@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" + integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== + +scrypt-js@3.0.1, scrypt-js@^3.0.0, scrypt-js@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== @@ -4737,7 +6604,7 @@ secp256k1@^4.0.1: node-addon-api "^2.0.0" node-gyp-build "^4.2.0" -semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -4754,6 +6621,40 @@ semver@^7.3.4: dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +sentence-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-2.1.1.tgz#1f6e2dda39c168bf92d13f86d4a918933f667ed4" + integrity sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ== + dependencies: + no-case "^2.2.0" + upper-case-first "^1.1.2" + serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" @@ -4761,6 +6662,32 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +servify@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" + integrity sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw== + dependencies: + body-parser "^1.16.0" + cors "^2.8.1" + express "^4.14.0" + request "^2.79.0" + xhr "^2.3.3" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + set-function-length@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" @@ -4780,6 +6707,11 @@ set-function-name@^2.0.0: functions-have-names "^1.2.3" has-property-descriptors "^1.0.0" +setimmediate@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" + integrity sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog== + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -4806,6 +6738,13 @@ sha1@^1.1.1: charenc ">= 0.0.1" crypt ">= 0.0.1" +sha3@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.4.tgz#000fac0fe7c2feac1f48a25e7a31b52a6492cc8f" + integrity sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg== + dependencies: + buffer "6.0.3" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -4846,6 +6785,20 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^2.7.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.2.tgz#5708fb0919d440657326cd5fe7d2599d07705019" + integrity sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw== + dependencies: + decompress-response "^3.3.0" + once "^1.3.1" + simple-concat "^1.0.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -4860,6 +6813,13 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +snake-case@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f" + integrity sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q== + dependencies: + no-case "^2.2.0" + solc@0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a" @@ -4875,6 +6835,17 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" +solc@^0.4.20: + version "0.4.26" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.26.tgz#5390a62a99f40806b86258c737c1cf653cc35cb5" + integrity sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA== + dependencies: + fs-extra "^0.30.0" + memorystream "^0.3.1" + require-from-string "^1.1.0" + semver "^5.3.0" + yargs "^4.7.1" + solidity-ast@^0.4.51: version "0.4.55" resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.55.tgz#00b685e6eefb2e8dfb67df1fe0afbe3b3bfb4b28" @@ -4908,7 +6879,7 @@ solidity-coverage@^0.8.2: shelljs "^0.8.3" web3-utils "^1.3.6" -source-map-support@^0.5.13: +source-map-support@^0.5.13, source-map-support@^0.5.19: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -4928,11 +6899,52 @@ source-map@~0.2.0: dependencies: amdefine ">=0.0.4" +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.17" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" + integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sshpk@^1.7.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" + integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + stacktrace-parser@^0.1.10: version "0.1.10" resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" @@ -4952,6 +6964,11 @@ stream-to-it@^0.2.2: dependencies: get-iterator "^1.0.2" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== + string-format@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" @@ -4966,6 +6983,15 @@ string-format@^2.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw== + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -5031,6 +7057,13 @@ string_decoder@~1.1.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -5045,6 +7078,13 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g== + dependencies: + is-utf8 "^0.2.0" + strip-hex-prefix@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" @@ -5052,6 +7092,11 @@ strip-hex-prefix@1.0.0: dependencies: is-hex-prefixed "1.0.0" +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + integrity sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA== + strip-json-comments@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -5090,6 +7135,31 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swap-case@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/swap-case/-/swap-case-1.1.2.tgz#c39203a4587385fad3c850a0bd1bcafa081974e3" + integrity sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ== + dependencies: + lower-case "^1.1.1" + upper-case "^1.1.1" + +swarm-js@^0.1.40: + version "0.1.42" + resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.42.tgz#497995c62df6696f6e22372f457120e43e727979" + integrity sha512-BV7c/dVlA3R6ya1lMlSSNPLYrntt0LUq4YMgy3iwpCIc6rZnS5W2wUoctarZ5pXlpKtxDDf9hNziEkcfrxdhqQ== + dependencies: + bluebird "^3.5.0" + buffer "^5.0.5" + eth-lib "^0.1.26" + fs-extra "^4.0.2" + got "^11.8.5" + mime-types "^2.1.16" + mkdirp-promise "^5.0.1" + mock-fs "^4.1.0" + setimmediate "^1.0.5" + tar "^4.0.2" + xhr-request "^1.0.1" + sync-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68" @@ -5127,6 +7197,24 @@ table@^6.8.0: string-width "^4.2.3" strip-ansi "^6.0.1" +tar@^4.0.2: + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== + dependencies: + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" + +testrpc@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/testrpc/-/testrpc-0.0.1.tgz#83e2195b1f5873aec7be1af8cbe6dcf39edb7aed" + integrity sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA== + then-request@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.2.tgz#ec18dd8b5ca43aaee5cb92f7e4c1630e950d4f0c" @@ -5144,6 +7232,11 @@ then-request@^6.0.0: promise "^8.0.0" qs "^6.4.0" +timed-out@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA== + 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" @@ -5157,6 +7250,14 @@ tiny-case@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== +title-case@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa" + integrity sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q== + dependencies: + no-case "^2.2.0" + upper-case "^1.0.3" + tmp-promise@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" @@ -5195,6 +7296,14 @@ toposort@^2.0.2: resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -5249,11 +7358,23 @@ tsort@0.0.1: resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" integrity sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + tweetnacl-util@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + tweetnacl@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" @@ -5286,6 +7407,24 @@ type-fest@^2.19.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + typechain@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/typechain/-/typechain-8.3.2.tgz#1090dd8d9c57b6ef2aed3640a516bdbf01b00d73" @@ -5341,6 +7480,13 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -5380,6 +7526,11 @@ uint8arrays@^3.0.0: dependencies: multiformats "^9.4.2" +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -5390,6 +7541,11 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +underscore@^1.8.3: + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -5417,11 +7573,23 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +upper-case-first@^1.1.0, upper-case-first@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115" + integrity sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ== + dependencies: + upper-case "^1.1.1" + +upper-case@^1.0.3, upper-case@^1.1.0, upper-case@^1.1.1, upper-case@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -5429,7 +7597,19 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -utf8@3.0.0: +url-set-query@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" + integrity sha512-3AChu4NiXquPfeckE5R5cGdiHCMWJx1dwCWOmWIL4KHAziJNOFIYJlpGFeKDvwLPHovZRCxK3cYlwzqI9Vp+Gg== + +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + +utf8@3.0.0, utf8@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== @@ -5439,7 +7619,7 @@ 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: +util@^0.12.3, util@^0.12.5: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== @@ -5450,21 +7630,68 @@ util@^0.12.3: is-typed-array "^1.1.3" which-typed-array "^1.1.2" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" + integrity sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +varint@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.2.tgz#5b47f8a947eb668b848e034dcfa87d0ff8a7f7a4" + integrity sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow== + 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== +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + web-encoding@1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" @@ -5479,6 +7706,439 @@ web-streams-polyfill@^3.1.1: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz#32e26522e05128203a7de59519be3c648004343b" integrity sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ== +web3-bzz@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.10.0.tgz#ac74bc71cdf294c7080a79091079192f05c5baed" + integrity sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA== + dependencies: + "@types/node" "^12.12.6" + got "12.1.0" + swarm-js "^0.1.40" + +web3-bzz@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.10.4.tgz#dcc787970767d9004c73d11d0eeef774ce16b880" + integrity sha512-ZZ/X4sJ0Uh2teU9lAGNS8EjveEppoHNQiKlOXAjedsrdWuaMErBPdLQjXfcrYvN6WM6Su9PMsAxf3FXXZ+HwQw== + dependencies: + "@types/node" "^12.12.6" + got "12.1.0" + swarm-js "^0.1.40" + +web3-core-helpers@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.10.0.tgz#1016534c51a5df77ed4f94d1fcce31de4af37fad" + integrity sha512-pIxAzFDS5vnbXvfvLSpaA1tfRykAe9adw43YCKsEYQwH0gCLL0kMLkaCX3q+Q8EVmAh+e1jWL/nl9U0de1+++g== + dependencies: + web3-eth-iban "1.10.0" + web3-utils "1.10.0" + +web3-core-helpers@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.10.4.tgz#bd2b4140df2016d5dd3bb2b925fc29ad8678677c" + integrity sha512-r+L5ylA17JlD1vwS8rjhWr0qg7zVoVMDvWhajWA5r5+USdh91jRUYosp19Kd1m2vE034v7Dfqe1xYRoH2zvG0g== + dependencies: + web3-eth-iban "1.10.4" + web3-utils "1.10.4" + +web3-core-method@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.10.0.tgz#82668197fa086e8cc8066742e35a9d72535e3412" + integrity sha512-4R700jTLAMKDMhQ+nsVfIXvH6IGJlJzGisIfMKWAIswH31h5AZz7uDUW2YctI+HrYd+5uOAlS4OJeeT9bIpvkA== + dependencies: + "@ethersproject/transactions" "^5.6.2" + web3-core-helpers "1.10.0" + web3-core-promievent "1.10.0" + web3-core-subscriptions "1.10.0" + web3-utils "1.10.0" + +web3-core-method@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.10.4.tgz#566b52f006d3cbb13b21b72b8d2108999bf5d6bf" + integrity sha512-uZTb7flr+Xl6LaDsyTeE2L1TylokCJwTDrIVfIfnrGmnwLc6bmTWCCrm71sSrQ0hqs6vp/MKbQYIYqUN0J8WyA== + dependencies: + "@ethersproject/transactions" "^5.6.2" + web3-core-helpers "1.10.4" + web3-core-promievent "1.10.4" + web3-core-subscriptions "1.10.4" + web3-utils "1.10.4" + +web3-core-promievent@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.10.0.tgz#cbb5b3a76b888df45ed3a8d4d8d4f54ccb66a37b" + integrity sha512-68N7k5LWL5R38xRaKFrTFT2pm2jBNFaM4GioS00YjAKXRQ3KjmhijOMG3TICz6Aa5+6GDWYelDNx21YAeZ4YTg== + dependencies: + eventemitter3 "4.0.4" + +web3-core-promievent@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.10.4.tgz#629b970b7934430b03c5033c79f3bb3893027e22" + integrity sha512-2de5WnJQ72YcIhYwV/jHLc4/cWJnznuoGTJGD29ncFQHAfwW/MItHFSVKPPA5v8AhJe+r6y4Y12EKvZKjQVBvQ== + dependencies: + eventemitter3 "4.0.4" + +web3-core-requestmanager@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.10.0.tgz#4b34f6e05837e67c70ff6f6993652afc0d54c340" + integrity sha512-3z/JKE++Os62APml4dvBM+GAuId4h3L9ckUrj7ebEtS2AR0ixyQPbrBodgL91Sv7j7cQ3Y+hllaluqjguxvSaQ== + dependencies: + util "^0.12.5" + web3-core-helpers "1.10.0" + web3-providers-http "1.10.0" + web3-providers-ipc "1.10.0" + web3-providers-ws "1.10.0" + +web3-core-requestmanager@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.10.4.tgz#eb1f147e6b9df84e3a37e602162f8925bdb4bb9a" + integrity sha512-vqP6pKH8RrhT/2MoaU+DY/OsYK9h7HmEBNCdoMj+4ZwujQtw/Mq2JifjwsJ7gits7Q+HWJwx8q6WmQoVZAWugg== + dependencies: + util "^0.12.5" + web3-core-helpers "1.10.4" + web3-providers-http "1.10.4" + web3-providers-ipc "1.10.4" + web3-providers-ws "1.10.4" + +web3-core-subscriptions@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.10.0.tgz#b534592ee1611788fc0cb0b95963b9b9b6eacb7c" + integrity sha512-HGm1PbDqsxejI075gxBc5OSkwymilRWZufIy9zEpnWKNmfbuv5FfHgW1/chtJP6aP3Uq2vHkvTDl3smQBb8l+g== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.10.0" + +web3-core-subscriptions@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.10.4.tgz#2f4dcb404237e92802a563265d11a33934dc38e6" + integrity sha512-o0lSQo/N/f7/L76C0HV63+S54loXiE9fUPfHFcTtpJRQNDBVsSDdWRdePbWwR206XlsBqD5VHApck1//jEafTw== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.10.4" + +web3-core@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.10.0.tgz#9aa07c5deb478cf356c5d3b5b35afafa5fa8e633" + integrity sha512-fWySwqy2hn3TL89w5TM8wXF1Z2Q6frQTKHWmP0ppRQorEK8NcHJRfeMiv/mQlSKoTS1F6n/nv2uyZsixFycjYQ== + dependencies: + "@types/bn.js" "^5.1.1" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.10.0" + web3-core-method "1.10.0" + web3-core-requestmanager "1.10.0" + web3-utils "1.10.0" + +web3-core@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.10.4.tgz#639de68b8b9871d2dc8892e0dd4e380cb1361a98" + integrity sha512-B6elffYm81MYZDTrat7aEhnhdtVE3lDBUZft16Z8awYMZYJDbnykEbJVS+l3mnA7AQTnSDr/1MjWofGDLBJPww== + dependencies: + "@types/bn.js" "^5.1.1" + "@types/node" "^12.12.6" + bignumber.js "^9.0.0" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-core-requestmanager "1.10.4" + web3-utils "1.10.4" + +web3-eth-abi@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.10.0.tgz#53a7a2c95a571e205e27fd9e664df4919483cce1" + integrity sha512-cwS+qRBWpJ43aI9L3JS88QYPfFcSJJ3XapxOQ4j40v6mk7ATpA8CVK1vGTzpihNlOfMVRBkR95oAj7oL6aiDOg== + dependencies: + "@ethersproject/abi" "^5.6.3" + web3-utils "1.10.0" + +web3-eth-abi@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.10.4.tgz#16c19d0bde0aaf8c1a56cb7743a83156d148d798" + integrity sha512-cZ0q65eJIkd/jyOlQPDjr8X4fU6CRL1eWgdLwbWEpo++MPU/2P4PFk5ZLAdye9T5Sdp+MomePPJ/gHjLMj2VfQ== + dependencies: + "@ethersproject/abi" "^5.6.3" + web3-utils "1.10.4" + +web3-eth-accounts@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.10.0.tgz#2942beca0a4291455f32cf09de10457a19a48117" + integrity sha512-wiq39Uc3mOI8rw24wE2n15hboLE0E9BsQLdlmsL4Zua9diDS6B5abXG0XhFcoNsXIGMWXVZz4TOq3u4EdpXF/Q== + dependencies: + "@ethereumjs/common" "2.5.0" + "@ethereumjs/tx" "3.3.2" + eth-lib "0.2.8" + ethereumjs-util "^7.1.5" + scrypt-js "^3.0.1" + uuid "^9.0.0" + web3-core "1.10.0" + web3-core-helpers "1.10.0" + web3-core-method "1.10.0" + web3-utils "1.10.0" + +web3-eth-accounts@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.10.4.tgz#df30e85a7cd70e475f8cf52361befba408829e34" + integrity sha512-ysy5sVTg9snYS7tJjxVoQAH6DTOTkRGR8emEVCWNGLGiB9txj+qDvSeT0izjurS/g7D5xlMAgrEHLK1Vi6I3yg== + dependencies: + "@ethereumjs/common" "2.6.5" + "@ethereumjs/tx" "3.5.2" + "@ethereumjs/util" "^8.1.0" + eth-lib "0.2.8" + scrypt-js "^3.0.1" + uuid "^9.0.0" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-utils "1.10.4" + +web3-eth-contract@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.10.0.tgz#8e68c7654576773ec3c91903f08e49d0242c503a" + integrity sha512-MIC5FOzP/+2evDksQQ/dpcXhSqa/2hFNytdl/x61IeWxhh6vlFeSjq0YVTAyIzdjwnL7nEmZpjfI6y6/Ufhy7w== + dependencies: + "@types/bn.js" "^5.1.1" + web3-core "1.10.0" + web3-core-helpers "1.10.0" + web3-core-method "1.10.0" + web3-core-promievent "1.10.0" + web3-core-subscriptions "1.10.0" + web3-eth-abi "1.10.0" + web3-utils "1.10.0" + +web3-eth-contract@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.10.4.tgz#22d39f04e11d9ff4e726e8025a56d78e843a2c3d" + integrity sha512-Q8PfolOJ4eV9TvnTj1TGdZ4RarpSLmHnUnzVxZ/6/NiTfe4maJz99R0ISgwZkntLhLRtw0C7LRJuklzGYCNN3A== + dependencies: + "@types/bn.js" "^5.1.1" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-core-promievent "1.10.4" + web3-core-subscriptions "1.10.4" + web3-eth-abi "1.10.4" + web3-utils "1.10.4" + +web3-eth-ens@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.10.0.tgz#96a676524e0b580c87913f557a13ed810cf91cd9" + integrity sha512-3hpGgzX3qjgxNAmqdrC2YUQMTfnZbs4GeLEmy8aCWziVwogbuqQZ+Gzdfrym45eOZodk+lmXyLuAdqkNlvkc1g== + dependencies: + content-hash "^2.5.2" + eth-ens-namehash "2.0.8" + web3-core "1.10.0" + web3-core-helpers "1.10.0" + web3-core-promievent "1.10.0" + web3-eth-abi "1.10.0" + web3-eth-contract "1.10.0" + web3-utils "1.10.0" + +web3-eth-ens@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.10.4.tgz#3d991adac52bc8e598f1f1b8528337fa6291004c" + integrity sha512-LLrvxuFeVooRVZ9e5T6OWKVflHPFgrVjJ/jtisRWcmI7KN/b64+D/wJzXqgmp6CNsMQcE7rpmf4CQmJCrTdsgg== + dependencies: + content-hash "^2.5.2" + eth-ens-namehash "2.0.8" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-promievent "1.10.4" + web3-eth-abi "1.10.4" + web3-eth-contract "1.10.4" + web3-utils "1.10.4" + +web3-eth-iban@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.10.0.tgz#5a46646401965b0f09a4f58e7248c8a8cd22538a" + integrity sha512-0l+SP3IGhInw7Q20LY3IVafYEuufo4Dn75jAHT7c2aDJsIolvf2Lc6ugHkBajlwUneGfbRQs/ccYPQ9JeMUbrg== + dependencies: + bn.js "^5.2.1" + web3-utils "1.10.0" + +web3-eth-iban@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.10.4.tgz#bc61b4a1930d19b1df8762c606d669902558e54d" + integrity sha512-0gE5iNmOkmtBmbKH2aTodeompnNE8jEyvwFJ6s/AF6jkw9ky9Op9cqfzS56AYAbrqEFuClsqB/AoRves7LDELw== + dependencies: + bn.js "^5.2.1" + web3-utils "1.10.4" + +web3-eth-personal@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.10.0.tgz#94d525f7a29050a0c2a12032df150ac5ea633071" + integrity sha512-anseKn98w/d703eWq52uNuZi7GhQeVjTC5/svrBWEKob0WZ5kPdo+EZoFN0sp5a5ubbrk/E0xSl1/M5yORMtpg== + dependencies: + "@types/node" "^12.12.6" + web3-core "1.10.0" + web3-core-helpers "1.10.0" + web3-core-method "1.10.0" + web3-net "1.10.0" + web3-utils "1.10.0" + +web3-eth-personal@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.10.4.tgz#e2ee920f47e84848288e03442659cdbb2c4deea2" + integrity sha512-BRa/hs6jU1hKHz+AC/YkM71RP3f0Yci1dPk4paOic53R4ZZG4MgwKRkJhgt3/GPuPliwS46f/i5A7fEGBT4F9w== + dependencies: + "@types/node" "^12.12.6" + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-net "1.10.4" + web3-utils "1.10.4" + +web3-eth@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.10.0.tgz#38b905e2759697c9624ab080cfcf4e6c60b3a6cf" + integrity sha512-Z5vT6slNMLPKuwRyKGbqeGYC87OAy8bOblaqRTgg94CXcn/mmqU7iPIlG4506YdcdK3x6cfEDG7B6w+jRxypKA== + dependencies: + web3-core "1.10.0" + web3-core-helpers "1.10.0" + web3-core-method "1.10.0" + web3-core-subscriptions "1.10.0" + web3-eth-abi "1.10.0" + web3-eth-accounts "1.10.0" + web3-eth-contract "1.10.0" + web3-eth-ens "1.10.0" + web3-eth-iban "1.10.0" + web3-eth-personal "1.10.0" + web3-net "1.10.0" + web3-utils "1.10.0" + +web3-eth@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.10.4.tgz#3a908c635cb5d935bd30473e452f3bd7f2ee66a5" + integrity sha512-Sql2kYKmgt+T/cgvg7b9ce24uLS7xbFrxE4kuuor1zSCGrjhTJ5rRNG8gTJUkAJGKJc7KgnWmgW+cOfMBPUDSA== + dependencies: + web3-core "1.10.4" + web3-core-helpers "1.10.4" + web3-core-method "1.10.4" + web3-core-subscriptions "1.10.4" + web3-eth-abi "1.10.4" + web3-eth-accounts "1.10.4" + web3-eth-contract "1.10.4" + web3-eth-ens "1.10.4" + web3-eth-iban "1.10.4" + web3-eth-personal "1.10.4" + web3-net "1.10.4" + web3-utils "1.10.4" + +web3-net@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.10.0.tgz#be53e7f5dafd55e7c9013d49c505448b92c9c97b" + integrity sha512-NLH/N3IshYWASpxk4/18Ge6n60GEvWBVeM8inx2dmZJVmRI6SJIlUxbL8jySgiTn3MMZlhbdvrGo8fpUW7a1GA== + dependencies: + web3-core "1.10.0" + web3-core-method "1.10.0" + web3-utils "1.10.0" + +web3-net@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.10.4.tgz#20e12c60e4477d4298979d8d5d66b9abf8e66a09" + integrity sha512-mKINnhOOnZ4koA+yV2OT5s5ztVjIx7IY9a03w6s+yao/BUn+Luuty0/keNemZxTr1E8Ehvtn28vbOtW7Ids+Ow== + dependencies: + web3-core "1.10.4" + web3-core-method "1.10.4" + web3-utils "1.10.4" + +web3-providers-http@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.10.0.tgz#864fa48675e7918c9a4374e5f664b32c09d0151b" + integrity sha512-eNr965YB8a9mLiNrkjAWNAPXgmQWfpBfkkn7tpEFlghfww0u3I0tktMZiaToJVcL2+Xq+81cxbkpeWJ5XQDwOA== + dependencies: + abortcontroller-polyfill "^1.7.3" + cross-fetch "^3.1.4" + es6-promise "^4.2.8" + web3-core-helpers "1.10.0" + +web3-providers-http@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.10.4.tgz#ca7aa58aeaf8123500c24ffe0595896319f830e8" + integrity sha512-m2P5Idc8hdiO0l60O6DSCPw0kw64Zgi0pMjbEFRmxKIck2Py57RQMu4bxvkxJwkF06SlGaEQF8rFZBmuX7aagQ== + dependencies: + abortcontroller-polyfill "^1.7.5" + cross-fetch "^4.0.0" + es6-promise "^4.2.8" + web3-core-helpers "1.10.4" + +web3-providers-ipc@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.10.0.tgz#9747c7a6aee96a51488e32fa7c636c3460b39889" + integrity sha512-OfXG1aWN8L1OUqppshzq8YISkWrYHaATW9H8eh0p89TlWMc1KZOL9vttBuaBEi96D/n0eYDn2trzt22bqHWfXA== + dependencies: + oboe "2.1.5" + web3-core-helpers "1.10.0" + +web3-providers-ipc@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.10.4.tgz#2e03437909e4e7771d646ff05518efae44b783c3" + integrity sha512-YRF/bpQk9z3WwjT+A6FI/GmWRCASgd+gC0si7f9zbBWLXjwzYAKG73bQBaFRAHex1hl4CVcM5WUMaQXf3Opeuw== + dependencies: + oboe "2.1.5" + web3-core-helpers "1.10.4" + +web3-providers-ws@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.10.0.tgz#cb0b87b94c4df965cdf486af3a8cd26daf3975e5" + integrity sha512-sK0fNcglW36yD5xjnjtSGBnEtf59cbw4vZzJ+CmOWIKGIR96mP5l684g0WD0Eo+f4NQc2anWWXG74lRc9OVMCQ== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.10.0" + websocket "^1.0.32" + +web3-providers-ws@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.10.4.tgz#55d0c3ba36c6a79d105f02e20a707eb3978e7f82" + integrity sha512-j3FBMifyuFFmUIPVQR4pj+t5ILhAexAui0opgcpu9R5LxQrLRUZxHSnU+YO25UycSOa/NAX8A+qkqZNpcFAlxA== + dependencies: + eventemitter3 "4.0.4" + web3-core-helpers "1.10.4" + websocket "^1.0.32" + +web3-shh@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.10.0.tgz#c2979b87e0f67a7fef2ce9ee853bd7bfbe9b79a8" + integrity sha512-uNUUuNsO2AjX41GJARV9zJibs11eq6HtOe6Wr0FtRUcj8SN6nHeYIzwstAvJ4fXA53gRqFMTxdntHEt9aXVjpg== + dependencies: + web3-core "1.10.0" + web3-core-method "1.10.0" + web3-core-subscriptions "1.10.0" + web3-net "1.10.0" + +web3-shh@1.10.4: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.10.4.tgz#9852d6f3d05678e31e49235a60fea10ca7a9e21d" + integrity sha512-cOH6iFFM71lCNwSQrC3niqDXagMqrdfFW85hC9PFUrAr3PUrIem8TNstTc3xna2bwZeWG6OBy99xSIhBvyIACw== + dependencies: + web3-core "1.10.4" + web3-core-method "1.10.4" + web3-core-subscriptions "1.10.4" + web3-net "1.10.4" + +web3-utils@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.0.tgz#ca4c1b431a765c14ac7f773e92e0fd9377ccf578" + integrity sha512-kSaCM0uMcZTNUSmn5vMEhlo02RObGNRRCkdX0V9UTAU0+lrvn0HSaudyCo6CQzuXUsnuY2ERJGCGPfeWmv19Rg== + dependencies: + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereumjs-util "^7.1.0" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + +web3-utils@1.10.4, web3-utils@^1.0.0-beta.31: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec" + integrity sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A== + dependencies: + "@ethereumjs/util" "^8.1.0" + bn.js "^5.2.1" + ethereum-bloom-filters "^1.0.6" + ethereum-cryptography "^2.1.2" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + utf8 "3.0.0" + web3-utils@^1.3.6: version "1.10.3" resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.3.tgz#f1db99c82549c7d9f8348f04ffe4e0188b449714" @@ -5493,11 +8153,49 @@ web3-utils@^1.3.6: randombytes "^2.1.0" utf8 "3.0.0" +web3@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.10.0.tgz#2fde0009f59aa756c93e07ea2a7f3ab971091274" + integrity sha512-YfKY9wSkGcM8seO+daR89oVTcbu18NsVfvOngzqMYGUU0pPSQmE57qQDvQzUeoIOHAnXEBNzrhjQJmm8ER0rng== + dependencies: + web3-bzz "1.10.0" + web3-core "1.10.0" + web3-eth "1.10.0" + web3-eth-personal "1.10.0" + web3-net "1.10.0" + web3-shh "1.10.0" + web3-utils "1.10.0" + +web3@^1.0.0-beta.34: + version "1.10.4" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.10.4.tgz#5d5e59b976eaf758b060fe1a296da5fe87bdc79c" + integrity sha512-kgJvQZjkmjOEKimx/tJQsqWfRDPTTcBfYPa9XletxuHLpHcXdx67w8EFn5AW3eVxCutE9dTVHgGa9VYe8vgsEA== + dependencies: + web3-bzz "1.10.4" + web3-core "1.10.4" + web3-eth "1.10.4" + web3-eth-personal "1.10.4" + web3-net "1.10.4" + web3-shh "1.10.4" + web3-utils "1.10.4" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +websocket@^1.0.32: + version "1.0.34" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" + integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -5517,6 +8215,11 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ== + which-typed-array@^1.1.11, which-typed-array@^1.1.13, 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" @@ -5542,6 +8245,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +window-size@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" + integrity sha512-UD7d8HFA2+PZsbKyaOCEy8gMh1oDtHgJh1LfgjQ4zVXmYjAT/kvz3PueITKuqDiIXQe7yzpPnxX3lNc+AhQMyw== + word-wrap@~1.2.3: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -5574,6 +8282,14 @@ workerpool@6.2.1: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw== + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -5593,17 +8309,76 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@^3.0.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" + integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" + ws@^7.4.6: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +xhr-request-promise@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz#2d5f4b16d8c6c893be97f1a62b0ed4cf3ca5f96c" + integrity sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg== + dependencies: + xhr-request "^1.1.0" + +xhr-request@^1.0.1, xhr-request@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" + integrity sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA== + dependencies: + buffer-to-arraybuffer "^0.0.5" + object-assign "^4.1.1" + query-string "^5.0.1" + simple-get "^2.7.0" + timed-out "^4.0.1" + url-set-query "^1.0.0" + xhr "^2.0.4" + +xhr@^2.0.4, xhr@^2.3.3: + version "2.6.0" + resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" + integrity sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA== + dependencies: + global "~4.4.0" + is-function "^1.0.1" + parse-headers "^2.0.0" + xtend "^4.0.0" + +xmlhttprequest@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + integrity sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^3.0.2: +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== + +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== @@ -5618,6 +8393,14 @@ yargs-parser@20.2.4: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yargs-parser@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" + integrity sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA== + dependencies: + camelcase "^3.0.0" + lodash.assign "^4.0.6" + yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" @@ -5646,6 +8429,26 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^4.7.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" + integrity sha512-LqodLrnIDM3IFT+Hf/5sxBnEGECrfdC1uIbgZeJmESCSo4HoCAaKEus8MylXHAkdacGc0ye+Qa+dpkuom8uVYA== + dependencies: + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + lodash.assign "^4.0.3" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.1" + which-module "^1.0.0" + window-size "^0.2.0" + y18n "^3.2.1" + yargs-parser "^2.4.1" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From c456d0779dda9abf2761fd94518456e3b5544f57 Mon Sep 17 00:00:00 2001 From: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> Date: Thu, 7 Mar 2024 17:36:05 +0100 Subject: [PATCH 05/17] refactor: tests (#2) * refactor: plugin tests * refactor: separated upgradeability tests * refactor: remaining tests * refactor: remove storage.ts * refactor: improved tests and comments * doc: more comment improvements * fix: wrong variable name in test * fix: linter error * refactor: use typechain struct * fix: wrong versioning numbers * test: added missing test * doc: remove todo --- packages/contracts/hardhat.config.ts | 2 +- packages/contracts/src/mocks/Migration.sol | 4 +- .../test/10_unit-testing/11_plugin.ts | 2544 +++++++++++------ .../test/10_unit-testing/12_plugin-setup.ts | 609 ++-- .../22_setup-processing.ts | 213 +- .../20_integration-testing/test-helpers.ts | 120 +- .../31_upgradeability.ts | 135 + packages/contracts/test/multisig-constants.ts | 5 - .../test/test-utils/protocol-version.ts | 11 - packages/contracts/test/test-utils/storage.ts | 18 - .../test/test-utils/typechain-versions.ts | 4 +- .../test/test-utils/uups-upgradeable.ts | 44 +- 12 files changed, 2366 insertions(+), 1343 deletions(-) create mode 100644 packages/contracts/test/30_regression-testing/31_upgradeability.ts delete mode 100644 packages/contracts/test/test-utils/protocol-version.ts delete mode 100644 packages/contracts/test/test-utils/storage.ts diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 28e7f77a..749b7d34 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -105,7 +105,7 @@ const config: HardhatUserConfig = { throwOnTransactionFailures: true, throwOnCallFailures: true, blockGasLimit: BigNumber.from(10).pow(6).mul(300).toNumber(), // 300 million, really high to test some things that are only possible with a higher block gas limit - gasPrice: BigNumber.from(10).pow(9).mul(150).toNumber(), // 150 gwei + gasPrice: BigNumber.from(10).pow(9).mul(300).toNumber(), // 300 gwei accounts: getHardhatNetworkAccountsConfig( Object.keys(namedAccounts).length ), diff --git a/packages/contracts/src/mocks/Migration.sol b/packages/contracts/src/mocks/Migration.sol index 937651a2..faac7b6f 100644 --- a/packages/contracts/src/mocks/Migration.sol +++ b/packages/contracts/src/mocks/Migration.sol @@ -18,8 +18,8 @@ pragma solidity ^0.8.8; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // Regression Testing -import {Multisig as Multisig_v1_0_0} from "@aragon/osx-v1.0.0/plugins/governance/multisig/Multisig.sol"; -import {Multisig as Multisig_v1_3_0} from "@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig.sol"; +import {Multisig as Multisig_v1_1} from "@aragon/osx-v1.0.0/plugins/governance/multisig/Multisig.sol"; +import {Multisig as Multisig_v1_2} from "@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig.sol"; import {ProxyFactory} from "@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory.sol"; diff --git a/packages/contracts/test/10_unit-testing/11_plugin.ts b/packages/contracts/test/10_unit-testing/11_plugin.ts index 8bbdd861..037dbb5a 100644 --- a/packages/contracts/test/10_unit-testing/11_plugin.ts +++ b/packages/contracts/test/10_unit-testing/11_plugin.ts @@ -19,643 +19,905 @@ import { import { MULTISIG_EVENTS, MULTISIG_INTERFACE, - MultisigSettings, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, } from '../multisig-constants'; -import { - Multisig_V1_0_0__factory, - Multisig_V1_3_0__factory, - Multisig__factory, - Multisig, -} from '../test-utils/typechain-versions'; -import { - deployAndUpgradeFromToCheck, - deployAndUpgradeSelfCheck, - getProtocolVersion, -} from '../test-utils/uups-upgradeable'; +import {Multisig__factory, Multisig} from '../test-utils/typechain-versions'; import { getInterfaceId, proposalIdToBytes32, IDAO_EVENTS, - PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, IMEMBERSHIP_EVENTS, IPROPOSAL_EVENTS, findEvent, findEventTopicLog, TIME, + DAO_PERMISSIONS, } from '@aragon/osx-commons-sdk'; -import {DAO, DAO__factory} from '@aragon/osx-ethers'; -import {time} from '@nomicfoundation/hardhat-network-helpers'; +import {DAO, DAOStructs, DAO__factory} from '@aragon/osx-ethers'; +import {loadFixture, time} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; -import {BigNumber, Contract, ContractFactory} from 'ethers'; +import {BigNumber} from 'ethers'; import {ethers} from 'hardhat'; -export async function approveWithSigners( - multisigContract: Contract, - proposalId: number, - signers: SignerWithAddress[], - signerIds: number[] -) { - const promises = signerIds.map(i => - multisigContract.connect(signers[i]).approve(proposalId, false) +type FixtureResult = { + deployer: SignerWithAddress; + alice: SignerWithAddress; + bob: SignerWithAddress; + carol: SignerWithAddress; + dave: SignerWithAddress; + eve: SignerWithAddress; + initializedPlugin: Multisig; + uninitializedPlugin: Multisig; + defaultInitData: { + members: string[]; + settings: Multisig.MultisigSettingsStruct; + }; + dao: DAO; + dummyActions: DAOStructs.ActionStruct[]; + dummyMetadata: string; +}; + +async function fixture(): Promise { + const [deployer, alice, bob, carol, dave, eve] = await ethers.getSigners(); + + // Deploy a DAO proxy. + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') ); + const dao = await createDaoProxy(deployer, dummyMetadata); - await Promise.all(promises); -} - -describe('Multisig', function () { - let signers: SignerWithAddress[]; - let multisig: Multisig; - let dao: DAO; - let dummyActions: any; - let dummyMetadata: string; - let startDate: number; - let endDate: number; - let multisigSettings: MultisigSettings; - - const id = 0; - - before(async () => { - signers = await ethers.getSigners(); - dummyActions = [ - { - to: signers[0].address, - data: '0x00000000', - value: 0, - }, - ]; - dummyMetadata = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('0x123456789') - ); + // Deploy a plugin proxy factory containing the multisig implementation. + const pluginImplementation = await new Multisig__factory(deployer).deploy(); + const proxyFactory = await new ProxyFactory__factory(deployer).deploy( + pluginImplementation.address + ); - dao = await createDaoProxy(signers[0], dummyMetadata); - }); + // Deploy an initialized plugin proxy. + const defaultInitData = { + members: [alice.address, bob.address, carol.address], + settings: { + onlyListed: true, + minApprovals: 2, + }, + }; + const pluginInitdata = pluginImplementation.interface.encodeFunctionData( + 'initialize', + [dao.address, defaultInitData.members, defaultInitData.settings] + ); + const deploymentTx1 = await proxyFactory.deployUUPSProxy(pluginInitdata); + const proxyCreatedEvent1 = await findEvent( + deploymentTx1, + proxyFactory.interface.getEvent('ProxyCreated').name + ); + const initializedPlugin = Multisig__factory.connect( + proxyCreatedEvent1.args.proxy, + deployer + ); - beforeEach(async function () { - startDate = (await time.latest()) + 1000; - endDate = startDate + 1000; + // Deploy an initialized plugin proxy. + const deploymentTx2 = await proxyFactory.deployUUPSProxy([]); + const proxyCreatedEvent2 = await findEvent( + deploymentTx2, + proxyFactory.interface.getEvent('ProxyCreated').name + ); + const uninitializedPlugin = Multisig__factory.connect( + proxyCreatedEvent2.args.proxy, + deployer + ); - multisigSettings = { - minApprovals: 3, - onlyListed: true, - }; - - const multisigImplementation = await new Multisig__factory( - signers[0] - ).deploy(); - const multisigProxyFactory = await new ProxyFactory__factory( - signers[0] - ).deploy(multisigImplementation.address); - - const tx = await multisigProxyFactory.deployUUPSProxy([]); - const event = await findEvent( - tx, - multisigProxyFactory.interface.getEvent('ProxyCreated').name - ); - multisig = Multisig__factory.connect(event.args.proxy, signers[0]); - - await dao.grant( - dao.address, - multisig.address, - ethers.utils.id('EXECUTE_PERMISSION') - ); - await dao.grant( - multisig.address, - signers[0].address, - ethers.utils.id('UPDATE_MULTISIG_SETTINGS_PERMISSION') - ); - }); + // Provide a dummy action array. + const dummyActions: DAOStructs.ActionStruct[] = [ + { + to: deployer.address, + data: '0x1234', + value: 0, + }, + ]; + + return { + deployer, + alice, + bob, + carol, + dave, + eve, + initializedPlugin, + uninitializedPlugin, + defaultInitData, + dao, + dummyActions, + dummyMetadata, + }; +} +describe('Multisig', function () { describe('initialize', async () => { it('reverts if trying to re-initialize', async () => { - await multisig.initialize( - dao.address, - signers.slice(0, 5).map(s => s.address), - multisigSettings + const {dao, initializedPlugin, defaultInitData} = await loadFixture( + fixture ); + // Try to reinitialize the initialized plugin. await expect( - multisig.initialize( + initializedPlugin.initialize( dao.address, - signers.slice(0, 5).map(s => s.address), - multisigSettings + defaultInitData.members, + defaultInitData.settings ) ).to.be.revertedWith('Initializable: contract is already initialized'); }); it('adds the initial addresses to the address list', async () => { - expect(await multisig.addresslistLength()).to.equal(0); + const { + dao, + uninitializedPlugin: plugin, + defaultInitData, + } = await loadFixture(fixture); + + // Check that the uninitialized plugin has no members. + expect(await plugin.addresslistLength()).to.equal(0); - multisigSettings.minApprovals = 2; - await multisig.initialize( + // Initialize the plugin. + await plugin.initialize( dao.address, - signers.slice(0, 2).map(s => s.address), - multisigSettings + defaultInitData.members, + defaultInitData.settings ); - expect(await multisig.addresslistLength()).to.equal(2); - expect(await multisig.isListed(signers[0].address)).to.equal(true); - expect(await multisig.isListed(signers[1].address)).to.equal(true); - }); - - it('should set the `minApprovals`', async () => { - await multisig.initialize( - dao.address, - signers.slice(0, 5).map(s => s.address), - multisigSettings + // Check that all members from the init data have been listed as members. + expect(await plugin.addresslistLength()).to.equal( + defaultInitData.members.length ); - expect((await multisig.multisigSettings()).minApprovals).to.be.eq( - multisigSettings.minApprovals + const promises = defaultInitData.members.map(member => + plugin.isListed(member) ); + (await Promise.all(promises)).forEach(isListedResult => { + expect(isListedResult).to.be.true; + }); }); - it('should set `onlyListed`', async () => { - await multisig.initialize( - dao.address, - signers.slice(0, 5).map(s => s.address), - multisigSettings - ); - expect((await multisig.multisigSettings()).onlyListed).to.be.eq( - multisigSettings.onlyListed + it('sets the `minApprovals`', async () => { + const {initializedPlugin, defaultInitData} = await loadFixture(fixture); + expect( + (await initializedPlugin.multisigSettings()).minApprovals + ).to.be.eq(defaultInitData.settings.minApprovals); + }); + + it('sets `onlyListed`', async () => { + const {initializedPlugin, defaultInitData} = await loadFixture(fixture); + expect((await initializedPlugin.multisigSettings()).onlyListed).to.be.eq( + defaultInitData.settings.onlyListed ); }); - it('should emit `MultisigSettingsUpdated` during initialization', async () => { + it('emits `MultisigSettingsUpdated` during initialization', async () => { + const {uninitializedPlugin, defaultInitData, dao} = await loadFixture( + fixture + ); await expect( - multisig.initialize( + uninitializedPlugin.initialize( dao.address, - signers.slice(0, 5).map(s => s.address), - multisigSettings + defaultInitData.members, + defaultInitData.settings ) ) - .to.emit(multisig, MULTISIG_EVENTS.MultisigSettingsUpdated) - .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); - }); - - it('should revert if members list is longer than uint16 max', async () => { - const members = new Array(65536).fill(signers[1].address); - await expect( - multisig.initialize(dao.address, members, multisigSettings, { - gasLimit: BigNumber.from(10).pow(8).toNumber(), - }) - ) - .to.revertedWithCustomError(multisig, 'AddresslistLengthOutOfBounds') - .withArgs(65535, members.length); - }); - }); - - describe('Upgrades', () => { - let legacyContractFactory: ContractFactory; - let currentContractFactory: ContractFactory; - let initArgs: any; - - before(() => { - currentContractFactory = new Multisig__factory(signers[0]); - }); - - beforeEach(() => { - initArgs = { - dao: dao.address, - members: [signers[0].address, signers[1].address, signers[2].address], - multisigSettings: multisigSettings, - }; - }); - - it('upgrades to a new implementation', async () => { - await deployAndUpgradeSelfCheck( - signers[0], - signers[1], - initArgs, - 'initialize', - currentContractFactory, - PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, - dao - ); - }); - - it('upgrades from v1.0.0', async () => { - legacyContractFactory = new Multisig_V1_0_0__factory(signers[0]); - - const {fromImplementation, toImplementation} = - await deployAndUpgradeFromToCheck( - signers[0], - signers[1], - initArgs, - 'initialize', - legacyContractFactory, - currentContractFactory, - PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, - dao + .to.emit(uninitializedPlugin, MULTISIG_EVENTS.MultisigSettingsUpdated) + .withArgs( + defaultInitData.settings.onlyListed, + defaultInitData.settings.minApprovals ); - expect(toImplementation).to.not.equal(fromImplementation); // The build did change - - const fromProtocolVersion = await getProtocolVersion( - legacyContractFactory.attach(fromImplementation) - ); - const toProtocolVersion = await getProtocolVersion( - currentContractFactory.attach(toImplementation) - ); - - expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); - expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); - expect(toProtocolVersion).to.deep.equal([1, 4, 0]); // TODO Check this automatically }); - it('from v1.3.0', async () => { - legacyContractFactory = new Multisig_V1_3_0__factory(signers[0]); - - const {fromImplementation, toImplementation} = - await deployAndUpgradeFromToCheck( - signers[0], - signers[1], - initArgs, - 'initialize', - legacyContractFactory, - currentContractFactory, - PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, - dao - ); - expect(toImplementation).to.not.equal(fromImplementation); + it('reverts if the member list is longer than uint16 max', async () => { + const {uninitializedPlugin, alice, defaultInitData, dao} = + await loadFixture(fixture); - const fromProtocolVersion = await getProtocolVersion( - legacyContractFactory.attach(fromImplementation) - ); - const toProtocolVersion = await getProtocolVersion( - currentContractFactory.attach(toImplementation) + // Create a member list causing an overflow during initialization. + const uint16MaxValue = 2 ** 16 - 1; // = 65535 + const overflowingMemberList = new Array(uint16MaxValue + 1).fill( + alice.address ); - expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); - expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); - expect(toProtocolVersion).to.deep.equal([1, 4, 0]); // TODO Check this automatically + // Try to initialize the plugin with a list of new members causing an overflow. + await expect( + uninitializedPlugin.initialize( + dao.address, + overflowingMemberList, + defaultInitData.settings, + { + gasLimit: BigNumber.from(10).pow(8).toNumber(), + } + ) + ) + .to.revertedWithCustomError( + uninitializedPlugin, + 'AddresslistLengthOutOfBounds' + ) + .withArgs(uint16MaxValue, overflowingMemberList.length); }); }); describe('ERC-165', async () => { it('does not support the empty interface', async () => { - expect(await multisig.supportsInterface('0xffffffff')).to.be.false; + const {initializedPlugin: plugin} = await loadFixture(fixture); + expect(await plugin.supportsInterface('0xffffffff')).to.be.false; }); it('supports the `IERC165Upgradeable` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const iface = IERC165Upgradeable__factory.createInterface(); - expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be - .true; + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; }); it('supports the `IPlugin` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const iface = IPlugin__factory.createInterface(); - expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be - .true; + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; }); it('supports the `IProtocolVersion` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const iface = IProtocolVersion__factory.createInterface(); - expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be - .true; + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; }); it('supports the `IProposal` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const iface = IProposal__factory.createInterface(); - expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be - .true; + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; }); it('supports the `IMembership` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const iface = IMembership__factory.createInterface(); - expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be - .true; + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; }); it('supports the `Addresslist` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const iface = Addresslist__factory.createInterface(); - expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be - .true; + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; }); it('supports the `IMultisig` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const iface = IMultisig__factory.createInterface(); - expect(await multisig.supportsInterface(getInterfaceId(iface))).to.be - .true; + expect(await plugin.supportsInterface(getInterfaceId(iface))).to.be.true; }); it('supports the `Multisig` interface', async () => { + const {initializedPlugin: plugin} = await loadFixture(fixture); const interfaceId = getInterfaceId(MULTISIG_INTERFACE); - expect(await multisig.supportsInterface(interfaceId)).to.be.true; + expect(await plugin.supportsInterface(interfaceId)).to.be.true; }); }); describe('updateMultisigSettings', async () => { - beforeEach(async () => { - await multisig.initialize( - dao.address, - signers.slice(0, 5).map(s => s.address), - multisigSettings - ); + it('reverts if the caller misses the `UPDATE_MULTISIG_SETTINGS_PERMISSION` permission', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Check that Alice hasn't `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission on the Multisig plugin. + expect( + await dao.hasPermission( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, + [] + ) + ).to.be.false; + + // Expect Alice's `updateMultisigSettings` call to be reverted because she hasn't `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` + // permission on the Multisig plugin. + const newSettings: Multisig.MultisigSettingsStruct = { + onlyListed: false, + minApprovals: 1, + }; + await expect(plugin.connect(alice).updateMultisigSettings(newSettings)) + .to.be.revertedWithCustomError(plugin, 'DaoUnauthorized') + .withArgs( + dao.address, + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); }); - it('should not allow to set minApprovals larger than the address list length', async () => { - const addresslistLength = (await multisig.addresslistLength()).toNumber(); + it('reverts when setting `minApprovals` to a value greater than the address list length', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); - multisigSettings.minApprovals = addresslistLength + 1; + // Create settings where `minApprovals` is greater than the address list length + const addresslistLength = (await plugin.addresslistLength()).toNumber(); + const badSettings: Multisig.MultisigSettingsStruct = { + onlyListed: true, + minApprovals: addresslistLength + 1, + }; - await expect(multisig.updateMultisigSettings(multisigSettings)) - .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') - .withArgs(addresslistLength, multisigSettings.minApprovals); + // Try to update the multisig settings + await expect(plugin.connect(alice).updateMultisigSettings(badSettings)) + .to.be.revertedWithCustomError(plugin, 'MinApprovalsOutOfBounds') + .withArgs(addresslistLength, badSettings.minApprovals); }); - it('should not allow to set `minApprovals` to 0', async () => { - multisigSettings.minApprovals = 0; - await expect(multisig.updateMultisigSettings(multisigSettings)) - .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + it('reverts when setting `minApprovals` to 0', async () => { + const { + alice, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); + + // Try as Alice to update the settings with `minApprovals` being 0. + const badSettings: Multisig.MultisigSettingsStruct = { + onlyListed: true, + minApprovals: 0, + }; + await expect(plugin.connect(alice).updateMultisigSettings(badSettings)) + .to.be.revertedWithCustomError(plugin, 'MinApprovalsOutOfBounds') .withArgs(1, 0); }); - it('should emit `MultisigSettingsUpdated` when `updateMutlsigSettings` gets called', async () => { - await expect(multisig.updateMultisigSettings(multisigSettings)) - .to.emit(multisig, MULTISIG_EVENTS.MultisigSettingsUpdated) - .withArgs(multisigSettings.onlyListed, multisigSettings.minApprovals); + it('emits `MultisigSettingsUpdated` when `updateMultisigSettings` gets called', async () => { + const { + alice, + initializedPlugin: plugin, + defaultInitData, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); + + // Update the settings as Alice. + await expect( + plugin.connect(alice).updateMultisigSettings(defaultInitData.settings) + ) + .to.emit(plugin, MULTISIG_EVENTS.MultisigSettingsUpdated) + .withArgs( + defaultInitData.settings.onlyListed, + defaultInitData.settings.minApprovals + ); }); }); describe('isListed', async () => { - it('should return false, if a user is not listed', async () => { - multisigSettings.minApprovals = 1; - await multisig.initialize( - dao.address, - [signers[0].address], - multisigSettings - ); + it('returns false, if a user is not listed', async () => { + const {dave, initializedPlugin: plugin} = await loadFixture(fixture); + expect(await plugin.isListed(dave.address)).to.equal(false); + }); - expect(await multisig.isListed(signers[9].address)).to.equal(false); + it('returns true, if a user is listed', async () => { + const {alice, initializedPlugin: plugin} = await loadFixture(fixture); + expect(await plugin.isListed(alice.address)).to.equal(true); }); }); describe('isMember', async () => { - it('should return false, if user is not listed', async () => { - expect(await multisig.isMember(signers[0].address)).to.be.false; + it('returns false, if user is not a member', async () => { + const {dave, initializedPlugin: plugin} = await loadFixture(fixture); + expect(await plugin.isMember(dave.address)).to.be.false; }); - it('should return true if user is in the latest list', async () => { - multisigSettings.minApprovals = 1; - await multisig.initialize( - dao.address, - [signers[0].address], - multisigSettings - ); - expect(await multisig.isMember(signers[0].address)).to.be.true; + it('returns true if user a user is a member', async () => { + const {alice, initializedPlugin: plugin} = await loadFixture(fixture); + expect(await plugin.isMember(alice.address)).to.be.true; }); }); describe('addAddresses', async () => { - it('should add new members to the address list and emit the `MembersAdded` event', async () => { - multisigSettings.minApprovals = 1; - await multisig.initialize( - dao.address, - [signers[0].address], - multisigSettings + it('reverts if the caller misses the `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission', async () => { + const { + alice, + dave, + eve, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Check that the Alice hasn't `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission on the Multisig plugin. + expect( + await dao.hasPermission( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, + [] + ) + ).to.be.false; + + // Expect Alice's `addAddresses` call to be reverted because she hasn't `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` + // permission on the Multisig plugin. + await expect( + plugin.connect(alice).addAddresses([dave.address, eve.address]) + ) + .to.be.revertedWithCustomError(plugin, 'DaoUnauthorized') + .withArgs( + dao.address, + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); + }); + + it('reverts if the member list would become longer than uint16 max', async () => { + const { + initializedPlugin: plugin, + alice, + dave, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); + + const currentMemberCount = ( + await plugin.callStatic.addresslistLength() + ).toNumber(); + + // Create list of new members causing an overflow. + const uint16MaxValue = 2 ** 16 - 1; // = 65535 + const overflowingNewMemberList = new Array( + uint16MaxValue - currentMemberCount + 1 + ).fill(dave.address); + + // Try to add a list of new members causing an overflow as Alice. + await expect(plugin.connect(alice).addAddresses(overflowingNewMemberList)) + .to.revertedWithCustomError(plugin, 'AddresslistLengthOutOfBounds') + .withArgs(uint16MaxValue, uint16MaxValue + 1); + }); + + it('adds new members to the address list and emit the `MembersAdded` event', async () => { + const { + alice, + dave, + eve, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID ); - expect(await multisig.isListed(signers[0].address)).to.equal(true); - expect(await multisig.isListed(signers[1].address)).to.equal(false); + // Check that Dave and Eve are not listed yet. + expect(await plugin.isListed(dave.address)).to.equal(false); + expect(await plugin.isListed(eve.address)).to.equal(false); - // add a new member - await expect(multisig.addAddresses([signers[1].address])) - .to.emit(multisig, IMEMBERSHIP_EVENTS.MembersAdded) - .withArgs([signers[1].address]); + // Call `addAddresses` as Alice to add Dave and Eve. + await expect( + plugin.connect(alice).addAddresses([dave.address, eve.address]) + ) + .to.emit(plugin, IMEMBERSHIP_EVENTS.MembersAdded) + .withArgs([dave.address, eve.address]); - expect(await multisig.isListed(signers[0].address)).to.equal(true); - expect(await multisig.isListed(signers[1].address)).to.equal(true); + // Check that Dave and Eve are listed now. + expect(await plugin.isListed(dave.address)).to.equal(true); + expect(await plugin.isListed(eve.address)).to.equal(true); }); }); describe('removeAddresses', async () => { - it('should remove users from the address list and emit the `MembersRemoved` event', async () => { - multisigSettings.minApprovals = 1; - await multisig.initialize( - dao.address, - signers.slice(0, 2).map(s => s.address), - multisigSettings + it('reverts if the caller misses the `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission', async () => { + const { + alice, + bob, + carol, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Check that Alice hasn't `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` permission on the Multisig plugin. + expect( + await dao.hasPermission( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, + [] + ) + ).to.be.false; + + // Expect Alice's `removeAddresses` call to be reverted because she hasn't `UPDATE_MULTISIG_SETTINGS_PERMISSION_ID` + // permission on the Multisig plugin. + await expect( + plugin.connect(alice).removeAddresses([bob.address, carol.address]) + ) + .to.be.revertedWithCustomError(plugin, 'DaoUnauthorized') + .withArgs( + dao.address, + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); + }); + + it('removes users from the address list and emit the `MembersRemoved` event', async () => { + const { + alice, + bob, + carol, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID ); - expect(await multisig.isListed(signers[0].address)).to.equal(true); - expect(await multisig.isListed(signers[1].address)).to.equal(true); + // Check that Alice, Bob, and Carol are listed. + expect(await plugin.isListed(alice.address)).to.equal(true); + expect(await plugin.isListed(bob.address)).to.equal(true); + expect(await plugin.isListed(carol.address)).to.equal(true); - // remove an existing member - await expect(multisig.removeAddresses([signers[1].address])) - .to.emit(multisig, IMEMBERSHIP_EVENTS.MembersRemoved) - .withArgs([signers[1].address]); + // Call `removeAddresses` as Alice to remove Bob. + await expect(plugin.connect(alice).removeAddresses([bob.address])) + .to.emit(plugin, IMEMBERSHIP_EVENTS.MembersRemoved) + .withArgs([bob.address]); - expect(await multisig.isListed(signers[0].address)).to.equal(true); - expect(await multisig.isListed(signers[1].address)).to.equal(false); + // Check that Bob is removed while Alice and Carol remains listed. + expect(await plugin.isListed(alice.address)).to.equal(true); + expect(await plugin.isListed(bob.address)).to.equal(false); + expect(await plugin.isListed(carol.address)).to.equal(true); }); it('reverts if the address list would become empty', async () => { - multisigSettings.minApprovals = 1; - await multisig.initialize( - dao.address, - [signers[0].address], - multisigSettings + const { + alice, + initializedPlugin: plugin, + defaultInitData, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID ); - await expect(multisig.removeAddresses([signers[0].address])) - .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') - .withArgs( - (await multisig.addresslistLength()).sub(1), - multisigSettings.minApprovals - ); + // Try to remove all members. + await expect( + plugin.connect(alice).removeAddresses(defaultInitData.members) + ) + .to.be.revertedWithCustomError(plugin, 'MinApprovalsOutOfBounds') + .withArgs(0, defaultInitData.settings.minApprovals); }); it('reverts if the address list would become shorter than the current minimum approval parameter requires', async () => { - multisigSettings.minApprovals = 2; - await multisig.initialize( - dao.address, - signers.slice(0, 3).map(s => s.address), - multisigSettings + const { + alice, + carol, + initializedPlugin: plugin, + defaultInitData, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID ); - await expect(multisig.removeAddresses([signers[1].address])).not.to.be - .reverted; + // Initially, there are 3 members and `minApprovals` is 2. + // Remove one member, which is ok. + await expect(plugin.connect(alice).removeAddresses([carol.address])).not + .to.be.reverted; - await expect(multisig.removeAddresses([signers[2].address])) - .to.be.revertedWithCustomError(multisig, 'MinApprovalsOutOfBounds') + // Try to remove another member, which will revert. + await expect(plugin.connect(alice).removeAddresses([alice.address])) + .to.be.revertedWithCustomError(plugin, 'MinApprovalsOutOfBounds') .withArgs( - (await multisig.addresslistLength()).sub(1), - multisigSettings.minApprovals + (await plugin.addresslistLength()).sub(1), + defaultInitData.settings.minApprovals ); }); }); describe('createProposal', async () => { - beforeEach(async () => { - multisigSettings.minApprovals = 1; - }); + it('increments the proposal count', async () => { + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); - it('increments the proposal counter', async () => { - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings - ); + // Check that the proposal count is 0. + expect(await plugin.proposalCount()).to.equal(0); - expect(await multisig.proposalCount()).to.equal(0); + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; - await expect( - multisig.createProposal( + await plugin + .connect(alice) + .createProposal( dummyMetadata, dummyActions, 0, false, false, 0, - startDate - ) - ).not.to.be.reverted; + endDate + ); + // Check that the proposal count is 1. + expect(await plugin.proposalCount()).to.equal(1); + + // Create another proposal as Alice. + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); - expect(await multisig.proposalCount()).to.equal(1); + // Check that the proposal count is 2. + expect(await plugin.proposalCount()).to.equal(2); }); it('creates unique proposal IDs for each proposal', async () => { - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings - ); - await ethers.provider.send('evm_mine', []); - - const proposalId0 = await multisig.callStatic.createProposal( + const { + alice, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - 0, - endDate - ); - // create a new proposal for the proposalCounter to be incremented - await expect( - multisig.createProposal( + } = await loadFixture(fixture); + + // Make a static call to `createProposal` to get the first proposal ID ahead of time. + const endDate = (await time.latest()) + TIME.HOUR; + const proposalId0 = await plugin + .connect(alice) + .callStatic.createProposal( dummyMetadata, dummyActions, 0, false, false, 0, - startDate - ) - ).not.to.be.reverted; + endDate + ); - const proposalId1 = await multisig.callStatic.createProposal( - dummyMetadata, - dummyActions, - 0, - false, - false, - 0, - endDate - ); + // Create the new proposal as Alice. + await expect( + plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ) + ).not.to.be.reverted; - expect(proposalId0).to.equal(0); // To be removed when proposal ID is generated as a hash. - expect(proposalId1).to.equal(1); // To be removed when proposal ID is generated as a hash. + // Make a static call to `createProposal` to get the next proposal ID ahead of time. + const proposalId1 = await plugin + .connect(alice) + .callStatic.createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); - expect(proposalId0).to.not.equal(proposalId1); + // Check that the proposal IDs are as expected. + expect(proposalId0).to.equal(0); + expect(proposalId1).to.equal(1); }); it('emits the `ProposalCreated` event', async () => { - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings - ); - - const allowFailureMap = 1; + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + } = await loadFixture(fixture); + // Create a proposal as Alice and check the event arguments. + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + const expectedProposalId = 0; await expect( - multisig - .connect(signers[0]) + plugin + .connect(alice) .createProposal( dummyMetadata, [], - allowFailureMap, + 0, false, false, startDate, endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) + .to.emit(plugin, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( - id, - signers[0].address, + expectedProposalId, + alice.address, startDate, endDate, dummyMetadata, [], - allowFailureMap + 0 ); }); it('reverts if the multisig settings have been changed in the same block', async () => { - await multisig.initialize( + const { + alice, + initializedPlugin: plugin, + dao, + } = await loadFixture(fixture); + + // Grant Alice the permission to update the settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); + + const newSettings = { + onlyListed: false, + minApprovals: 1, + }; + + /* Make two calls to update the settings in the same block. */ + // Disable auto-mining so that both proposals end up in the same block. + await ethers.provider.send('evm_setAutomine', [false]); + // Update #1 + await plugin.connect(alice).updateMultisigSettings(newSettings); + // Update #2 + await plugin.connect(alice).updateMultisigSettings(newSettings); + // Re-enable auto-mining so that the remaining tests run normally. + await ethers.provider.send('evm_setAutomine', [true]); + }); + + it('reverts if the multisig settings have been changed in the same block via the proposals process', async () => { + const { + alice, + uninitializedPlugin: plugin, + dummyMetadata, + dao, + } = await loadFixture(fixture); + await plugin.initialize(dao.address, [alice.address], { + onlyListed: true, + minApprovals: 1, + }); + + // Grant permissions between the DAO and the plugin. + await dao.grant( + plugin.address, dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID ); await dao.grant( - multisig.address, dao.address, - await multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID ); + // Create an action calling `updateMultisigSettings`. + const updateMultisigSettingsAction = { + to: plugin.address, + value: 0, + data: plugin.interface.encodeFunctionData('updateMultisigSettings', [ + { + onlyListed: false, + minApprovals: 1, + }, + ]), + }; + + /* Create two proposals to update the settings in the same block. */ + const endDate = (await time.latest()) + TIME.HOUR; + + // Disable auto-mining so that both proposals end up in the same block. await ethers.provider.send('evm_setAutomine', [false]); - await multisig.connect(signers[0]).createProposal( + // Create and execute proposal #1 calling `updateMultisigSettings`. + await plugin.connect(alice).createProposal( dummyMetadata, - [ - { - to: multisig.address, - value: 0, - data: multisig.interface.encodeFunctionData( - 'updateMultisigSettings', - [ - { - onlyListed: false, - minApprovals: 1, - }, - ] - ), - }, - ], + [updateMultisigSettingsAction], 0, - true, - true, + true, // approve + true, // execute 0, endDate ); + + // Try to call update the settings a second time. await expect( - multisig - .connect(signers[0]) - .createProposal(dummyMetadata, [], 0, true, true, 0, endDate) + plugin.connect(alice).createProposal( + dummyMetadata, + [updateMultisigSettingsAction], + 0, + false, // approve + false, // execute + 0, + endDate + ) ) - .to.revertedWithCustomError(multisig, 'ProposalCreationForbidden') - .withArgs(signers[0].address); + .to.revertedWithCustomError(plugin, 'ProposalCreationForbidden') + .withArgs(alice.address); + // Re-enable auto-mining so that the remaining tests run normally. await ethers.provider.send('evm_setAutomine', [true]); }); - context('`onlyListed` is set to `false`', async () => { - beforeEach(async () => { - multisigSettings.onlyListed = false; + describe('`onlyListed` is set to `false`', async () => { + it('creates a proposal when an unlisted accounts is calling', async () => { + const { + alice, + dave, + initializedPlugin: plugin, + dao, + dummyMetadata, + } = await loadFixture(fixture); - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID ); - }); - it('creates a proposal when unlisted accounts are allowed', async () => { + // As Alice, set `onlyListed` to `false`. + await plugin.connect(alice).updateMultisigSettings({ + minApprovals: 2, + onlyListed: false, + }); + + // Create a proposal as Dave (who is not listed). + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + + const expectedProposalId = 0; + await expect( - multisig - .connect(signers[1]) // not listed + plugin + .connect(dave) // Dave is not listed. .createProposal( dummyMetadata, [], @@ -666,10 +928,10 @@ describe('Multisig', function () { endDate ) ) - .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) + .to.emit(plugin, IPROPOSAL_EVENTS.ProposalCreated) .withArgs( - id, - signers[1].address, + expectedProposalId, + dave.address, startDate, endDate, dummyMetadata, @@ -679,62 +941,103 @@ describe('Multisig', function () { }); }); - context('`onlyListed` is set to `true`', async () => { - beforeEach(async () => { - multisigSettings.onlyListed = true; - - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings - ); - }); + describe('`onlyListed` is set to `true`', async () => { + it('reverts if the caller is not listed and only listed accounts can create proposals', async () => { + const { + dave, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); - it('reverts if the user is not on the list and only listed accounts can create proposals', async () => { + // Try to create a proposal as Dave (who is not listed), which should revert. + const endDate = (await time.latest()) + TIME.HOUR; await expect( - multisig - .connect(signers[1]) // not listed - .createProposal(dummyMetadata, [], 0, false, false, 0, startDate) + plugin + .connect(dave) // Dave is not listed. + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ) ) - .to.be.revertedWithCustomError(multisig, 'ProposalCreationForbidden') - .withArgs(signers[1].address); - - await expect( - multisig - .connect(signers[0]) - .createProposal(dummyMetadata, [], 0, false, false, 0, startDate) - ).not.to.be.reverted; + .to.be.revertedWithCustomError(plugin, 'ProposalCreationForbidden') + .withArgs(dave.address); }); - it('reverts if `_msgSender` is not listed in the current block although he was listed in the last block', async () => { + it('reverts if caller is not listed in the current block although she was listed in the last block', async () => { + const { + alice, + carol, + dave, + initializedPlugin: plugin, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Grant Alice the permission to update settings. + await dao.grant( + plugin.address, + alice.address, + UPDATE_MULTISIG_SETTINGS_PERMISSION_ID + ); + + const endDate = (await time.latest()) + TIME.HOUR; + + // Disable auto-mining so that all subsequent transactions end up in the same block. await ethers.provider.send('evm_setAutomine', [false]); const expectedSnapshotBlockNumber = ( await ethers.provider.getBlock('latest') ).number; - // Transaction 1 & 2: Add signers[1] and remove signers[0] - const tx1 = await multisig.addAddresses([signers[1].address]); - const tx2 = await multisig.removeAddresses([signers[0].address]); - - // Transaction 3: Expect the proposal creation to fail for signers[0] because he was removed as a member in transaction 2. - await expect( - multisig - .connect(signers[0]) - .createProposal(dummyMetadata, [], 0, false, false, 0, startDate) - ) - .to.be.revertedWithCustomError(multisig, 'ProposalCreationForbidden') - .withArgs(signers[0].address); + // Transaction 1 & 2: Add Dave and remove Carol. + const tx1 = await plugin.connect(alice).addAddresses([dave.address]); + const tx2 = await plugin + .connect(alice) + .removeAddresses([carol.address]); - // Transaction 4: Create the proposal as signers[1] - const tx4 = await multisig - .connect(signers[1]) - .createProposal(dummyMetadata, [], 0, false, false, 0, startDate); + // Transaction 3: Expect the proposal creation to fail for Carol because she was removed as a member in transaction 2. - // Check the listed members before the block is mined - expect(await multisig.isListed(signers[0].address)).to.equal(true); - expect(await multisig.isListed(signers[1].address)).to.equal(false); - - // Mine the block + await expect( + plugin + .connect(carol) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ) + ) + .to.be.revertedWithCustomError(plugin, 'ProposalCreationForbidden') + .withArgs(carol.address); + + // Transaction 4: Create the proposal as Dave + const tx4 = await plugin + .connect(dave) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Check the listed members before the block is mined. + expect(await plugin.isListed(carol.address)).to.equal(true); + expect(await plugin.isListed(dave.address)).to.equal(false); + + // Mine the block await ethers.provider.send('evm_mine', []); const minedBlockNumber = (await ethers.provider.getBlock('latest')) .number; @@ -745,475 +1048,1003 @@ describe('Multisig', function () { expect((await tx4.wait()).blockNumber).to.equal(minedBlockNumber); expect(minedBlockNumber).to.equal(expectedSnapshotBlockNumber + 1); - // Expect the listed member to have changed - expect(await multisig.isListed(signers[0].address)).to.equal(false); - expect(await multisig.isListed(signers[1].address)).to.equal(true); + // Expect the listed member to have changed. + expect(await plugin.isListed(carol.address)).to.equal(false); + expect(await plugin.isListed(dave.address)).to.equal(true); - // Check the `ProposalCreatedEvent` for the creator and proposalId + // Check the `ProposalCreatedEvent` for the creator and proposalId. const event = await findEvent( tx4, 'ProposalCreated' ); expect(event.args.proposalId).to.equal(id); - expect(event.args.creator).to.equal(signers[1].address); + expect(event.args.creator).to.equal(dave.address); - // Check that the snapshot block stored in the proposal struct - const proposal = await multisig.getProposal(id); + // Check that the snapshot block stored in the proposal struct. + const proposal = await plugin.getProposal(id); expect(proposal.parameters.snapshotBlock).to.equal( expectedSnapshotBlockNumber ); + // Re-enable auto-mining so that the remaining tests run normally. await ethers.provider.send('evm_setAutomine', [true]); }); + }); - it('creates a proposal successfully and does not approve if not specified', async () => { - await time.setNextBlockTimestamp(startDate); - - await expect( - multisig.createProposal( - dummyMetadata, - [], - 0, - false, - false, - startDate, - endDate - ) - ) - .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) - .withArgs( - id, - signers[0].address, - startDate, - endDate, - dummyMetadata, - [], - 0 - ); + it('creates a proposal successfully and does not approve if not specified', async () => { + const { + alice, + bob, + initializedPlugin: plugin, + defaultInitData, + dummyMetadata, + } = await loadFixture(fixture); - const block = await ethers.provider.getBlock('latest'); + // Create a proposal (ID 0) as Alice but don't approve on creation. + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + const allowFailureMap = 0; + await time.setNextBlockTimestamp(startDate); + const id = 0; - const proposal = await multisig.getProposal(id); - expect(proposal.executed).to.equal(false); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); - expect(proposal.parameters.minApprovals).to.equal( - multisigSettings.minApprovals - ); - expect(proposal.allowFailureMap).to.equal(0); - expect(proposal.parameters.startDate).to.equal(startDate); - expect(proposal.parameters.endDate).to.equal(endDate); - expect(proposal.approvals).to.equal(0); - expect(proposal.actions.length).to.equal(0); - - expect(await multisig.canApprove(id, signers[0].address)).to.be.true; - expect(await multisig.canApprove(id, signers[1].address)).to.be.false; - }); + const approveProposal = false; - it('creates a proposal successfully and approves if specified', async () => { - const allowFailureMap = 1; + await expect( + plugin.connect(alice).createProposal( + dummyMetadata, + [], + allowFailureMap, + approveProposal, // false + false, + startDate, + endDate + ) + ) + .to.emit(plugin, IPROPOSAL_EVENTS.ProposalCreated) + .withArgs(id, alice.address, startDate, endDate, dummyMetadata, [], 0); - await time.setNextBlockTimestamp(startDate); + const latestBlock = await ethers.provider.getBlock('latest'); - await expect( - multisig.createProposal( - dummyMetadata, - [], - allowFailureMap, - true, - false, - 0, - endDate - ) - ) - .to.emit(multisig, IPROPOSAL_EVENTS.ProposalCreated) - .withArgs( - id, - signers[0].address, - startDate, - endDate, - dummyMetadata, - [], - allowFailureMap - ) - .to.emit(multisig, MULTISIG_EVENTS.Approved) - .withArgs(id, signers[0].address); + // Check that the proposal was created as expected and has 0 approvals. + const proposal = await plugin.getProposal(id); + expect(proposal.executed).to.equal(false); + expect(proposal.allowFailureMap).to.equal(0); + expect(proposal.parameters.snapshotBlock).to.equal( + latestBlock.number - 1 + ); + expect(proposal.parameters.minApprovals).to.equal( + defaultInitData.settings.minApprovals + ); + expect(proposal.parameters.startDate).to.equal(startDate); + expect(proposal.parameters.endDate).to.equal(endDate); + expect(proposal.actions.length).to.equal(0); + expect(proposal.approvals).to.equal(0); + + // Check that Alice hasn't approved the proposal yet. + expect(await plugin.canApprove(id, alice.address)).to.be.true; + // Check that, e.g., Bob hasn't approved the proposal yet. + expect(await plugin.canApprove(id, bob.address)).to.be.true; + }); - const block = await ethers.provider.getBlock('latest'); + it('creates a proposal successfully and approves if specified', async () => { + const { + alice, + bob, + initializedPlugin: plugin, + defaultInitData, + dummyMetadata, + } = await loadFixture(fixture); - const proposal = await multisig.getProposal(id); - expect(proposal.executed).to.equal(false); - expect(proposal.allowFailureMap).to.equal(allowFailureMap); - expect(proposal.parameters.snapshotBlock).to.equal(block.number - 1); - expect(proposal.parameters.minApprovals).to.equal( - multisigSettings.minApprovals - ); - expect(proposal.parameters.startDate).to.equal(startDate); - expect(proposal.parameters.endDate).to.equal(endDate); - expect(proposal.approvals).to.equal(1); - }); + // Create a proposal as Alice and approve on creation. + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + const allowFailureMap = 1; + const approveProposal = true; - it('increases the proposal count', async () => { - expect(await multisig.proposalCount()).to.equal(0); + await time.setNextBlockTimestamp(startDate); - await multisig.createProposal( + const id = 0; + await expect( + plugin.connect(alice).createProposal( dummyMetadata, - dummyActions, - 0, - true, + [], + allowFailureMap, + approveProposal, // true false, - 0, + startDate, endDate - ); - expect(await multisig.proposalCount()).to.equal(1); - - await multisig.createProposal( + ) + ) + .to.emit(plugin, IPROPOSAL_EVENTS.ProposalCreated) + .withArgs( + id, + alice.address, + startDate, + endDate, dummyMetadata, - dummyActions, - 0, - true, - false, - 0, - endDate - ); - expect(await multisig.proposalCount()).to.equal(2); - }); - }); + [], + allowFailureMap + ) + .to.emit(plugin, MULTISIG_EVENTS.Approved) + .withArgs(id, alice.address); - it('should revert if startDate is < than now', async () => { - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings + const latestBlock = await ethers.provider.getBlock('latest'); + + // Check that the proposal was created as expected and has 1 approval. + const proposal = await plugin.getProposal(id); + expect(proposal.executed).to.equal(false); + expect(proposal.allowFailureMap).to.equal(allowFailureMap); + expect(proposal.parameters.snapshotBlock).to.equal( + latestBlock.number - 1 ); + expect(proposal.parameters.minApprovals).to.equal( + defaultInitData.settings.minApprovals + ); + expect(proposal.parameters.startDate).to.equal(startDate); + expect(proposal.parameters.endDate).to.equal(endDate); + expect(proposal.actions.length).to.equal(0); + expect(proposal.approvals).to.equal(1); + + // Check that Alice has approved the proposal already. + expect(await plugin.canApprove(id, alice.address)).to.be.false; + // Check that, e.g., Bob hasn't approved the proposal yet. + expect(await plugin.canApprove(id, bob.address)).to.be.true; + }); - const currentDate = await time.latest(); - const startDateInThePast = currentDate - 1; - const endDate = 0; // startDate + minDuration + it('reverts if startDate < now', async () => { + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Set the clock to the start date. + const startDate = (await time.latest()) + TIME.MINUTE; + await time.setNextBlockTimestamp(startDate); + // Try to create a proposal as Alice where the start date lies in the past. + const startDateInThePast = startDate - 1; + const endDate = startDate + TIME.HOUR; await expect( - multisig.createProposal( - dummyMetadata, - dummyActions, - 0, - true, - false, - startDateInThePast, - endDate - ) + plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + true, + false, + startDateInThePast, + endDate + ) ) - .to.be.revertedWithCustomError(multisig, 'DateOutOfBounds') - .withArgs( - currentDate + 1, // await takes one second - startDateInThePast - ); + .to.be.revertedWithCustomError(plugin, 'DateOutOfBounds') + .withArgs(startDate, startDateInThePast); }); - it('should revert if endDate is < than startDate', async () => { - await multisig.initialize( - dao.address, - [signers[0].address], // signers[0] is listed - multisigSettings - ); + it('reverts if endDate < startDate', async () => { + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + // Try to create a proposal as Alice where the end date is before the start date const startDate = (await time.latest()) + TIME.MINUTE; const endDate = startDate - 1; // endDate < startDate await expect( - multisig.createProposal( - dummyMetadata, - dummyActions, - 0, - true, - false, - startDate, - endDate - ) + plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + true, + false, + startDate, + endDate + ) ) - .to.be.revertedWithCustomError(multisig, 'DateOutOfBounds') + .to.be.revertedWithCustomError(plugin, 'DateOutOfBounds') .withArgs(startDate, endDate); }); }); context('Approving and executing proposals', async () => { - beforeEach(async () => { - multisigSettings.minApprovals = 3; - await multisig.initialize( - dao.address, - signers.slice(0, 5).map(s => s.address), - multisigSettings - ); - - await multisig.createProposal( - dummyMetadata, - dummyActions, - 0, - false, - false, - 0, - endDate - ); - }); - describe('canApprove', async () => { it('returns `false` if the proposal is already executed', async () => { - await approveWithSigners(multisig, id, signers, [0, 1]); + const { + alice, + bob, + carol, + initializedPlugin: plugin, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); - await multisig.connect(signers[2]).approve(id, true); - expect((await multisig.getProposal(id)).executed).to.be.true; + const endDate = (await time.latest()) + TIME.HOUR; - expect(await multisig.canApprove(id, signers[3].address)).to.be.false; + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + // Check that Carol can approve. + expect(await plugin.canApprove(id, carol.address)).to.be.true; + + // Approve with Alice. + await plugin.connect(alice).approve(id, false); + // Approve and execute with Bob. + await plugin.connect(bob).approve(id, true); + + // Check that the proposal got executed. + expect((await plugin.getProposal(id)).executed).to.be.true; + + // Check that Carol cannot approve the executed proposal anymore. + expect(await plugin.canApprove(id, carol.address)).to.be.false; }); it('returns `false` if the approver is not listed', async () => { - expect(await multisig.isListed(signers[9].address)).to.be.false; + const { + alice, + dave, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; - expect(await multisig.canApprove(id, signers[9].address)).to.be.false; + // Check that Dave who is not listed cannot approve. + expect(await plugin.isListed(dave.address)).to.be.false; + expect(await plugin.canApprove(id, dave.address)).to.be.false; }); it('returns `false` if the approver has already approved', async () => { - await multisig.connect(signers[0]).approve(id, false); - expect(await multisig.canApprove(id, signers[0].address)).to.be.false; + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + await plugin.connect(alice).approve(id, false); + expect(await plugin.canApprove(id, alice.address)).to.be.false; }); it('returns `true` if the approver is listed', async () => { - expect(await multisig.canApprove(id, signers[0].address)).to.be.true; + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + expect(await plugin.canApprove(id, alice.address)).to.be.true; }); it("returns `false` if the proposal hasn't started yet", async () => { - await multisig.createProposal( + const { + alice, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - startDate, - endDate - ); + } = await loadFixture(fixture); + + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + const id = 0; - expect(await multisig.canApprove(1, signers[0].address)).to.be.false; + expect(await plugin.canApprove(id, alice.address)).to.be.false; await time.increaseTo(startDate); - expect(await multisig.canApprove(1, signers[0].address)).to.be.true; + expect(await plugin.canApprove(id, alice.address)).to.be.true; }); it('returns `false` if the proposal has ended', async () => { - await multisig.createProposal( + const { + alice, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - 0, - endDate - ); + } = await loadFixture(fixture); - expect(await multisig.canApprove(1, signers[0].address)).to.be.true; + const endDate = (await time.latest()) + TIME.HOUR; + + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + expect(await plugin.canApprove(id, alice.address)).to.be.true; await time.increaseTo(endDate + 1); - expect(await multisig.canApprove(1, signers[0].address)).to.be.false; + expect(await plugin.canApprove(id, alice.address)).to.be.false; }); }); describe('hasApproved', async () => { it("returns `false` if user hasn't approved yet", async () => { - expect(await multisig.hasApproved(id, signers[0].address)).to.be.false; + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + expect(await plugin.hasApproved(id, alice.address)).to.be.false; }); it('returns `true` if user has approved', async () => { - await multisig.approve(id, true); - expect(await multisig.hasApproved(id, signers[0].address)).to.be.true; + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + await plugin.connect(alice).approve(id, false); + expect(await plugin.hasApproved(id, alice.address)).to.be.true; }); }); describe('approve', async () => { it('reverts when approving multiple times', async () => { - await multisig.approve(id, true); + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + await plugin.connect(alice).approve(id, true); + + // Try to vote again + await expect(plugin.connect(alice).approve(id, true)) + .to.be.revertedWithCustomError(plugin, 'ApprovalCastForbidden') + .withArgs(id, alice.address); + }); + + it('reverts if minimal approval is not met yet', async () => { + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + dao, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + // Create a proposal (with ID 0) + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; - // Try to vote again - await expect(multisig.approve(id, true)) - .to.be.revertedWithCustomError(multisig, 'ApprovalCastForbidden') - .withArgs(id, signers[0].address); - }); + // Grant the plugin `EXECUTE_PERMISSION_ID` permission on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); - it('reverts if minimal approval is not met yet', async () => { - const proposal = await multisig.getProposal(id); + // Try to execute the proposals although `minApprovals` has not been reached yet. + const proposal = await plugin.getProposal(id); expect(proposal.approvals).to.eq(0); - await expect(multisig.execute(id)) - .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') + await expect(plugin.connect(alice).execute(id)) + .to.be.revertedWithCustomError(plugin, 'ProposalExecutionForbidden') .withArgs(id); }); - it('approves with the msg.sender address', async () => { - expect((await multisig.getProposal(id)).approvals).to.equal(0); + it('approves with the caller address', async () => { + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + // Create a proposal (with ID 0). + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Check that there are 0 approvals yet. + expect((await plugin.getProposal(id)).approvals).to.equal(0); - const tx = await multisig.connect(signers[0]).approve(id, false); + // Approve the proposal as Alice. + const tx = await plugin.connect(alice).approve(id, false); + // Check the `Approved` event and make sure that Alice is emitted as the approver. const event = await findEvent(tx, 'Approved'); expect(event.args.proposalId).to.eq(id); - expect(event.args.approver).to.not.eq(multisig.address); - expect(event.args.approver).to.eq(signers[0].address); + expect(event.args.approver).to.eq(alice.address); - expect((await multisig.getProposal(id)).approvals).to.equal(1); + // Check that the approval was counted. + expect((await plugin.getProposal(id)).approvals).to.equal(1); }); it("reverts if the proposal hasn't started yet", async () => { - await multisig.createProposal( + const { + alice, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - startDate, - endDate - ); + } = await loadFixture(fixture); - await expect(multisig.approve(1, false)).to.be.revertedWithCustomError( - multisig, - 'ApprovalCastForbidden' - ); + // Create a proposal as Alice that didn't started yet. + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + const id = 0; + // Try to approve the proposal as Alice although being before the start date. + await expect(plugin.connect(alice).approve(id, false)) + .to.be.revertedWithCustomError(plugin, 'ApprovalCastForbidden') + .withArgs(0, alice.address); + + // Advance to the start date. await time.increaseTo(startDate); - await expect(multisig.approve(1, false)).not.to.be.reverted; + // Approve as Alice and check that this doesn't revert. + await expect(plugin.connect(alice).approve(id, false)).not.to.be + .reverted; }); it('reverts if the proposal has ended', async () => { - await multisig.createProposal( + const { + alice, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - 0, - endDate - ); + } = await loadFixture(fixture); - await expect(multisig.connect(signers[1]).approve(1, false)).not.to.be - .reverted; + // Create a proposal as Alice that starts now. + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + // Advance time after the end date. await time.increaseTo(endDate + 1); - await expect(multisig.approve(1, false)).to.be.revertedWithCustomError( - multisig, - 'ApprovalCastForbidden' - ); + await expect(plugin.connect(alice).approve(id, false)) + .to.be.revertedWithCustomError(plugin, 'ApprovalCastForbidden') + .withArgs(id, alice.address); }); }); describe('canExecute', async () => { it('returns `false` if the proposal has not reached the minimum approval yet', async () => { - const proposal = await multisig.getProposal(id); + const { + alice, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Check that `minApprovals` isn't met yet. + const proposal = await plugin.getProposal(id); expect(proposal.approvals).to.be.lt(proposal.parameters.minApprovals); - expect(await multisig.canExecute(id)).to.be.false; + // Check that the proposal can not be executed. + expect(await plugin.canExecute(id)).to.be.false; }); it('returns `false` if the proposal is already executed', async () => { - await approveWithSigners(multisig, id, signers, [0, 1]); - await multisig.connect(signers[2]).approve(id, true); + const { + alice, + bob, + initializedPlugin: plugin, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; - expect((await multisig.getProposal(id)).executed).to.be.true; + // Grant the plugin `EXECUTE_PERMISSION_ID` permission on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + // Approve as Alice. + await plugin.connect(alice).approve(id, false); + // Approve and execute as Bob. + await plugin.connect(bob).approve(id, true); + + // Check that the proposal got executed. + expect((await plugin.getProposal(id)).executed).to.be.true; - expect(await multisig.canExecute(id)).to.be.false; + // Check that it cannot be executed again. + expect(await plugin.canExecute(id)).to.be.false; }); it('returns `true` if the proposal can be executed', async () => { - await approveWithSigners(multisig, id, signers, [0, 1, 2]); + const { + alice, + bob, + initializedPlugin: plugin, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; - expect((await multisig.getProposal(id)).executed).to.be.false; + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); - expect(await multisig.canExecute(id)).to.be.true; + expect((await plugin.getProposal(id)).executed).to.be.false; + expect(await plugin.canExecute(id)).to.be.true; }); it("returns `false` if the proposal hasn't started yet", async () => { - await multisig.createProposal( + const { + alice, + bob, + carol, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - startDate, - endDate - ); + } = await loadFixture(fixture); + + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + const id = 0; - expect(await multisig.canExecute(1)).to.be.false; + expect(await plugin.canExecute(id)).to.be.false; await time.increaseTo(startDate); - await multisig.connect(signers[0]).approve(1, false); - await multisig.connect(signers[1]).approve(1, false); - await multisig.connect(signers[2]).approve(1, false); + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); + await plugin.connect(carol).approve(id, false); - expect(await multisig.canExecute(1)).to.be.true; + expect(await plugin.canExecute(id)).to.be.true; }); it('returns `false` if the proposal has ended', async () => { - await multisig.createProposal( + const { + alice, + bob, + carol, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - 0, - endDate - ); + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; - await multisig.connect(signers[0]).approve(1, false); - await multisig.connect(signers[1]).approve(1, false); - await multisig.connect(signers[2]).approve(1, false); + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); + await plugin.connect(carol).approve(id, false); - expect(await multisig.canExecute(1)).to.be.true; + expect(await plugin.canExecute(id)).to.be.true; await time.increaseTo(endDate + 1); - expect(await multisig.canExecute(1)).to.be.false; + expect(await plugin.canExecute(id)).to.be.false; }); }); describe('execute', async () => { it('reverts if the minimum approval is not met', async () => { - await expect(multisig.execute(id)) - .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') + const { + alice, + initializedPlugin: plugin, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Grant the plugin `EXECUTE_PERMISSION_ID` permission on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + // Check that proposal cannot be executed if the minimum approval is not met yet. + await expect(plugin.execute(id)) + .to.be.revertedWithCustomError(plugin, 'ProposalExecutionForbidden') .withArgs(id); }); it('executes if the minimum approval is met', async () => { - await approveWithSigners(multisig, id, signers, [0, 1, 2]); + const { + alice, + bob, + initializedPlugin: plugin, + defaultInitData, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Grant the plugin `EXECUTE_PERMISSION_ID` permission on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); - const proposal = await multisig.getProposal(id); + // Approve with Alice and Bob. + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); + // Check that the `minApprovals` threshold is met. + const proposal = await plugin.getProposal(id); expect(proposal.parameters.minApprovals).to.equal( - multisigSettings.minApprovals + defaultInitData.settings.minApprovals + ); + expect(proposal.approvals).to.be.eq( + defaultInitData.settings.minApprovals ); - expect(proposal.approvals).to.be.eq(multisigSettings.minApprovals); - expect(await multisig.canExecute(id)).to.be.true; - await expect(multisig.execute(id)).not.to.be.reverted; + // Check that the proposal can be executed. + expect(await plugin.canExecute(id)).to.be.true; + + // Check that it executes. + await expect(plugin.execute(id)).not.to.be.reverted; }); it('executes if the minimum approval is met and can be called by an unlisted accounts', async () => { - await approveWithSigners(multisig, id, signers, [0, 1, 2]); + const { + alice, + bob, + dave, + initializedPlugin: plugin, + defaultInitData, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + const endDate = (await time.latest()) + TIME.HOUR; + + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); - const proposal = await multisig.getProposal(id); + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); + + const proposal = await plugin.getProposal(id); expect(proposal.parameters.minApprovals).to.equal( - multisigSettings.minApprovals + defaultInitData.settings.minApprovals + ); + expect(proposal.approvals).to.be.eq( + defaultInitData.settings.minApprovals ); - expect(proposal.approvals).to.be.eq(multisigSettings.minApprovals); - expect(await multisig.canExecute(id)).to.be.true; - expect(await multisig.isListed(signers[9].address)).to.be.false; // signers[9] is not listed - await expect(multisig.connect(signers[9]).execute(id)).not.to.be - .reverted; + expect(await plugin.canExecute(id)).to.be.true; + expect(await plugin.isListed(dave.address)).to.be.false; // Dave is not listed + await expect(plugin.connect(dave).execute(id)).not.to.be.reverted; }); it('executes if the minimum approval is met when multisig with the `tryExecution` option', async () => { - await multisig.connect(signers[0]).approve(id, true); + const { + alice, + bob, + carol, + initializedPlugin: plugin, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; - expect(await multisig.canExecute(id)).to.equal(false); + // Grant the plugin `EXECUTE_PERMISSION_ID` permission on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); - // `tryExecution` is turned on but the vote is not decided yet - let tx = await multisig.connect(signers[1]).approve(id, true); + // Approve and try execution as Alice although the `minApprovals` threshold is not met yet. + let tx = await plugin.connect(alice).approve(id, true); await expect( findEventTopicLog( tx, @@ -1224,10 +2055,10 @@ describe('Multisig', function () { `Event "${IDAO_EVENTS.Executed}" could not be found in transaction ${tx.hash}.` ); - expect(await multisig.canExecute(id)).to.equal(false); + expect(await plugin.canExecute(id)).to.equal(false); - // `tryExecution` is turned off and the vote is decided - tx = await multisig.connect(signers[2]).approve(id, false); + // Approve but do not try execution as Bob although the `minApprovals` threshold is reached now. + tx = await plugin.connect(bob).approve(id, false); await expect( findEventTopicLog( tx, @@ -1238,8 +2069,10 @@ describe('Multisig', function () { `Event "${IDAO_EVENTS.Executed}" could not be found in transaction ${tx.hash}.` ); - // `tryEarlyExecution` is turned on and the vote is decided - tx = await multisig.connect(signers[3]).approve(id, true); + // Approve and try execution as Carol while `minApprovals` threshold is reached already. + tx = await plugin.connect(carol).approve(id, true); + + // Check that the proposal got executed by checking the `Executed` event emitted by the DAO. { const event = await findEventTopicLog( tx, @@ -1247,7 +2080,7 @@ describe('Multisig', function () { IDAO_EVENTS.Executed ); - expect(event.args.actor).to.equal(multisig.address); + expect(event.args.actor).to.equal(plugin.address); expect(event.args.callId).to.equal(proposalIdToBytes32(id)); expect(event.args.actions.length).to.equal(1); expect(event.args.actions[0].to).to.equal(dummyActions[0].to); @@ -1255,11 +2088,11 @@ describe('Multisig', function () { expect(event.args.actions[0].data).to.equal(dummyActions[0].data); expect(event.args.execResults).to.deep.equal(['0x']); - const prop = await multisig.getProposal(id); + const prop = await plugin.getProposal(id); expect(prop.executed).to.equal(true); } - // check for the `ProposalExecuted` event in the multisig contract + // Check that the proposal got executed by checking the `ProposalExecuted` event emitted by the plugin. { const event = await findEvent( tx, @@ -1268,73 +2101,184 @@ describe('Multisig', function () { expect(event.args.proposalId).to.equal(id); } - // calling execute again should fail - await expect(multisig.execute(id)) - .to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden') + // Try executing it again. + await expect(plugin.execute(id)) + .to.be.revertedWithCustomError(plugin, 'ProposalExecutionForbidden') .withArgs(id); }); it('emits the `ProposalExecuted` and `Executed` events', async () => { - await approveWithSigners(multisig, id, signers, [0, 1, 2]); + const { + alice, + bob, + initializedPlugin: plugin, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Grant the plugin `EXECUTE_PERMISSION_ID` on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + // Approve the proposal as Alice and Bob. + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); - await expect(multisig.connect(signers[3]).execute(id)) + // Execute the proposal and check that the `Executed` and `ProposalExecuted` event is emitted + // and that the `Approved` event is not emitted. + await expect(plugin.connect(alice).execute(id)) .to.emit(dao, IDAO_EVENTS.Executed) - .to.emit(multisig, IPROPOSAL_EVENTS.ProposalExecuted) - .to.not.emit(multisig, MULTISIG_EVENTS.Approved); + .to.emit(plugin, IPROPOSAL_EVENTS.ProposalExecuted) + .to.not.emit(plugin, MULTISIG_EVENTS.Approved); }); it('emits the `Approved`, `ProposalExecuted`, and `Executed` events if execute is called inside the `approve` method', async () => { - await approveWithSigners(multisig, id, signers, [0, 1]); + const { + alice, + bob, + initializedPlugin: plugin, + dao, + dummyMetadata, + dummyActions, + } = await loadFixture(fixture); - await expect(multisig.connect(signers[2]).approve(id, true)) + // Create a proposal as Alice. + const endDate = (await time.latest()) + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Grant the plugin `EXECUTE_PERMISSION_ID` permission on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID + ); + + // Approve the proposal as Alice. + await plugin.connect(alice).approve(id, false); + + // Approve and execute the proposal as Bob and check that the `Executed`, `ProposalExecuted`, and `Approved` + // event is not emitted. + await expect(plugin.connect(bob).approve(id, true)) .to.emit(dao, IDAO_EVENTS.Executed) - .to.emit(multisig, IPROPOSAL_EVENTS.ProposalExecuted) - .to.emit(multisig, MULTISIG_EVENTS.Approved); + .to.emit(plugin, IPROPOSAL_EVENTS.ProposalExecuted) + .to.emit(plugin, MULTISIG_EVENTS.Approved); }); it("reverts if the proposal hasn't started yet", async () => { - await multisig.createProposal( + const { + alice, + bob, + initializedPlugin: plugin, + dao, dummyMetadata, dummyActions, - 0, - false, - false, - startDate, - endDate - ); + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + startDate, + endDate + ); + const id = 0; - await expect(multisig.execute(1)).to.be.revertedWithCustomError( - multisig, - 'ProposalExecutionForbidden' + // Grant the plugin `EXECUTE_PERMISSION_ID` permission on the DAO. + await dao.grant( + dao.address, + plugin.address, + DAO_PERMISSIONS.EXECUTE_PERMISSION_ID ); + // Try to execute the proposal before the start date. + await expect(plugin.execute(id)) + .to.be.revertedWithCustomError(plugin, 'ProposalExecutionForbidden') + .withArgs(id); + + // Advance time to the start date. await time.increaseTo(startDate); - await multisig.connect(signers[0]).approve(1, false); - await multisig.connect(signers[1]).approve(1, false); - await multisig.connect(signers[2]).approve(1, false); + // Approve the proposal as Alice and Bob. + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); - await expect(multisig.execute(1)).not.to.be.reverted; + // Execute the proposal. + await expect(plugin.execute(id)).not.to.be.reverted; }); it('reverts if the proposal has ended', async () => { - await multisig.createProposal( + const { + alice, + bob, + initializedPlugin: plugin, dummyMetadata, dummyActions, - 0, - false, - false, - 0, - endDate - ); - await multisig.connect(signers[0]).approve(1, false); - await multisig.connect(signers[1]).approve(1, false); - await multisig.connect(signers[2]).approve(1, false); + } = await loadFixture(fixture); + + // Create a proposal as Alice. + const startDate = (await time.latest()) + TIME.MINUTE; + const endDate = startDate + TIME.HOUR; + await plugin + .connect(alice) + .createProposal( + dummyMetadata, + dummyActions, + 0, + false, + false, + 0, + endDate + ); + const id = 0; + + // Approve the proposal but do not execute yet. + await plugin.connect(alice).approve(id, false); + await plugin.connect(bob).approve(id, false); + + // Advance time after the end date + await time.increase(endDate + 1); - await time.increase(10000); + // Try to execute the proposal after the end date. await expect( - multisig.connect(signers[1]).execute(1) - ).to.be.revertedWithCustomError(multisig, 'ProposalExecutionForbidden'); + plugin.connect(bob).execute(id) + ).to.be.revertedWithCustomError(plugin, 'ProposalExecutionForbidden'); }); }); }); diff --git a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts index 62d87bee..86c0d758 100644 --- a/packages/contracts/test/10_unit-testing/12_plugin-setup.ts +++ b/packages/contracts/test/10_unit-testing/12_plugin-setup.ts @@ -1,89 +1,105 @@ import {createDaoProxy} from '../20_integration-testing/test-helpers'; -import metadata from '../../src/build-metadata.json'; -import { - MultisigSetup, - MultisigSetup__factory, - ProxyFactory__factory, -} from '../../typechain'; -import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; -import {hashHelpers} from '../../utils/helpers'; +import {METADATA, VERSION} from '../../plugin-settings'; +import {MultisigSetup, MultisigSetup__factory} from '../../typechain'; import { MULTISIG_INTERFACE, - MultisigSettings, UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, } from '../multisig-constants'; import {Multisig__factory, Multisig} from '../test-utils/typechain-versions'; import { getInterfaceId, - findEvent, Operation, DAO_PERMISSIONS, PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; -import { - DAO, - IPluginRepo__factory, - InterfaceBasedRegistryMock, - InterfaceBasedRegistryMock__factory, - PluginRepo, - PluginRepo__factory, - PluginSetupProcessor, - PluginSetupProcessorEvents, - PluginSetupProcessor__factory, -} from '@aragon/osx-ethers'; +import {DAO} from '@aragon/osx-ethers'; +import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {ethers} from 'hardhat'; const abiCoder = ethers.utils.defaultAbiCoder; const AddressZero = ethers.constants.AddressZero; -const EMPTY_DATA = '0x'; -let defaultMultisigSettings: MultisigSettings; +type FixtureResult = { + deployer: SignerWithAddress; + alice: SignerWithAddress; + bob: SignerWithAddress; + carol: SignerWithAddress; + pluginSetup: MultisigSetup; + defaultMembers: string[]; + defaultMultisigSettings: Multisig.MultisigSettingsStruct; + prepareInstallationInputs: string; + prepareUninstallationInputs: string; + dao: DAO; +}; + +async function fixture(): Promise { + const [deployer, alice, bob, carol] = await ethers.getSigners(); + + // Deploy a DAO proxy. + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + const dao = await createDaoProxy(deployer, dummyMetadata); + + // Deploy a plugin setup contract + const pluginSetup = await new MultisigSetup__factory(deployer).deploy(); + + // Provide default multisig settings + const defaultMembers = [alice.address, bob.address, carol.address]; + const defaultMultisigSettings: Multisig.MultisigSettingsStruct = { + onlyListed: true, + minApprovals: 1, + }; + + // Provide installation inputs + const prepareInstallationInputs = ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareInstallation.inputs + ), + [defaultMembers, Object.values(defaultMultisigSettings)] + ); + + // Provide uninstallation inputs + const prepareUninstallationInputs = ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareUninstallation.inputs + ), + [] + ); + + return { + deployer, + alice, + bob, + carol, + pluginSetup, + defaultMembers, + defaultMultisigSettings, + prepareInstallationInputs, + prepareUninstallationInputs, + dao, + }; +} describe('MultisigSetup', function () { - let signers: SignerWithAddress[]; - let multisigSetup: MultisigSetup; - let MultisigFactory: Multisig__factory; - let implementationAddress: string; - let targetDao: DAO; - let minimum_data: any; - - before(async () => { - signers = await ethers.getSigners(); - targetDao = await createDaoProxy(signers[0], EMPTY_DATA); - - defaultMultisigSettings = { - onlyListed: true, - minApprovals: 1, - }; - - minimum_data = abiCoder.encode( - getNamedTypesFromMetadata( - metadata.pluginSetup.prepareInstallation.inputs - ), - [[signers[0].address], Object.values(defaultMultisigSettings)] - ); - - const MultisigSetup = new MultisigSetup__factory(signers[0]); - multisigSetup = await MultisigSetup.deploy(); - - MultisigFactory = new Multisig__factory(signers[0]); - - implementationAddress = await multisigSetup.implementation(); - }); - it('does not support the empty interface', async () => { - expect(await multisigSetup.supportsInterface('0xffffffff')).to.be.false; + const {pluginSetup} = await loadFixture(fixture); + expect(await pluginSetup.supportsInterface('0xffffffff')).to.be.false; }); - it('creates multisig base with the correct interface', async () => { - const factory = new Multisig__factory(signers[0]); - const multisigContract = factory.attach(implementationAddress); + it('has a multisig implementation supporting the correct interface', async () => { + const {deployer, pluginSetup} = await loadFixture(fixture); + + const factory = new Multisig__factory(deployer); + const multisigImplementation = factory.attach( + await pluginSetup.implementation() + ); expect( - await multisigContract.supportsInterface( + await multisigImplementation.supportsInterface( getInterfaceId(MULTISIG_INTERFACE) ) ).to.be.true; @@ -91,45 +107,59 @@ describe('MultisigSetup', function () { describe('prepareInstallation', async () => { it('fails if data is empty, or not of minimum length', async () => { - await expect( - multisigSetup.prepareInstallation(targetDao.address, EMPTY_DATA) - ).to.be.reverted; + const {pluginSetup, dao, prepareInstallationInputs} = await loadFixture( + fixture + ); - await expect( - multisigSetup.prepareInstallation( - targetDao.address, - minimum_data.substring(0, minimum_data.length - 2) - ) - ).to.be.reverted; + // Try calling `prepareInstallation` without input data. + await expect(pluginSetup.prepareInstallation(dao.address, [])).to.be + .reverted; + // Try calling `prepareInstallation` without input data of wrong length. + const trimmedData = prepareInstallationInputs.substring( + 0, + prepareInstallationInputs.length - 2 + ); + await expect(pluginSetup.prepareInstallation(dao.address, trimmedData)).to + .be.reverted; + + // Check that `prepareInstallation` can be called with the correct input data. await expect( - multisigSetup.prepareInstallation(targetDao.address, minimum_data) + pluginSetup.prepareInstallation(dao.address, prepareInstallationInputs) ).not.to.be.reverted; }); - it('reverts if zero members are provided in `_data`', async () => { - const noMembers: string[] = []; + it('reverts if zero members are provided in the initialization data', async () => { + const {deployer, pluginSetup, dao, defaultMultisigSettings} = + await loadFixture(fixture); + // Create input data containing an empty list of initial members. + const noMembers: string[] = []; const wrongPrepareInstallationData = abiCoder.encode( getNamedTypesFromMetadata( - metadata.pluginSetup.prepareInstallation.inputs + METADATA.build.pluginSetup.prepareInstallation.inputs ), [noMembers, defaultMultisigSettings] ); + // Anticipate the plugin proxy address that will be deployed. const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + pluginSetup.address ); const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, + from: pluginSetup.address, nonce, }); + const multisig = Multisig__factory.connect( + anticipatedPluginAddress, + deployer + ); - const multisig = MultisigFactory.attach(anticipatedPluginAddress); - + // Try calling `prepareInstallation`, which will fail during plugin initialization because of the empty initial + // member list. await expect( - multisigSetup.prepareInstallation( - targetDao.address, + pluginSetup.prepareInstallation( + dao.address, wrongPrepareInstallationData ) ) @@ -138,31 +168,39 @@ describe('MultisigSetup', function () { }); it('reverts if the `minApprovals` value in `_data` is zero', async () => { - const multisigSettings: MultisigSettings = { + const {deployer, pluginSetup, dao} = await loadFixture(fixture); + + // Create input data containing a `minApprovals` threshold of 0. + const multisigSettings: Multisig.MultisigSettingsStruct = { onlyListed: true, minApprovals: 0, }; - const members = [signers[0].address]; - + const members = [deployer.address]; const wrongPrepareInstallationData = abiCoder.encode( getNamedTypesFromMetadata( - metadata.pluginSetup.prepareInstallation.inputs + METADATA.build.pluginSetup.prepareInstallation.inputs ), [members, multisigSettings] ); + // Anticipate the plugin proxy address that will be deployed. const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + pluginSetup.address ); const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, + from: pluginSetup.address, nonce, }); - const multisig = MultisigFactory.attach(anticipatedPluginAddress); + const multisig = Multisig__factory.connect( + anticipatedPluginAddress, + deployer + ); + // Try calling `prepareInstallation`, which will fail during plugin initialization because of the invalid + // `minApprovals` value. await expect( - multisigSetup.prepareInstallation( - targetDao.address, + pluginSetup.prepareInstallation( + dao.address, wrongPrepareInstallationData ) ) @@ -170,32 +208,41 @@ describe('MultisigSetup', function () { .withArgs(1, 0); }); - it('reverts if the `minApprovals` value in `_data` is greater than the number members', async () => { - const multisigSettings: MultisigSettings = { + it('reverts if the `minApprovals` value in `_data` is greater than the number of members', async () => { + const {deployer, pluginSetup, dao} = await loadFixture(fixture); + + // Create input data containing an initial member list with a length lower that the specified `minApprovals` + // threshold. + const multisigSettings: Multisig.MultisigSettingsStruct = { onlyListed: true, minApprovals: 2, }; - const members = [signers[0].address]; - + const members = [deployer.address]; const wrongPrepareInstallationData = abiCoder.encode( getNamedTypesFromMetadata( - metadata.pluginSetup.prepareInstallation.inputs + METADATA.build.pluginSetup.prepareInstallation.inputs ), [members, multisigSettings] ); + // Anticipate the plugin proxy address that will be deployed. const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + pluginSetup.address ); const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, + from: pluginSetup.address, nonce, }); - const multisig = MultisigFactory.attach(anticipatedPluginAddress); + const multisig = Multisig__factory.connect( + anticipatedPluginAddress, + deployer + ); + // Try calling `prepareInstallation`, which will fail during plugin initialization because of the mismatch + // between the `minApprovals` value and the initial member list length. await expect( - multisigSetup.prepareInstallation( - targetDao.address, + pluginSetup.prepareInstallation( + dao.address, wrongPrepareInstallationData ) ) @@ -204,22 +251,29 @@ describe('MultisigSetup', function () { }); it('returns the plugin, helpers, and permissions', async () => { + const {pluginSetup, dao, prepareInstallationInputs} = await loadFixture( + fixture + ); + + // Anticipate the plugin proxy address that will be deployed. const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + pluginSetup.address ); const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, + from: pluginSetup.address, nonce, }); + // Make a static call to check that the plugin preparation data being returned is correct. const { plugin, preparedSetupData: {helpers, permissions}, - } = await multisigSetup.callStatic.prepareInstallation( - targetDao.address, - minimum_data + } = await pluginSetup.callStatic.prepareInstallation( + dao.address, + prepareInstallationInputs ); + // Check the return data. expect(plugin).to.be.equal(anticipatedPluginAddress); expect(helpers.length).to.be.equal(0); expect(permissions.length).to.be.equal(3); @@ -227,20 +281,20 @@ describe('MultisigSetup', function () { [ Operation.Grant, plugin, - targetDao.address, + dao.address, AddressZero, UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, ], [ Operation.Grant, plugin, - targetDao.address, + dao.address, AddressZero, PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, ], [ Operation.Grant, - targetDao.address, + dao.address, plugin, AddressZero, DAO_PERMISSIONS.EXECUTE_PERMISSION_ID, @@ -249,36 +303,52 @@ describe('MultisigSetup', function () { }); it('sets up the plugin', async () => { - const daoAddress = targetDao.address; + const { + deployer, + pluginSetup, + dao, + prepareInstallationInputs, + defaultMembers, + defaultMultisigSettings, + } = await loadFixture(fixture); + // Anticipate the plugin proxy address that will be deployed. const nonce = await ethers.provider.getTransactionCount( - multisigSetup.address + pluginSetup.address ); const anticipatedPluginAddress = ethers.utils.getContractAddress({ - from: multisigSetup.address, + from: pluginSetup.address, nonce, }); - await multisigSetup.prepareInstallation(daoAddress, minimum_data); + // Prepare the installation + await pluginSetup.prepareInstallation( + dao.address, + prepareInstallationInputs + ); - const factory = new Multisig__factory(signers[0]); - const multisigContract = factory.attach(anticipatedPluginAddress); + const plugin = Multisig__factory.connect( + anticipatedPluginAddress, + deployer + ); - expect(await multisigContract.dao()).to.eq(daoAddress); - expect(await multisigContract.addresslistLength()).to.be.eq(1); - const settings = await multisigContract.multisigSettings(); - expect(settings.onlyListed).to.be.true; - expect(settings.minApprovals).to.eq(1); + // Check that the plugin is initialized correctly. + expect(await plugin.dao()).to.eq(dao.address); + expect(await plugin.addresslistLength()).to.be.eq(defaultMembers.length); + const settings = await plugin.multisigSettings(); + expect(settings.onlyListed).to.equal(defaultMultisigSettings.onlyListed); + expect(settings.minApprovals).to.eq(defaultMultisigSettings.minApprovals); }); }); describe('prepareUpdate', async () => { it('should return nothing', async () => { - const dao = ethers.Wallet.createRandom().address; - const currentBuild = 1; - const prepareUpdateData = await multisigSetup.callStatic.prepareUpdate( - dao, - currentBuild, + const {pluginSetup, dao} = await loadFixture(fixture); + + // Make a static call to check that the plugin update data being returned is correct. + const prepareUpdateData = await pluginSetup.callStatic.prepareUpdate( + dao.address, + VERSION.build, { currentHelpers: [ ethers.Wallet.createRandom().address, @@ -288,6 +358,7 @@ describe('MultisigSetup', function () { plugin: ethers.Wallet.createRandom().address, } ); + // Check the return data. expect(prepareUpdateData.initData).to.be.eq('0x'); expect(prepareUpdateData.preparedSetupData.permissions).to.be.eql([]); expect(prepareUpdateData.preparedSetupData.helpers).to.be.eql([]); @@ -296,36 +367,44 @@ describe('MultisigSetup', function () { describe('prepareUninstallation', async () => { it('correctly returns permissions', async () => { + const {pluginSetup, dao, prepareUninstallationInputs} = await loadFixture( + fixture + ); + + // Use a random address to prepare an uninstallation. + // Note: Applying this uninstallation would fail because the PSP knows if the plugin was installed at some point. const plugin = ethers.Wallet.createRandom().address; - const permissions = await multisigSetup.callStatic.prepareUninstallation( - targetDao.address, + // Make a static call to check that the plugin uninstallation data being returned is correct. + const permissions = await pluginSetup.callStatic.prepareUninstallation( + dao.address, { plugin, currentHelpers: [], - data: EMPTY_DATA, + data: prepareUninstallationInputs, } ); + // Check the return data. expect(permissions.length).to.be.equal(3); expect(permissions).to.deep.equal([ [ Operation.Revoke, plugin, - targetDao.address, + dao.address, AddressZero, UPDATE_MULTISIG_SETTINGS_PERMISSION_ID, ], [ Operation.Revoke, plugin, - targetDao.address, + dao.address, AddressZero, PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, ], [ Operation.Revoke, - targetDao.address, + dao.address, plugin, AddressZero, DAO_PERMISSIONS.EXECUTE_PERMISSION_ID, @@ -333,280 +412,4 @@ describe('MultisigSetup', function () { ]); }); }); - - describe('Updates', async () => { - let psp: PluginSetupProcessor; - let setup1: MultisigSetup; - let setup2: MultisigSetup; - let dao: DAO; - let managingDAO: DAO; - let owner: SignerWithAddress; - let pluginRepoRegistry: InterfaceBasedRegistryMock; - let pluginRepo: PluginRepo; - - before(async () => { - [owner] = await ethers.getSigners(); - managingDAO = await createDaoProxy(owner, EMPTY_DATA); - - // Create the PluginRepo - const pluginRepoImplementation = await new PluginRepo__factory( - signers[0] - ).deploy(); - const pluginRepoProxyFactory = await new ProxyFactory__factory( - signers[0] - ).deploy(pluginRepoImplementation.address); - const tx = await pluginRepoProxyFactory.deployUUPSProxy([]); - const event = await findEvent( - tx, - pluginRepoProxyFactory.interface.getEvent('ProxyCreated').name - ); - pluginRepo = PluginRepo__factory.connect(event.args.proxy, signers[0]); - - await pluginRepo.initialize(owner.address); - - // Create the PluginRepoRegistry - const pluginRepoRegistryFactory = new InterfaceBasedRegistryMock__factory( - owner - ); - pluginRepoRegistry = await pluginRepoRegistryFactory.deploy(); - await pluginRepoRegistry.initialize( - managingDAO.address, - getInterfaceId(IPluginRepo__factory.createInterface()) - ); - - // Grant the owner full rights on the registry - await managingDAO.grant( - pluginRepoRegistry.address, - owner.address, - await pluginRepoRegistry.REGISTER_PERMISSION_ID() - ); - - // Register the PluginRepo in the registry - await pluginRepoRegistry.register(pluginRepo.address); - - // Create the PluginSetupProcessor - const pspFactory = new PluginSetupProcessor__factory(owner); - psp = await pspFactory.deploy(pluginRepoRegistry.address); - - // Prepare all MultisigSetup' - We can reuse the same for now - const multisigSetupFactory = new MultisigSetup__factory(owner); - setup1 = await multisigSetupFactory.deploy(); - setup2 = await multisigSetupFactory.deploy(); - - // Create the versions in the plugin repo - await expect(pluginRepo.createVersion(1, setup1.address, '0x00', '0x00')) - .to.emit(pluginRepo, 'VersionCreated') - .withArgs(1, 1, setup1.address, '0x00'); - await expect(pluginRepo.createVersion(1, setup2.address, '0x00', '0x00')) - .to.emit(pluginRepo, 'VersionCreated') - .withArgs(1, 2, setup2.address, '0x00'); - }); - - describe('Release 1 Build 1', () => { - let plugin: Multisig; - let helpers: string[]; - - before(async () => { - dao = await createDaoProxy(owner, EMPTY_DATA); - // grant the owner full permission for plugins - await dao.applySingleTargetPermissions(psp.address, [ - { - operation: Operation.Grant, - who: owner.address, - permissionId: await psp.APPLY_INSTALLATION_PERMISSION_ID(), - }, - { - operation: Operation.Grant, - who: owner.address, - permissionId: await psp.APPLY_UPDATE_PERMISSION_ID(), - }, - { - operation: Operation.Grant, - who: owner.address, - permissionId: await psp.APPLY_UNINSTALLATION_PERMISSION_ID(), - }, - ]); - // grant the PSP root to apply stuff - await dao.grant( - dao.address, - psp.address, - await dao.ROOT_PERMISSION_ID() - ); - }); - - it('should install', async () => { - const tx = await psp.prepareInstallation(dao.address, { - pluginSetupRef: { - versionTag: { - build: 1, - release: 1, - }, - pluginSetupRepo: pluginRepo.address, - }, - data: ethers.utils.defaultAbiCoder.encode( - ['address[]', '(bool, uint16)'], - [[owner.address], [true, 1]] - ), - }); - const preparedEvent = - await findEvent( - tx, - 'InstallationPrepared' - ); - - await expect( - psp.applyInstallation(dao.address, { - pluginSetupRef: { - versionTag: { - build: 1, - release: 1, - }, - pluginSetupRepo: pluginRepo.address, - }, - helpersHash: hashHelpers( - preparedEvent.args.preparedSetupData.helpers - ), - permissions: preparedEvent.args.preparedSetupData.permissions, - plugin: preparedEvent.args.plugin, - }) - ).to.emit(psp, 'InstallationApplied'); - - plugin = Multisig__factory.connect(preparedEvent.args.plugin, owner); - helpers = preparedEvent.args.preparedSetupData.helpers; - - expect(await plugin.implementation()).to.be.eq( - await setup1.implementation() - ); - }); - - it('should update to Release 1 Build 2', async () => { - // grant psp permission to update the proxy implementation - await dao.grant( - plugin.address, - psp.address, - await plugin.UPGRADE_PLUGIN_PERMISSION_ID() - ); - - const tx = await psp.prepareUpdate(dao.address, { - currentVersionTag: { - release: 1, - build: 1, - }, - newVersionTag: { - release: 1, - build: 2, - }, - pluginSetupRepo: pluginRepo.address, - setupPayload: { - plugin: plugin.address, - currentHelpers: helpers, - data: '0x00', - }, - }); - const preparedEvent = - await findEvent( - tx, - 'UpdatePrepared' - ); - - await expect( - psp.applyUpdate(dao.address, { - plugin: plugin.address, - helpersHash: hashHelpers( - preparedEvent.args.preparedSetupData.helpers - ), - permissions: preparedEvent.args.preparedSetupData.permissions, - initData: preparedEvent.args.initData, - pluginSetupRef: { - versionTag: { - release: 1, - build: 2, - }, - pluginSetupRepo: pluginRepo.address, - }, - }) - ).to.emit(psp, 'UpdateApplied'); - - expect(await plugin.implementation()).to.be.eq( - await setup2.implementation() - ); - }); - }); - - describe('Release 1 Build 2', () => { - before(async () => { - dao = await createDaoProxy(owner, EMPTY_DATA); - // grant the owner full permission for plugins - await dao.applySingleTargetPermissions(psp.address, [ - { - operation: Operation.Grant, - who: owner.address, - permissionId: await psp.APPLY_INSTALLATION_PERMISSION_ID(), - }, - { - operation: Operation.Grant, - who: owner.address, - permissionId: await psp.APPLY_UPDATE_PERMISSION_ID(), - }, - { - operation: Operation.Grant, - who: owner.address, - permissionId: await psp.APPLY_UNINSTALLATION_PERMISSION_ID(), - }, - ]); - // grant the PSP root to apply stuff - await dao.grant( - dao.address, - psp.address, - await dao.ROOT_PERMISSION_ID() - ); - }); - - it('should install', async () => { - const tx = await psp.prepareInstallation(dao.address, { - pluginSetupRef: { - versionTag: { - release: 1, - build: 2, - }, - pluginSetupRepo: pluginRepo.address, - }, - data: ethers.utils.defaultAbiCoder.encode( - ['address[]', '(bool, uint16)'], - [[owner.address], [true, 1]] - ), - }); - const preparedEvent = - await findEvent( - tx, - 'InstallationPrepared' - ); - - await expect( - psp.applyInstallation(dao.address, { - pluginSetupRef: { - versionTag: { - release: 1, - build: 2, - }, - pluginSetupRepo: pluginRepo.address, - }, - helpersHash: hashHelpers( - preparedEvent.args.preparedSetupData.helpers - ), - permissions: preparedEvent.args.preparedSetupData.permissions, - plugin: preparedEvent.args.plugin, - }) - ).to.emit(psp, 'InstallationApplied'); - - const plugin = Multisig__factory.connect( - preparedEvent.args.plugin, - owner - ); - expect(await plugin.implementation()).to.be.eq( - await setup2.implementation() - ); - }); - }); - }); }); diff --git a/packages/contracts/test/20_integration-testing/22_setup-processing.ts b/packages/contracts/test/20_integration-testing/22_setup-processing.ts index 87eac0ed..3b1fc997 100644 --- a/packages/contracts/test/20_integration-testing/22_setup-processing.ts +++ b/packages/contracts/test/20_integration-testing/22_setup-processing.ts @@ -1,8 +1,13 @@ import {METADATA, VERSION} from '../../plugin-settings'; import {MultisigSetup, Multisig__factory} from '../../typechain'; import {getProductionNetworkName, findPluginRepo} from '../../utils/helpers'; -import {MultisigSettings} from '../multisig-constants'; -import {createDaoProxy, installPLugin, uninstallPLugin} from './test-helpers'; +import {Multisig} from '../test-utils/typechain-versions'; +import { + createDaoProxy, + installPLugin, + uninstallPLugin, + updateFromBuildTest, +} from './test-helpers'; import { getLatestNetworkDeployment, getNetworkNameByAlias, @@ -28,11 +33,92 @@ import env, {deployments, ethers} from 'hardhat'; const productionNetworkName = getProductionNetworkName(env); +type FixtureResult = { + deployer: SignerWithAddress; + alice: SignerWithAddress; + bob: SignerWithAddress; + dao: DAO; + defaultInitData: { + members: string[]; + settings: Multisig.MultisigSettingsStruct; + }; + psp: PluginSetupProcessor; + pluginRepo: PluginRepo; + pluginSetup: MultisigSetup; + pluginSetupRefLatestBuild: PluginSetupProcessorStructs.PluginSetupRefStruct; +}; + +async function fixture(): Promise { + // Deploy all contracts + const tags = ['CreateRepo', 'NewVersion']; + await deployments.fixture(tags); + + const [deployer, alice, bob] = await ethers.getSigners(); + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + const dao = await createDaoProxy(deployer, dummyMetadata); + + const network = getNetworkNameByAlias(productionNetworkName); + if (network === null) { + throw new UnsupportedNetworkError(productionNetworkName); + } + const networkDeployments = getLatestNetworkDeployment(network); + if (networkDeployments === null) { + throw `Deployments are not available on network ${network}.`; + } + + // Get the `PluginSetupProcessor` from the network + const psp = PluginSetupProcessor__factory.connect( + networkDeployments.PluginSetupProcessor.address, + deployer + ); + + // Get the deployed `PluginRepo` + const {pluginRepo, ensDomain} = await findPluginRepo(env); + if (pluginRepo === null) { + throw `PluginRepo '${ensDomain}' does not exist yet.`; + } + + const release = 1; + const pluginSetup = MultisigSetup__factory.connect( + (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, + deployer + ); + + const defaultInitData = { + members: [alice.address], + settings: { + onlyListed: true, + minApprovals: 1, + }, + }; + + const pluginSetupRefLatestBuild = { + versionTag: { + release: VERSION.release, + build: VERSION.build, + }, + pluginSetupRepo: pluginRepo.address, + }; + + return { + deployer, + alice, + bob, + psp, + dao, + defaultInitData, + pluginRepo, + pluginSetup, + pluginSetupRefLatestBuild, + }; +} + describe(`PluginSetup processing on network '${productionNetworkName}'`, function () { it('installs & uninstalls the current build', async () => { - const {alice, bob, deployer, psp, dao, pluginSetupRef} = await loadFixture( - fixture - ); + const {alice, bob, deployer, psp, dao, pluginSetupRefLatestBuild} = + await loadFixture(fixture); // Grant deployer all required permissions await dao @@ -54,9 +140,8 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); // Install the current build. - const initialMembers = [alice.address, bob.address]; - const multisigSettings: MultisigSettings = { + const multisigSettings: Multisig.MultisigSettingsStruct = { onlyListed: true, minApprovals: 2, }; @@ -65,7 +150,7 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio deployer, psp, dao, - pluginSetupRef, + pluginSetupRefLatestBuild, ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareInstallation.inputs @@ -88,7 +173,7 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio psp, dao, plugin, - pluginSetupRef, + pluginSetupRefLatestBuild, ethers.utils.defaultAbiCoder.encode( getNamedTypesFromMetadata( METADATA.build.pluginSetup.prepareUninstallation.inputs @@ -98,76 +183,48 @@ describe(`PluginSetup processing on network '${productionNetworkName}'`, functio [] ); }); - it.skip('updates to the current build', async () => { - expect(false).to.be.true; - }); -}); -type FixtureResult = { - deployer: SignerWithAddress; - alice: SignerWithAddress; - bob: SignerWithAddress; - dao: DAO; - psp: PluginSetupProcessor; - pluginRepo: PluginRepo; - pluginSetup: MultisigSetup; - pluginSetupRef: PluginSetupProcessorStructs.PluginSetupRefStruct; -}; - -async function fixture(): Promise { - // Deploy all contracts - const tags = ['CreateRepo', 'NewVersion']; - await deployments.fixture(tags); - - const [deployer, alice, bob] = await ethers.getSigners(); - const dummyMetadata = ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('0x123456789') - ); - const dao = await createDaoProxy(deployer, dummyMetadata); - - const network = getNetworkNameByAlias(productionNetworkName); - if (network === null) { - throw new UnsupportedNetworkError(productionNetworkName); - } - const networkDeployments = getLatestNetworkDeployment(network); - if (networkDeployments === null) { - throw `Deployments are not available on network ${network}.`; - } - - // Get the `PluginSetupProcessor` from the network - const psp = PluginSetupProcessor__factory.connect( - networkDeployments.PluginSetupProcessor.address, - deployer - ); - - // Get the deployed `PluginRepo` - const {pluginRepo, ensDomain} = await findPluginRepo(env); - if (pluginRepo === null) { - throw `PluginRepo '${ensDomain}' does not exist yet.`; - } + it('updates from build 1 to the current build', async () => { + const { + deployer, + psp, + dao, + defaultInitData, + pluginRepo, + pluginSetupRefLatestBuild, + } = await loadFixture(fixture); - const release = 1; - const pluginSetup = MultisigSetup__factory.connect( - (await pluginRepo['getLatestVersion(uint8)'](release)).pluginSetup, - deployer - ); + await updateFromBuildTest( + dao, + deployer, + psp, + pluginRepo, + pluginSetupRefLatestBuild, + 1, + [defaultInitData.members, Object.values(defaultInitData.settings)], + [] + ); + }); - const pluginSetupRef = { - versionTag: { - release: VERSION.release, - build: VERSION.build, - }, - pluginSetupRepo: pluginRepo.address, - }; + it('updates from build 2 to the current build', async () => { + const { + deployer, + psp, + dao, + defaultInitData, + pluginRepo, + pluginSetupRefLatestBuild, + } = await loadFixture(fixture); - return { - deployer, - alice, - bob, - psp, - dao, - pluginRepo, - pluginSetup, - pluginSetupRef, - }; -} + await updateFromBuildTest( + dao, + deployer, + psp, + pluginRepo, + pluginSetupRefLatestBuild, + 2, + [defaultInitData.members, Object.values(defaultInitData.settings)], + [] + ); + }); +}); diff --git a/packages/contracts/test/20_integration-testing/test-helpers.ts b/packages/contracts/test/20_integration-testing/test-helpers.ts index a9bac891..bfbf2046 100644 --- a/packages/contracts/test/20_integration-testing/test-helpers.ts +++ b/packages/contracts/test/20_integration-testing/test-helpers.ts @@ -1,10 +1,18 @@ -import {IPlugin, ProxyFactory__factory} from '../../typechain'; +import {METADATA, VERSION} from '../../plugin-settings'; +import { + IPlugin, + PluginUpgradeableSetup__factory, + ProxyFactory__factory, +} from '../../typechain'; import {ProxyCreatedEvent} from '../../typechain/@aragon/osx-commons-contracts/src/utils/deployment/ProxyFactory'; +import {PluginUUPSUpgradeable__factory} from '../../typechain/factories/@aragon/osx-v1.0.0/core/plugin'; import {hashHelpers} from '../../utils/helpers'; import { DAO_PERMISSIONS, PLUGIN_SETUP_PROCESSOR_PERMISSIONS, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS, findEvent, + getNamedTypesFromMetadata, } from '@aragon/osx-commons-sdk'; import { PluginSetupProcessorEvents, @@ -13,6 +21,7 @@ import { DAOStructs, DAO, DAO__factory, + PluginRepo, } from '@aragon/osx-ethers'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; @@ -217,6 +226,115 @@ async function checkPermissions( } } +export async function updateFromBuildTest( + dao: DAO, + deployer: SignerWithAddress, + psp: PluginSetupProcessor, + pluginRepo: PluginRepo, + pluginSetupRefLatestBuild: PluginSetupProcessorStructs.PluginSetupRefStruct, + build: number, + installationInputs: any[], + updateInputs: any[] +) { + // Grant deployer all required permissions + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_INSTALLATION_PERMISSION_ID + ); + await dao + .connect(deployer) + .grant( + psp.address, + deployer.address, + PLUGIN_SETUP_PROCESSOR_PERMISSIONS.APPLY_UPDATE_PERMISSION_ID + ); + + await dao + .connect(deployer) + .grant(dao.address, psp.address, DAO_PERMISSIONS.ROOT_PERMISSION_ID); + + // Install build 1. + const pluginSetupRefBuild1 = { + versionTag: { + release: VERSION.release, + build: build, + }, + pluginSetupRepo: pluginRepo.address, + }; + const installationResults = await installPLugin( + deployer, + psp, + dao, + pluginSetupRefBuild1, + ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareInstallation.inputs + ), + installationInputs + ) + ); + + // Get the plugin address. + const plugin = PluginUUPSUpgradeable__factory.connect( + installationResults.preparedEvent.args.plugin, + deployer + ); + + // Check that the implementation of the plugin proxy matches the latest build + const implementationBuild1 = await PluginUpgradeableSetup__factory.connect( + ( + await pluginRepo['getVersion((uint8,uint16))']( + pluginSetupRefBuild1.versionTag + ) + ).pluginSetup, + deployer + ).implementation(); + expect(await plugin.implementation()).to.equal(implementationBuild1); + + // Grant the PSP the permission to upgrade the plugin implementation. + await dao + .connect(deployer) + .grant( + plugin.address, + psp.address, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID + ); + + // Update build 1 to the latest build + await expect( + updatePlugin( + deployer, + psp, + dao, + plugin, + installationResults.preparedEvent.args.preparedSetupData.helpers, + pluginSetupRefBuild1, + pluginSetupRefLatestBuild, + ethers.utils.defaultAbiCoder.encode( + getNamedTypesFromMetadata( + METADATA.build.pluginSetup.prepareUpdate[1].inputs + ), + updateInputs + ) + ) + ).to.not.be.reverted; + + // Check that the implementation of the plugin proxy matches the latest build + const implementationLatestBuild = + await PluginUpgradeableSetup__factory.connect( + ( + await pluginRepo['getVersion((uint8,uint16))']( + pluginSetupRefLatestBuild.versionTag + ) + ).pluginSetup, + deployer + ).implementation(); + expect(await plugin.implementation()).to.equal(implementationLatestBuild); +} + // TODO Move into OSX commons as part of Task OS-928. export async function createDaoProxy( deployer: SignerWithAddress, diff --git a/packages/contracts/test/30_regression-testing/31_upgradeability.ts b/packages/contracts/test/30_regression-testing/31_upgradeability.ts new file mode 100644 index 00000000..4d57b5c5 --- /dev/null +++ b/packages/contracts/test/30_regression-testing/31_upgradeability.ts @@ -0,0 +1,135 @@ +import {createDaoProxy} from '../20_integration-testing/test-helpers'; +import { + Multisig_V1_1__factory, + Multisig_V1_2__factory, + Multisig__factory, + Multisig, +} from '../test-utils/typechain-versions'; +import { + deployAndUpgradeFromToCheck, + deployAndUpgradeSelfCheck, + getProtocolVersion, +} from '../test-utils/uups-upgradeable'; +import {PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS} from '@aragon/osx-commons-sdk'; +import {DAO} from '@aragon/osx-ethers'; +import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; +import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; +import {expect} from 'chai'; +import {ethers} from 'hardhat'; + +describe('Upgrades', () => { + it('upgrades to a new implementation', async () => { + const {deployer, alice, dao, defaultInitData} = await loadFixture(fixture); + const currentContractFactory = new Multisig__factory(deployer); + + await deployAndUpgradeSelfCheck( + deployer, + alice, + [dao.address, defaultInitData.members, defaultInitData.settings], + 'initialize', + currentContractFactory, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + dao + ); + }); + + it('upgrades from v1.1', async () => { + const {deployer, alice, dao, defaultInitData} = await loadFixture(fixture); + const currentContractFactory = new Multisig__factory(deployer); + const legacyContractFactory = new Multisig_V1_1__factory(deployer); + + const {fromImplementation, toImplementation} = + await deployAndUpgradeFromToCheck( + deployer, + alice, + [dao.address, defaultInitData.members, defaultInitData.settings], + 'initialize', + legacyContractFactory, + currentContractFactory, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + dao + ); + expect(toImplementation).to.not.equal(fromImplementation); // The build did change + + const fromProtocolVersion = await getProtocolVersion( + legacyContractFactory.attach(fromImplementation) + ); + const toProtocolVersion = await getProtocolVersion( + currentContractFactory.attach(toImplementation) + ); + + expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); + expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); + expect(toProtocolVersion).to.deep.equal([1, 4, 0]); + }); + + it('from v1.2', async () => { + const {deployer, alice, dao, defaultInitData} = await loadFixture(fixture); + const currentContractFactory = new Multisig__factory(deployer); + const legacyContractFactory = new Multisig_V1_2__factory(deployer); + + const {fromImplementation, toImplementation} = + await deployAndUpgradeFromToCheck( + deployer, + alice, + [dao.address, defaultInitData.members, defaultInitData.settings], + 'initialize', + legacyContractFactory, + currentContractFactory, + PLUGIN_UUPS_UPGRADEABLE_PERMISSIONS.UPGRADE_PLUGIN_PERMISSION_ID, + dao + ); + expect(toImplementation).to.not.equal(fromImplementation); + + const fromProtocolVersion = await getProtocolVersion( + legacyContractFactory.attach(fromImplementation) + ); + const toProtocolVersion = await getProtocolVersion( + currentContractFactory.attach(toImplementation) + ); + + expect(fromProtocolVersion).to.not.deep.equal(toProtocolVersion); + expect(fromProtocolVersion).to.deep.equal([1, 0, 0]); + expect(toProtocolVersion).to.deep.equal([1, 4, 0]); + }); +}); + +type FixtureResult = { + deployer: SignerWithAddress; + alice: SignerWithAddress; + bob: SignerWithAddress; + carol: SignerWithAddress; + defaultInitData: { + members: string[]; + settings: Multisig.MultisigSettingsStruct; + }; + dao: DAO; +}; + +async function fixture(): Promise { + const [deployer, alice, bob, carol] = await ethers.getSigners(); + + const dummyMetadata = ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('0x123456789') + ); + + const dao = await createDaoProxy(deployer, dummyMetadata); + + // Create an initialized plugin clone + const defaultInitData = { + members: [alice.address, bob.address, carol.address], + settings: { + onlyListed: true, + minApprovals: 2, + }, + }; + + return { + deployer, + alice, + bob, + carol, + dao, + defaultInitData, + }; +} diff --git a/packages/contracts/test/multisig-constants.ts b/packages/contracts/test/multisig-constants.ts index b908bdcf..d9e55641 100644 --- a/packages/contracts/test/multisig-constants.ts +++ b/packages/contracts/test/multisig-constants.ts @@ -15,8 +15,3 @@ export const MULTISIG_INTERFACE = new ethers.utils.Interface([ export const UPDATE_MULTISIG_SETTINGS_PERMISSION_ID = ethers.utils.id( 'UPDATE_MULTISIG_SETTINGS_PERMISSION' ); - -export type MultisigSettings = { - minApprovals: number; - onlyListed: boolean; -}; diff --git a/packages/contracts/test/test-utils/protocol-version.ts b/packages/contracts/test/test-utils/protocol-version.ts deleted file mode 100644 index 5ddbe0c3..00000000 --- a/packages/contracts/test/test-utils/protocol-version.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {version} from '../../package.json'; - -/** - * Returns the NPM version number from the `osx` package.json file - */ -// TODO This will be refactored as part of Task OS-1093. -export function osxContractsVersion(): [number, number, number] { - const trimmedVersion = version.split('-')[0]; - const semver = trimmedVersion.split('.'); - return [Number(semver[0]), Number(semver[1]), Number(semver[2])]; -} diff --git a/packages/contracts/test/test-utils/storage.ts b/packages/contracts/test/test-utils/storage.ts deleted file mode 100644 index 543c0fa1..00000000 --- a/packages/contracts/test/test-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 ethers.provider - .getStorageAt(contractAddress, location) - .then(encoded => defaultAbiCoder.decode(types, encoded)[0]); -} diff --git a/packages/contracts/test/test-utils/typechain-versions.ts b/packages/contracts/test/test-utils/typechain-versions.ts index e9f430bd..34988e62 100644 --- a/packages/contracts/test/test-utils/typechain-versions.ts +++ b/packages/contracts/test/test-utils/typechain-versions.ts @@ -2,8 +2,8 @@ /// The version specified in src is the factory and contract without the version number. /// Import as needed in the test files, and use the correct version of the contract. -export {Multisig__factory as Multisig_V1_0_0__factory} from '../../typechain/factories/@aragon/osx-v1.0.0/plugins/governance/multisig/Multisig__factory'; -export {Multisig__factory as Multisig_V1_3_0__factory} from '../../typechain/factories/@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig__factory'; +export {Multisig__factory as Multisig_V1_1__factory} from '../../typechain/factories/@aragon/osx-v1.0.0/plugins/governance/multisig/Multisig__factory'; +export {Multisig__factory as Multisig_V1_2__factory} from '../../typechain/factories/@aragon/osx-v1.3.0/plugins/governance/multisig/Multisig__factory'; export {Multisig__factory} from '../../typechain/factories/src/Multisig__factory'; export {Multisig} from '../../typechain/src/Multisig'; export {IMultisig} from '../../typechain/src/IMultisig'; diff --git a/packages/contracts/test/test-utils/uups-upgradeable.ts b/packages/contracts/test/test-utils/uups-upgradeable.ts index a3b43293..80183815 100644 --- a/packages/contracts/test/test-utils/uups-upgradeable.ts +++ b/packages/contracts/test/test-utils/uups-upgradeable.ts @@ -1,15 +1,21 @@ -import {readStorage, ERC1967_IMPLEMENTATION_SLOT} from './storage'; import {DAO, PluginRepo} from '@aragon/osx-ethers'; +import {defaultAbiCoder} from '@ethersproject/abi'; import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers'; import {expect} from 'chai'; import {Contract, ContractFactory, errors} from 'ethers'; -import {upgrades} from 'hardhat'; +import {ethers, upgrades} from 'hardhat'; // The protocol version number of contracts not having a `getProtocolVersion()` function because they don't inherit from `ProtocolVersion.sol` yet. export const IMPLICIT_INITIAL_PROTOCOL_VERSION: [number, number, number] = [ 1, 0, 0, ]; +// 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; + // Deploys a proxy and a new implementation from the same factory and checks that the upgrade works. export async function deployAndUpgradeSelfCheck( deployer: SignerWithAddress, @@ -70,22 +76,20 @@ export async function deployAndUpgradeSelfCheck( const toImplementation = (await factory.deploy()).address; // Confirm that the two implementations are different - const fromImplementation = await readStorage( - proxy.address, - ERC1967_IMPLEMENTATION_SLOT, - ['address'] - ); + + const fromImplementation = await ethers.provider + .getStorageAt(proxy.address, ERC1967_IMPLEMENTATION_SLOT) + .then(encoded => defaultAbiCoder.decode(['address'], encoded)[0]); + expect(toImplementation).to.not.equal(fromImplementation); // Upgrade from the old to the new implementation await proxy.connect(upgrader).upgradeTo(toImplementation); // Confirm that the proxy points to the new implementation - const implementationAfterUpgrade = await readStorage( - proxy.address, - ERC1967_IMPLEMENTATION_SLOT, - ['address'] - ); + const implementationAfterUpgrade = await ethers.provider + .getStorageAt(proxy.address, ERC1967_IMPLEMENTATION_SLOT) + .then(encoded => defaultAbiCoder.decode(['address'], encoded)[0]); expect(implementationAfterUpgrade).to.equal(toImplementation); } @@ -116,11 +120,9 @@ export async function deployAndUpgradeFromToCheck( } ); - const fromImplementation = await readStorage( - proxy.address, - ERC1967_IMPLEMENTATION_SLOT, - ['address'] - ); + const fromImplementation = await ethers.provider + .getStorageAt(proxy.address, ERC1967_IMPLEMENTATION_SLOT) + .then(encoded => defaultAbiCoder.decode(['address'], encoded)[0]); // Grant the upgrade permission const grantArgs: [string, string, string] = [ @@ -159,11 +161,9 @@ export async function deployAndUpgradeFromToCheck( constructorArgs: [], }); - const toImplementation = await readStorage( - proxy.address, - ERC1967_IMPLEMENTATION_SLOT, - ['address'] - ); + const toImplementation = await ethers.provider + .getStorageAt(proxy.address, ERC1967_IMPLEMENTATION_SLOT) + .then(encoded => defaultAbiCoder.decode(['address'], encoded)[0]); return {proxy, fromImplementation, toImplementation}; } From 0d935474eaf7355eb4869b50bf1a6ee1a5af7abe Mon Sep 17 00:00:00 2001 From: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:05:48 +0100 Subject: [PATCH 06/17] doc: update README (#3) * doc: update readme * build: adapated package names --- README.md | 26 ++++++++++++++++--- package.json | 2 +- .../40_conclude/41_transfer_ownership.ts | 2 +- packages/contracts/package.json | 4 +-- packages/subgraph/package.json | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 0fba72a0..49ea2a21 100644 --- a/README.md +++ b/README.md @@ -171,15 +171,29 @@ Deploy the contracts to the local Hardhat Network (being forked from the network yarn deploy --tags CreateRepo,NewVersion ``` -This will create a plugin repo and publish the the first version (`v1.1`) of your plugin. +This will create a plugin repo and publish the first version (`v1.1`) of your plugin. +By adding the tag `TransferOwnershipToManagmentDao`, the `ROOT_PERMISSION_ID`, `MAINTAINER_PERMISSION_ID`, and +`UPGRADE_REPO_PERMISSION_ID` are granted to the management DAO and revoked from the deployer. +You can do this directly -Deploy the contracts to sepolia with +```sh +yarn deploy --tags CreateRepo,NewVersion,TransferOwnershipToManagmentDao +``` + +or at a later point by executing ```sh -yarn deploy --network sepolia --tags CreateRepo,NewVersion,Verification +yarn deploy --tags TransferOwnershipToManagmentDao ``` -This will create a plugin repo, publish the the first version (`v1.1`) of your plugin, and verfiy the contracts on sepolia. +To deploy the contracts to a production network use the `--network` option, for example + +```sh +yarn deploy --network sepolia --tags CreateRepo,NewVersion,TransferOwnershipToManagmentDao,Verification +``` + +This will create a plugin repo, publish the first version (`v1.1`) of your plugin, transfer permissions to the +management DAO, and lastly verfiy the contracts on sepolia. If you want to deploy a new version of your plugin afterwards (e.g., `1.2`), simply change the `VERSION` entry in the `packages/contracts/plugin-settings.ts` file and use @@ -187,6 +201,8 @@ If you want to deploy a new version of your plugin afterwards (e.g., `1.2`), sim yarn deploy --network sepolia --tags NewVersion,Verification ``` +Note, that if the deploying account doesn't own the repo anymore, this will create a `createVersionProposalData-sepolia.json` containing the data for a management DAO signer to create a proposal publishing a new version. + Note, that if you include the `CreateRepo` tag after you've created your plugin repo already, this part of the script will be skipped. #### Upgrading Your Plugin Repository @@ -206,6 +222,8 @@ yarn deploy --network sepolia --tags UpgradeRepo This will upgrade your plugin repo to the latest Aragon OSx protocol version implementation, which might include new features and security updates. **For this to work, make sure that you are using the latest version of [this repository](https://github.com/aragon/osx-plugin-template-hardhat) in your fork.** +Note, that if the deploying account doesn't own the repo anymore, this will create a `upgradeRepoProposalData-sepolia.json` containing the data for a management DAO signer to create a proposal upgrading the repo. + ## Subgraph ### Installing diff --git a/package.json b/package.json index 26e1cb04..26d74775 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@aragon/osx-plugin-template", + "name": "@aragon/multisig-plugin", "description": "A template to fork from when developing an Aragon OSx plugin", "version": "0.0.1-alpha.1", "license": "AGPL-3.0-or-later", diff --git a/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts b/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts index aac32467..935fb83a 100644 --- a/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts +++ b/packages/contracts/deploy/40_conclude/41_transfer_ownership.ts @@ -30,7 +30,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ); const permissions: DAOStructs.MultiTargetPermissionStruct[] = [ - // Grant to the managment DAO + // Grant to the management DAO { operation: Operation.Grant, where: pluginRepo.address, diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 06332257..53244bb6 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,5 +1,5 @@ { - "name": "@aragon/osx-plugin-template-contracts", + "name": "@aragon/multisig-plugin-contracts", "version": "1.0.0", "license": "AGPL-3.0-or-later", "scripts": { @@ -20,8 +20,8 @@ }, "devDependencies": { "@aragon/osx-commons-configs": "0.1.0", - "@aragon/osx-ethers": "1.4.0-alpha.0", "@aragon/osx-commons-sdk": "0.0.1-alpha.5", + "@aragon/osx-ethers": "1.4.0-alpha.0", "@aragon/osx-v1.0.0": "npm:@aragon/osx@1.0.1", "@aragon/osx-v1.3.0": "npm:@aragon/osx@1.3.0", "@ethersproject/abi": "^5.7.0", diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index 6fd0d40e..bc4571ed 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -1,5 +1,5 @@ { - "name": "@aragon/osx-plugin-template-subgraph", + "name": "@aragon/multisig-plugin-subgraph", "version": "1.0.0", "license": "AGPL-3.0-or-later", "scripts": { From 851fc4db7b1c8485b559104b699f5af7eee8d1c3 Mon Sep 17 00:00:00 2001 From: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:47:03 +0100 Subject: [PATCH 07/17] chore: sync with `osx-plugin-template-hardhat` `develop` branch (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: copied files from the simple-storage-example-plugin repo into package/contracts * set monorepo package.json * fix: CI/CD * init subgraph * fix: CI/CD * feat: CI/CD improvements * feat: cleaned devDependencies * feat: improve CI step description * subgraph bare bone (WIP) * feat: add subpackages and cleaned dependencies * feat: temporary readme * feat: move packages * fix: wrong path * add post install * plugin repo only * switch to osx-ethers * fix missing dir issue * make it build * build 1 * chore: cleaning * feat: deleted contracts-versions * feat: refactoring * feat: refactoring * feat: improvements * feat: refactoring * fix: linting and prettier * feat: print more infos, remove unused function * fix: print more info * More infos * Renaming * More info * fix: test failing because of wrong default test network * feature: add subgraph workflow * fix: rename flows * fix: adapt triggers * fix: install packages first * fix: triggers * feat: one .env file * fix: .env paths * fix: remove redundant checkout * debug: print info * debug: attempt fix * fix: missing env variable * fix: triggers * Removed summaries * WIP * improvements * fix: path * fix: empty json handling * fix: lint warnings * add psp and pluginRepos related mapping WIP * refactor PSP handlers (WIP) * refactor PSP handlers (WIP) * refactor PSP handlers (WIP) * refactor PSP handlers (WIP) * clean up * create common id functions * clean up * Feature: Add js client (#2) * add dummy js-client * update yarn.lock * update package.json * fix formatting * fix comments and add js-client github actions * update js-client test workflow * fix test workflow * fix lint * update workflow * wip * fix comments and add implementation * update yarn lock and remove docs workflow * Update packages/contracts-ethers/package.json Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> * Update packages/js-client/src/context.ts Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> * Update packages/js-client/test/unit/context.test.ts Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> * add tests * fix comments * fix comments * update client-common version * fix lint command * Update packages/js-client/src/internal/modules/methods.ts Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> * update size limit * update gh actions * update gh actions * fix gh actions * add build command * update gh actions * fix gh action * fix lint * fix lint * ignore js-client tests * minor fixes --------- Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> * fix subgraph build * fix lint * map 1 event * chore: bump package versions * feat: added event, tests, and improved signer, renamed plugin * fix: linting and formatting errors * feature: support base goerli network * Add missing migration import for setup processing * feat: added sepolia and base support and fix IPFS returning empty strings (#10) * feat: added sepolia and base support * fix: replaced outdated IPFS server url * fix: empty return value * OS-593: refactors repo for linting and linting during workflows (#11) * chore(OS-593): Removes .editorconfig from project root Prettier uses its .prettierrc as a first priority to configure the formatter * chore(OS-593): Updates node to v18 in gitpod * chore(OS-593): same network for subgraph and contracts Use the same network for subgraph and contracts in the .env.example file * chore(OS-593): configure vscode editor correctly configures vscode to use fixed a tabsize of 4 and adds commitizen extentions into recommendations * chore(OS-593): reconfigures linting system to start from the root all linting commands have to be run from the root, then eslint merges the configs per package and lints those * build(OS-593): refactors package.json and dependency handling moves all linting commands to the root of the repo for proper config merging * chore(OS-593): adds solidity visual developer as recommmended we recommend to use the solidity visual auditor extension for development * style(OS-593): fixes linting issues fixes some linting issues in contracts and js-client * ci(OS-593): refactor workflows and include linting refactors workflows to be easier and includes formatting and linting in all workflows * style(OS-593): apply prettier formatting * ci(OS-593): lint all packages indiviually lints the packages individually and adds step summaries for important steps * ci(OS-593): remove prettier output from summary prettiers output isn't really working with step summary * ci(OS-593): uses now node 18 in workflows * chore(OS-593): fix typo in formatting-linting.yml * docs: added readme (#13) * docs: added readme * docs: fix typos * Os 782 remove the contracts ethers and do rollup in mini sdk (#12) * refactor(OS-782): removes contracts-ethers package Removes a now unecessary contracts-ethers package. js-client creates its own typechain now BREAKING CHANGE: Removal of contracts-ethers * refactor(OS-782): js-client generates typescript js-client doesn't depend on contracts-ethers anymore and creates the typechain typings during its own build * test(OS-782): ganache replacment, optimizations, use native jest replaces depricated ganache setup with hardhat for testing, removes contract interactions where they aren't necessary, upgrades jest by not using tsdx for testing * ci(OS-782): fixes js-client publish and tests workflows corrects reusable worfklow in js-client publishing workflow, adapts js-client-tests to use the new typechain integration properly * build(OS-782): removes contracts-ethers linting command * chore(OS-782): applies prettier formatting * refactor: renamed and refactored files * refactor: skip repo creation if ENS name is already taken * docs: added usage guide and improved comments * ci: fix workflow by removing outdated lint * ci: formatting & linting on every push * docs: improve migration file comment * ci: fix linting workflow * build: use the same typescript version everywhere * docs: improve usage guide * docs: improved usage guide * OS-754 : re-organize entity relations & add testing (#15) * re organize entity relations * add testing for OSx & Plugin * remove extra files * Update Satsuma deploy node url * Clean up * fix lint * fix subgraph scripts * fix subgraph test flow * ci: fixed linting work flow * add comment * OSx: Remove redundant code * flow: build contracts for subgraph testing * flow: build root for subgraph testing * flow: remove comments * subgraph: update test constants and comments * subgraph: refactor & add comments * ci: skip js-client linting * docs: fix typo --------- Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> * fix(js-client): exclude dist from tsconfig.json (#20) * fix(js-client): exclude dist from tsconfig.json not excluding dist would result in an error trying to run tests after the client has been built * ci(js-client): fix missing env in build * chore: added arbitrum and arbitrumGoerli (#18) * chore: added arbitrum and arbitrumGoerli * ci: fix ci pipeline by adding typescript dev dependency * ci: fix pipeline by removing leftover contracts-ethers lint * ci: do a global lint on every push * fix: etherscan arbitrum name * ci: skip js-client linting * build: add cross-env to fix subgraph ci * refactor: add event explicitly * build(js-client): replaces tsdx with dts-cli (#21) * build(js-client): replace tsdx with dts-cli deps(js-client): update to typescript 5.2.2 deps(root): update eslint packages * lint(js-client): fixes linting errors * test(js-client): fixes jest-environment import * style: apply prettier changes * chore: bump OZ dependency (#22) * docs: add readme and usage guide * OS-888 fix: Import Repo address from plugin-info (#26) * fix: Import repo address from plugin-info * fix: fix typo * update plugin-info.json location * update script * update gitignore * update osx-ethers to version 1.3.0 * Update .gitignore Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> * fix import * remove alias import --------- Co-authored-by: Jose Manuel Mariñas Bascoy Co-authored-by: josemarinas <36479864+josemarinas@users.noreply.github.com> Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> * build: clean-up of dependecy and commands (#19) * build: remove docker-related commands * build: remove local subgraphs commands * chore: bump dependencies * build: bump OZ version and fix unmet dependencies * Feature: Add Subgraph step-by-step guide (#25) * add step-by-step guide * fiux lintint * fix comments * fix lint * fix: Remove individual usage * fix: update .env.example to include Sepolia * docs: Improve usage guide * fix: replace arbitrumGoerli and baseGoerli with the sepolia replacements * fix: improve usage format * Apply suggestions from code review Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> * feat: improve usage guide file structure & address comments * fix: remove redundant titles * fix: link dependencies in the usage guide * fix: fix typo * Apply suggestions from code review Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> --------- Co-authored-by: Rekard0 <5880388+Rekard0@users.noreply.github.com> Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> * feat: use `osx-commons-contracts` and `osx-commons-configs` (#28) * build: use ts 5.2.2 * refactor: use osx-commons * refactor: use osx-commons * build: use osx-commons-sdk * build: use osx-commons-sdk * refactor: contract improvements * build: print coverage report * chore: bump osx-commons-sdk version * feat: use the latest osx-commons-contracts * refactor: use osx-commons-configs and deploy script refactor * build: subgraph fix * docs: improve .env example * style: fix prettier * build: use osx-commons * chore: bump subgraph osx-ethers dependency * chore: remove todo * test: remove redundant expect * fix: use unknown instead of any * fix: imports * fix: wrong type * fix: import * fix: import * feat: use typechain binding * refactor: deploy script simplification * refactor: findPluginRepo and domain * refactor: improve code, comments, and tests * refactor: improved helpers * style: fix linting issues * chore: remove todos * revert: deletion of hashHelpers * feat: catch errors properly * refactor: folder structure * refactor: help gh to track files * refactor: help gh to track files * refactor: improve folder structure (#30) * build: remove js client (#31) * ci: clean GH flows and remove gitpod (#32) * ci: improve flows * style: remove unused commitizen config file * build: update yarn.lock * docs: update README.md (#33) * docs: update README.md * doc: improve README and USAGE_GUIDE * doc: minor improvement * feat: use commons subgraph (#29) * use commons subgraph * update events folder structure * fix comments * fix tests * build: update yarn.lock --------- Co-authored-by: Michael Heuer * build: improve package.json files (#34) * feat: improvements surfaced from the admin plugin (#36) * feat: various improvements that surfaced from the admin plugin * build: remove duplicated dependency * ci: use network name env var * style: throw errors for unsupported networks or missing deployments * feat: update the env examples to change the infura api key for the alchemy one * feat: update the hardhat contracts file to use the new commons networks config * feat: update the hardhat config to use the new addRpcUrlToNetwork function from the config commons package * feat: update the config commons dependency version * ci: update the readme to add the new alchemy api key needed * feat: add new commons config version * ci: typo * Update .env.example Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> * Update README.md Co-authored-by: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> * feat: update the api key in the workflows * ci: update readme * feat: bump commons config version on subgraph * feat: update the actions to run over the PR changes instead of the commit changes * ci: comment to check the pipeline * ci: undo the last comment added on the readme * feature: improvements (#44) * doc: added missing comment * build: remove redundant dependency * feat: add a function to generate a random ens, set the ens to the plugin settings * feat: bump commons config version in subgraph * feat: add random name function * refactor: settings --------- Co-authored-by: Claudia * feat: add manifest with psp address for missing networks and add the Plugin address to the json * feat: update the script for getting the repo address * feat: update the readme * feat: update the deployment script to avoid deploying if the plugin address is not set * ci update comment --------- Co-authored-by: Rekard0 <5880388+Rekard0@users.noreply.github.com> Co-authored-by: josemarinas <36479864+josemarinas@users.noreply.github.com> Co-authored-by: Jør∂¡ <4456749+brickpop@users.noreply.github.com> Co-authored-by: Jør∂¡ Co-authored-by: Mathias Scherer Co-authored-by: Aaron Abu Usama <50079365+pythonpete32@users.noreply.github.com> Co-authored-by: Jose Manuel Mariñas Bascoy Co-authored-by: Claudia --- .env.example | 6 +-- .github/workflows/contracts-tests.yml | 6 +-- .github/workflows/subgraph-tests.yml | 4 +- README.md | 9 +++- .../deploy/20_new_version/21_setup.ts | 2 +- .../deploy/30_upgrade_repo/31_upgrade_repo.ts | 4 ++ packages/contracts/hardhat.config.ts | 24 +++++----- packages/contracts/package.json | 2 +- packages/contracts/plugin-settings.ts | 2 + .../20_integration-testing/21_deployment.ts | 2 +- packages/contracts/utils/helpers.ts | 11 +++++ packages/contracts/yarn.lock | 8 ++-- packages/subgraph/manifest/data/arbitrum.json | 18 ++++++++ .../manifest/data/arbitrumGoerli.json | 18 ++++++++ .../manifest/data/arbitrumSepolia.json | 18 ++++++++ .../subgraph/manifest/data/baseGoerli.json | 18 ++++++++ .../subgraph/manifest/data/baseMainnet.json | 18 ++++++++ .../subgraph/manifest/data/baseSepolia.json | 18 ++++++++ packages/subgraph/manifest/data/goerli.json | 7 ++- .../subgraph/manifest/data/localhost.json | 7 ++- packages/subgraph/manifest/data/mainnet.json | 7 ++- packages/subgraph/manifest/data/mumbai.json | 7 ++- packages/subgraph/manifest/data/polygon.json | 7 ++- packages/subgraph/manifest/data/sepolia.json | 7 ++- packages/subgraph/package.json | 2 +- packages/subgraph/scripts/deploy-subgraph.sh | 11 +++++ .../subgraph/scripts/import-plugin-repo.ts | 44 ++++++++++++------- packages/subgraph/yarn.lock | 8 ++-- 28 files changed, 238 insertions(+), 57 deletions(-) create mode 100644 packages/subgraph/manifest/data/arbitrum.json create mode 100644 packages/subgraph/manifest/data/arbitrumGoerli.json create mode 100644 packages/subgraph/manifest/data/arbitrumSepolia.json create mode 100644 packages/subgraph/manifest/data/baseGoerli.json create mode 100644 packages/subgraph/manifest/data/baseMainnet.json create mode 100644 packages/subgraph/manifest/data/baseSepolia.json diff --git a/.env.example b/.env.example index d277cd52..2ffc6856 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,15 @@ # GENERAL ## The network used for testing purposes -NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon", "polygonMumbai", "base", "baseSepolia", "arbitrum", "arbitrumSepolia"] +NETWORK_NAME="sepolia" # ["mainnet", "sepolia", "polygon", "mumbai","baseMainnet", "baseGoerli", "baseSepolia", "arbitrum", "arbitrumSepolia"] # CONTRACTS ## One or multiple hex encoded private keys separated by commas `,` replacing the hardhat default accounts. PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # Default hardhat account 0 private key. DON'T USE FOR DEPLOYMENTS -## Infura credentials -INFURA_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" +## Alchemy RPC endpoint credentials +ALCHEMY_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" ## Gas Reporting REPORT_GAS='true' diff --git a/.github/workflows/contracts-tests.yml b/.github/workflows/contracts-tests.yml index e4b3728b..fb34a28e 100644 --- a/.github/workflows/contracts-tests.yml +++ b/.github/workflows/contracts-tests.yml @@ -2,7 +2,7 @@ name: 'contracts' on: workflow_dispatch: - push: + pull_request: paths: - 'packages/contracts/**' - '.github/workflows/contracts-*.yml' @@ -31,10 +31,10 @@ jobs: - name: 'Build the contracts' run: 'yarn build' env: - INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} - name: 'Test the contracts and generate the coverage report' run: 'yarn coverage' env: NETWORK_NAME: ${{ vars.NETWORK_NAME }} - INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} diff --git a/.github/workflows/subgraph-tests.yml b/.github/workflows/subgraph-tests.yml index dd492b1e..ef013b86 100644 --- a/.github/workflows/subgraph-tests.yml +++ b/.github/workflows/subgraph-tests.yml @@ -2,7 +2,7 @@ name: 'subgraph' on: workflow_dispatch: - push: + pull_request: paths: - 'packages/subgraph/**' - 'packages/contracts/**' @@ -41,7 +41,7 @@ jobs: working-directory: packages/subgraph env: SUBGRAPH_NETWORK_NAME: ${{ vars.SUBGRAPH_NETWORK_NAME }} - INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} - name: 'Test the subgraph' run: yarn test diff --git a/README.md b/README.md index 49ea2a21..cc2fdf04 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ yarn lint To be able to work on the contracts, make sure that you have created an `.env` file from the `.env.example` file and put in the API keys for -- [Infura](https://www.infura.io/) that we use as the web3 provider +- [Alchemy](https://www.alchemy.com) that we use as the web3 provider - [Alchemy Subgraphs](https://www.alchemy.com/subgraphs) that we use as the subgraph provider - the block explorer that you want to use depending on the networks that you want to deploy to @@ -268,6 +268,10 @@ and finally the subgraph itself with yarn build:subgraph ``` +When running `yarn build`, it requires a plugin address, which is obtained from the configuration file located +at `subgraph/manifest/data/.json`, based on the network specified in your `.env` file under the `SUBGRAPH_NETWORK_NAME` variable. +You do not need to provide a plugin address for building or testing purposes, but it becomes necessary when deploying the subgraph. + During development of the subgraph, you might want to clean outdated files that were build, imported, and generated. To do this, run ```sh @@ -300,7 +304,8 @@ yarn coverage ### Deployment -To deploy the subgraph to the subgraph provider, write your intended subgraph name and version into the `SUBGRAPH_NAME` and `SUBGRAPH_VERSION` variables [in the `.env` file that you created in the beginning](environment-variables) and pick a network name `SUBGRAPH_NETWORK_NAME` [being supported by the subgraph provider](https://docs.alchemy.com/reference/supported-subgraph-chains). Then run +To deploy the subgraph to the subgraph provider, write your intended subgraph name and version into the `SUBGRAPH_NAME` and `SUBGRAPH_VERSION` variables [in the `.env` file that you created in the beginning](environment-variables) and pick a network name `SUBGRAPH_NETWORK_NAME` [being supported by the subgraph provider](https://docs.alchemy.com/reference/supported-subgraph-chains). Remember to place correctly the Plugin address on the network you are going to deploy to, you can do that by adding it on `subgraph/manifest/data/.json`. +Then run ```sh yarn deploy diff --git a/packages/contracts/deploy/20_new_version/21_setup.ts b/packages/contracts/deploy/20_new_version/21_setup.ts index a52a1b28..7ef97b63 100644 --- a/packages/contracts/deploy/20_new_version/21_setup.ts +++ b/packages/contracts/deploy/20_new_version/21_setup.ts @@ -21,7 +21,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }); console.log( - `Deployed contract '${PLUGIN_SETUP_CONTRACT_NAME}' at ${res.address}.` + `Deployed '${PLUGIN_SETUP_CONTRACT_NAME}' contract at '${res.address}'` ); }; diff --git a/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts b/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts index 7ca2b168..d8cf19c8 100644 --- a/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts +++ b/packages/contracts/deploy/30_upgrade_repo/31_upgrade_repo.ts @@ -21,6 +21,10 @@ import path from 'path'; type SemVer = [number, number, number]; +/** + * Upgrades the plugin repo to the latest implementation. + * @param {HardhatRuntimeEnvironment} hre + */ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const [deployer] = await hre.ethers.getSigners(); const productionNetworkName: string = getProductionNetworkName(hre); diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index 749b7d34..660e070a 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -1,7 +1,6 @@ import { - NetworkConfigs, - NetworkConfig, - networks, + addRpcUrlToNetwork, + networks as osxCommonsConfigNetworks, SupportedNetworks, } from '@aragon/osx-commons-configs'; import '@nomicfoundation/hardhat-chai-matchers'; @@ -18,14 +17,18 @@ import { HardhatNetworkAccountsUserConfig, HardhatRuntimeEnvironment, } from 'hardhat/types'; +import type {NetworkUserConfig} from 'hardhat/types'; import {resolve} from 'path'; import 'solidity-coverage'; const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || '../../.env'; dotenvConfig({path: resolve(__dirname, dotenvConfigPath), override: true}); -if (!process.env.INFURA_API_KEY) { - throw new Error('INFURA_API_KEY in .env not set'); +// check alchemy Api key existence +if (process.env.ALCHEMY_API_KEY) { + addRpcUrlToNetwork(process.env.ALCHEMY_API_KEY); +} else { + throw new Error('ALCHEMY_API_KEY in .env not set'); } // Fetch the accounts specified in the .env file @@ -67,15 +70,10 @@ function getHardhatNetworkAccountsConfig( return accountsConfig; } -type HardhatNetworksExtension = NetworkConfig & { - accounts?: string[]; -}; - // Add the accounts specified in the `.env` file to the networks from osx-commons-configs -const osxCommonsConfigNetworks: NetworkConfigs = - networks; +const networks: {[index: string]: NetworkUserConfig} = osxCommonsConfigNetworks; for (const network of Object.keys(networks) as SupportedNetworks[]) { - osxCommonsConfigNetworks[network].accounts = specifiedAccounts(); + networks[network].accounts = specifiedAccounts(); } // Extend HardhatRuntimeEnvironment @@ -110,7 +108,7 @@ const config: HardhatUserConfig = { Object.keys(namedAccounts).length ), }, - ...osxCommonsConfigNetworks, + ...networks, }, defaultNetwork: 'hardhat', diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 53244bb6..63ff96a2 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -19,7 +19,7 @@ "@openzeppelin/contracts-upgradeable": "^4.9.5" }, "devDependencies": { - "@aragon/osx-commons-configs": "0.1.0", + "@aragon/osx-commons-configs": "0.4.0", "@aragon/osx-commons-sdk": "0.0.1-alpha.5", "@aragon/osx-ethers": "1.4.0-alpha.0", "@aragon/osx-v1.0.0": "npm:@aragon/osx@1.0.1", diff --git a/packages/contracts/plugin-settings.ts b/packages/contracts/plugin-settings.ts index 18e3ce5a..d481d42c 100644 --- a/packages/contracts/plugin-settings.ts +++ b/packages/contracts/plugin-settings.ts @@ -6,6 +6,8 @@ export const PLUGIN_CONTRACT_NAME = 'Multisig'; export const PLUGIN_SETUP_CONTRACT_NAME = 'MultisigSetup'; export const PLUGIN_REPO_ENS_SUBDOMAIN_NAME = 'multisig'; // 'multisig.plugin.dao.eth' +// Specify the version of your plugin that you are currently working on. The first version is v1.1. +// For more details, visit https://devs.aragon.org/docs/osx/how-it-works/framework/plugin-management/plugin-repo. export const VERSION: VersionTag = { release: 1, // Increment this number ONLY if breaking/incompatible changes were made. Updates between releases are NOT possible. build: 3, // Increment this number if non-breaking/compatible changes were made. Updates to newer builds are possible. diff --git a/packages/contracts/test/20_integration-testing/21_deployment.ts b/packages/contracts/test/20_integration-testing/21_deployment.ts index eb6509e9..2e38b381 100644 --- a/packages/contracts/test/20_integration-testing/21_deployment.ts +++ b/packages/contracts/test/20_integration-testing/21_deployment.ts @@ -101,7 +101,7 @@ async function fixture(): Promise { await deployments.fixture(tags); const [deployer] = await ethers.getSigners(); - // Plugin repo + // Plugin Repo const {pluginRepo, ensDomain} = await findPluginRepo(env); if (pluginRepo === null) { throw `PluginRepo '${ensDomain}' does not exist yet.`; diff --git a/packages/contracts/utils/helpers.ts b/packages/contracts/utils/helpers.ts index 6b13eb8d..47fe645f 100644 --- a/packages/contracts/utils/helpers.ts +++ b/packages/contracts/utils/helpers.ts @@ -251,5 +251,16 @@ export async function createVersion( return tx; } +export function generateRandomName(length: number): string { + const allowedCharacters = 'abcdefghijklmnopqrstuvwxyz-0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += allowedCharacters.charAt( + Math.floor(Math.random() * allowedCharacters.length) + ); + } + return result; +} + export const AragonOSxAsciiArt = " ____ _____ \n /\\ / __ \\ / ____| \n / \\ _ __ __ _ __ _ ___ _ __ | | | | (_____ __ \n / /\\ \\ | '__/ _` |/ _` |/ _ \\| '_ \\ | | | |\\___ \\ \\/ / \n / ____ \\| | | (_| | (_| | (_) | | | | | |__| |____) > < \n /_/ \\_\\_| \\__,_|\\__, |\\___/|_| |_| \\____/|_____/_/\\_\\ \n __/ | \n |___/ \n"; diff --git a/packages/contracts/yarn.lock b/packages/contracts/yarn.lock index 1a23906f..4221ca0b 100644 --- a/packages/contracts/yarn.lock +++ b/packages/contracts/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@aragon/osx-commons-configs@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.1.0.tgz#21bbc5a964eb144e30033a44cc352d35c62982f9" - integrity sha512-qTs/loihwqALBGmhZngORb+p7pjuQJY5UEd8TLNiEW/BGHEpAJPp4GeQu7GSnigRGEKWpPD5W96kfEsaPtLkuQ== +"@aragon/osx-commons-configs@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.4.0.tgz#5b6ae025de1ccf7f9a135bfbcb0aa822c774acf9" + integrity sha512-/2wIQCbv/spMRdOjRXK0RrXG1TK5aMcbD73RvMgMwQwSrKcA1dCntUuSxmTm2W8eEtOzs8E1VPjqZk0cXL4SSQ== dependencies: tslib "^2.6.2" diff --git a/packages/subgraph/manifest/data/arbitrum.json b/packages/subgraph/manifest/data/arbitrum.json new file mode 100644 index 00000000..0428ea2c --- /dev/null +++ b/packages/subgraph/manifest/data/arbitrum.json @@ -0,0 +1,18 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "arbitrum-one", + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", + "startBlock": 145462184 + } + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } + } +} diff --git a/packages/subgraph/manifest/data/arbitrumGoerli.json b/packages/subgraph/manifest/data/arbitrumGoerli.json new file mode 100644 index 00000000..ab3a4f37 --- /dev/null +++ b/packages/subgraph/manifest/data/arbitrumGoerli.json @@ -0,0 +1,18 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "arbitrum-goerli", + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", + "startBlock": 51930460 + } + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } + } +} diff --git a/packages/subgraph/manifest/data/arbitrumSepolia.json b/packages/subgraph/manifest/data/arbitrumSepolia.json new file mode 100644 index 00000000..a05a2973 --- /dev/null +++ b/packages/subgraph/manifest/data/arbitrumSepolia.json @@ -0,0 +1,18 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "arbitrum-sepolia", + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", + "startBlock": 2827173 + } + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } + } +} diff --git a/packages/subgraph/manifest/data/baseGoerli.json b/packages/subgraph/manifest/data/baseGoerli.json new file mode 100644 index 00000000..63f49b34 --- /dev/null +++ b/packages/subgraph/manifest/data/baseGoerli.json @@ -0,0 +1,18 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "base-testnet", + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0x6095b5004c59301f8Bb98768Bd395d0bc637C893", + "startBlock": 7890981 + } + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } + } +} diff --git a/packages/subgraph/manifest/data/baseMainnet.json b/packages/subgraph/manifest/data/baseMainnet.json new file mode 100644 index 00000000..833132c5 --- /dev/null +++ b/packages/subgraph/manifest/data/baseMainnet.json @@ -0,0 +1,18 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "base", + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0x91a851E9Ed7F2c6d41b15F76e4a88f5A37067cC9", + "startBlock": 2094737 + } + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } + } +} diff --git a/packages/subgraph/manifest/data/baseSepolia.json b/packages/subgraph/manifest/data/baseSepolia.json new file mode 100644 index 00000000..474f7c3a --- /dev/null +++ b/packages/subgraph/manifest/data/baseSepolia.json @@ -0,0 +1,18 @@ +{ + "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", + "network": "base-sepolia", + "dataSources": { + "PluginSetupProcessors": [ + { + "name": "PluginSetupProcessor", + "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", + "startBlock": 3654401 + } + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } + } +} diff --git a/packages/subgraph/manifest/data/goerli.json b/packages/subgraph/manifest/data/goerli.json index 5f82e606..19a0dbb6 100644 --- a/packages/subgraph/manifest/data/goerli.json +++ b/packages/subgraph/manifest/data/goerli.json @@ -8,6 +8,11 @@ "address": "0xE8B5d8D66a02CD1b9Bd32a4064D7ABa45F51305e", "startBlock": 8548226 } - ] + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } } } diff --git a/packages/subgraph/manifest/data/localhost.json b/packages/subgraph/manifest/data/localhost.json index fc78a36a..0f778a14 100644 --- a/packages/subgraph/manifest/data/localhost.json +++ b/packages/subgraph/manifest/data/localhost.json @@ -8,6 +8,11 @@ "address": "0x2B8C4DD137104d1E869105cd0106e7D9EF955BfE", "startBlock": 7727664 } - ] + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } } } diff --git a/packages/subgraph/manifest/data/mainnet.json b/packages/subgraph/manifest/data/mainnet.json index b7969f87..81ffcdeb 100644 --- a/packages/subgraph/manifest/data/mainnet.json +++ b/packages/subgraph/manifest/data/mainnet.json @@ -8,6 +8,11 @@ "address": "0xE978942c691e43f65c1B7c7F8f1dc8cDF061B13f", "startBlock": 16721812 } - ] + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } } } diff --git a/packages/subgraph/manifest/data/mumbai.json b/packages/subgraph/manifest/data/mumbai.json index 9b1a7847..a6e30681 100644 --- a/packages/subgraph/manifest/data/mumbai.json +++ b/packages/subgraph/manifest/data/mumbai.json @@ -8,6 +8,11 @@ "address": "0x9227b311C5cecB416707F1C8B7Ca1b52649AabEc", "startBlock": 33514164 } - ] + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } } } diff --git a/packages/subgraph/manifest/data/polygon.json b/packages/subgraph/manifest/data/polygon.json index 7675c9ba..c831362b 100644 --- a/packages/subgraph/manifest/data/polygon.json +++ b/packages/subgraph/manifest/data/polygon.json @@ -8,6 +8,11 @@ "address": "0x879D9dfe3F36d7684BeC1a2bB4Aa8E8871A7245B", "startBlock": 40817440 } - ] + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } } } diff --git a/packages/subgraph/manifest/data/sepolia.json b/packages/subgraph/manifest/data/sepolia.json index 18a9af1e..87c67210 100644 --- a/packages/subgraph/manifest/data/sepolia.json +++ b/packages/subgraph/manifest/data/sepolia.json @@ -8,6 +8,11 @@ "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", "startBlock": 4415294 } - ] + ], + "Plugin": { + "name": "MyPlugin", + "address": null, + "startBlock": 7727664 + } } } diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index bc4571ed..c173357d 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@aragon/osx-ethers": "1.4.0-alpha.0", - "@aragon/osx-commons-configs": "0.2.0", + "@aragon/osx-commons-configs": "0.4.0", "@graphprotocol/graph-cli": "^0.51.0", "@graphprotocol/graph-ts": "^0.31.0", "cross-env": "^7.0.3", diff --git a/packages/subgraph/scripts/deploy-subgraph.sh b/packages/subgraph/scripts/deploy-subgraph.sh index fd15a08c..69c94067 100755 --- a/packages/subgraph/scripts/deploy-subgraph.sh +++ b/packages/subgraph/scripts/deploy-subgraph.sh @@ -38,6 +38,17 @@ echo '' echo '> Deploying subgraph: '$FULLNAME echo '> Subgraph version: '$SUBGRAPH_VERSION +# check if the repo address is null or zero address +FILE=manifest/data/$SUBGRAPH_NETWORK_NAME'.json' + +address=$(jq -r '.dataSources.Plugin.address' "$FILE") + +if [ "$address" = "null" ] || [ "$address" = "0x0000000000000000000000000000000000000000" ]; + then + echo "Repo address is not set properly, exiting..." + exit -1 +fi + # Deploy subgraph if [ "$LOCAL" ] then diff --git a/packages/subgraph/scripts/import-plugin-repo.ts b/packages/subgraph/scripts/import-plugin-repo.ts index 8f9d655c..59bad356 100644 --- a/packages/subgraph/scripts/import-plugin-repo.ts +++ b/packages/subgraph/scripts/import-plugin-repo.ts @@ -1,29 +1,45 @@ +import {SupportedNetworks} from '@aragon/osx-commons-configs'; import dotenv from 'dotenv'; import fs from 'fs'; import path from 'path'; +const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; + // Specify the path to the .env file at the root const rootDir = path.join(__dirname, '../../../'); // Adjust the path as necessary dotenv.config({path: path.join(rootDir, '.env')}); +// path to the networks manifests +const manifestsPath = path.join(__dirname, '../manifest/data'); -// Extract Repo address from the production-network-deployments.json -function extractAndWriteAddressToTS(jsonPath: string): void { - // Read the production-network-deployments.json file - const aragonDeploymentsInfo = JSON.parse(fs.readFileSync(jsonPath, 'utf8')); - +function extractAndWriteAddressToTS(): void { // Get the network from environment variables const network = process.env.SUBGRAPH_NETWORK_NAME; - // Check if the network is defined in aragonDeploymentsInfo - if (!network || !aragonDeploymentsInfo[network]) { - throw new Error( - `Network '${network}' not found in production-network-deployments.json` + // Check if the network is provided and supported + if ( + !network || + !Object.values(SupportedNetworks).includes(network as SupportedNetworks) + ) { + throw new Error(`Network '${network}' invalid or not Supported.`); + } + + // get the plugin address from the network manifest + const networkManifestPath = path.join(manifestsPath, `${network}.json`); + let networkRepoAddress = JSON.parse( + fs.readFileSync(networkManifestPath, 'utf8') + ).dataSources.Plugin.address; + + // check if address is null and throw warning and continue with zero address + if (!networkRepoAddress) { + console.warn( + '\x1b[33m%s\x1b[0m', + `WARNING: Plugin address for network '${network}' is null. Using zero address.` ); + networkRepoAddress = ZERO_ADDRESS; } - // Start the Map creation code with the specific network address const tsContent: string[] = [ - `export const PLUGIN_REPO_ADDRESS = '${aragonDeploymentsInfo[network].address}';`, + `export const PLUGIN_REPO_ADDRESS = '${networkRepoAddress}';`, ]; const outputDir = path.join(__dirname, '../imported'); @@ -42,8 +58,4 @@ function extractAndWriteAddressToTS(jsonPath: string): void { ); } -const aragonDeploymentsInfoPath = path.join( - rootDir, - 'production-network-deployments.json' -); -extractAndWriteAddressToTS(aragonDeploymentsInfoPath); +extractAndWriteAddressToTS(); diff --git a/packages/subgraph/yarn.lock b/packages/subgraph/yarn.lock index e209c607..7d11039e 100644 --- a/packages/subgraph/yarn.lock +++ b/packages/subgraph/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@aragon/osx-commons-configs@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.2.0.tgz#32f83596f4a2e9e48aef61cf560c1c5b4d32a049" - integrity sha512-wCFtgmuGCzs8L5mCxVCYQ6uEu69IrofS7q2w7E1Fjk7/nWuSmRUpgmif3ki9BQq1qpOvDu2P+u3UNLnIz8J82g== +"@aragon/osx-commons-configs@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-configs/-/osx-commons-configs-0.4.0.tgz#5b6ae025de1ccf7f9a135bfbcb0aa822c774acf9" + integrity sha512-/2wIQCbv/spMRdOjRXK0RrXG1TK5aMcbD73RvMgMwQwSrKcA1dCntUuSxmTm2W8eEtOzs8E1VPjqZk0cXL4SSQ== dependencies: tslib "^2.6.2" From f1ee9243a3425a095eeb8aa011a0a278375bdd0a Mon Sep 17 00:00:00 2001 From: jordaniza Date: Fri, 15 Mar 2024 17:04:36 +0400 Subject: [PATCH 08/17] feat: added pr template from osx --- .github/pull_request_template.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..35441605 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,27 @@ +## Description + +Please include a summary of the change and be sure you follow the contributions rules we do provide [here](./CONTRIBUTIONS.md) + +Task ID: [OS-?](https://aragonassociation.atlassian.net/browse/OS-?) + +## Type of change + +See the framework lifecylce in `packages/contracts/docs/framework-lifecycle` to decide what kind of change this pull request is. + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + +## Checklist: + +- [ ] I have selected the correct base branch. +- [ ] I have performed a self-review of my own code. +- [ ] I have commented my code, particularly in hard-to-understand areas. +- [ ] My changes generate no new warnings. +- [ ] Any dependent changes have been merged and published in downstream modules. +- [ ] I ran all tests with success and extended them if necessary. +- [ ] I have updated the `CHANGELOG.md` file in the root folder. +- [ ] I have updated the `DEPLOYMENT_CHECKLIST` file in the root folder. +- [ ] I have updated the `UPDATE_CHECKLIST` file in the root folder. From d1fbda0fe7e8f521679669ecd8f5b51e37284a4f Mon Sep 17 00:00:00 2001 From: jordaniza Date: Fri, 15 Mar 2024 17:19:56 +0400 Subject: [PATCH 09/17] fix: missing contributions guide --- .github/pull_request_template.md | 2 +- CONTRIBUTIONS.md | 129 +++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTIONS.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 35441605..acfd1510 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,6 @@ ## Description -Please include a summary of the change and be sure you follow the contributions rules we do provide [here](./CONTRIBUTIONS.md) +Please include a summary of the change and be sure you follow the contributions rules we do provide [here](../CONTRIBUTIONS.md) Task ID: [OS-?](https://aragonassociation.atlassian.net/browse/OS-?) diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md new file mode 100644 index 00000000..1ca43637 --- /dev/null +++ b/CONTRIBUTIONS.md @@ -0,0 +1,129 @@ +# Contribution Guide + +--- + +Thank you for being interested in contributing to Aragon! 🎉  We’re excited to have you building this vision with us. + +There are many ways to contribute! Some ideas are: + +- writing tutorials, blog posts or improving the documentation, +- submitting bug reports, +- submitting feature requests, +- refactoring code, +- or even writing new functionality to incorporate into the project. + +All community members are expected to adhere to our community guidelines. Please familiarize yourself with our [Terms and Conditions](https://aragon.org/terms-and-conditions) to ensure a welcoming and friendly environment across all our platforms. Adhering to these principles is essential. 💙 + +## Your first contribution + +Unsure where to begin contributing to Aragon? + +You can start with a [Good First Issue](https://github.com/aragon/osx/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). + +> Good first issues are usually for small features, additional tests, spelling / grammar fixes, formatting changes, or other clean up. + +Start small, pick a subject you care about, are familiar with, or want to learn. + +If you're not already familiar with git or Github, here are a couple of friendly tutorials: [First Contributions](https://github.com/firstcontributions/first-contributions), [Open Source Guide](https://opensource.guide/), and [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). + +Otherwise, you can also contribute through improving our documentation. You can find our documentation within the `docs` folder of each of our repositories. If you see something that could be better explained, or if you want to share better resources for each of the topics, we’d love to hear from you. Feel free to either submit a PR directly to the repo or simply [register here](https://aragonteam.typeform.com/to/QJyKtESU) and we’ll reach out. + +## How to file an issue or report a bug + +If you see a problem, you can report it in our [issue tracker](https://github.com/aragon/osx/issues) (or [here](https://aragonteam.typeform.com/to/QJyKtESU)). + +Please take a quick look to see if the issue doesn't already exist before filing yours. + +Do your best to include as many details as needed in order for someone else to fix the problem and resolve the issue. + +Some things that help us better understand: + +- Device: which device are you using, which version, how old is it, etc +- Browser: which browser are you using, which version.. +- Wallet: which wallet do you use to sign transactions +- Network: which network have you been testing on +- Logs: any specific transaction error or log that may support us in reviewing the error + +### If you find a security vulnerability, do NOT open an issue. Email [security@aragon.org](mailto:security@aragon.org) instead. + +In order to determine whether you are dealing with a security issue, ask yourself these two questions: + +- Can I access or steal something that's not mine, or access something I shouldn't have access to? +- Can I disable something for other people? + +If the answer to either of those two questions are "yes", then you're probably dealing with a security issue. Note that even if you answer "no" to both questions, you may still be dealing with a security issue, so if you're unsure, please send a email. + +## Fixing issues + +1. [Find an issue](https://github.com/aragon/osx/issues) that you are interested in. + - You may want to ask on the GitHub issue or in our [developer channel](https://discord.com/channels/672466989217873929/742442842474938478) if anyone has already started working on the issue. +2. Fork and clone a local copy of the repository. +3. Make the appropriate changes for the issue you are trying to address or the feature that you want to add. +4. Push the changes to the remote repository. +5. Submit a pull request to our repository on Github, explaining any changes and further questions you may have. + + We kindly ask that every PR follows this format and checks all checkboxes. + + ```markdown + ## Description + + ## Type of change + + ## Checklist + + [ ] I have selected the correct base branch. + [ ] I have performed a self-review of my own code. + [ ] I have commented my code, particularly in hard-to-understand areas. + [ ] I have made corresponding changes to the documentation. + [ ] My changes generate no new warnings. + [ ] Any dependent changes have been merged and published in downstream modules. + [ ] I ran all tests with success and extended them if necessary. + [ ] I have updated the CHANGELOG.md file in the root folder of the package after the [UPCOMING] title and before the latest version. + [ ] I have tested my code on the test network. + ``` + +6. Wait for the pull request to be reviewed by the team. +7. Make adjustments to the pull request if they were requested by the maintainer. +8. Celebrate your success after your pull request is merged! You will be able to claim your POAP for the hard work and join our Developer Inner Circle community. + +**Disclaimer** + +It's OK if your pull request is not perfect (no pull request is). The reviewer will be able to help you address any issues and improve it. + +### Tips and Tricks + +Windows users may need to install the [windows-build-tools](https://www.npmjs.com/package/windows-build-tools) or [Windows Subsystems for Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux) before installing this project's dependencies. + +### Documentation + +The documentation on how to run the code locally will be found within the `docs` folder of every repository. + +You can also access our [Developer Portal](https://devs.aragon.org) where you’ll learn about our architecture, how it works, and useful tutorials to get you started. + +### Style guide and development processes + +You can find our documentation [Style Guide here](https://www.notion.so/Documentation-Style-Guide-07c88cec18114b0aac88e8f0ba289976). + +For the frontends, we use [prettier](https://prettier.io/) and [eslint](https://eslint.org/) to automatically lint and format the project. + +For the contracts, we use [eth-lint](https://github.com/duaraghav8/Ethlint) and [prettier](https://prettier.io/) to automatically lint the project. + +For the SDK, we use [Prettier JS/TS formatter](https://prettier.io/docs/en/editors.html). + +Handy `npm` scripts (usually `npm run lint`) are provided at all levels to help automate these checks. + +We generally avoid adding external dependencies if they can be ported over easily, due to numerous NPM-related security issues in the past (e.g. `[event-stream](https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident)`). + +### Git branch convention + +Due to the unconventional release process of smart contracts, this repo utilizes a slightly different flow of git. + +The main challenge is that smart contracts should be heavily scrutinized before a release, making the process cumbersome and unlike the release process for "normal" dependencies or apps. [See here](https://forum.aragon.org/t/git-branch-convention-for-aragon-repos/298/3) for a more detailed explanation. + +Thus, we use the following convention: any change that can be release immediately, base it on the [develop branch](https://github.com/aragon/osx/tree/develop). + +As `next` becomes ready, merge `next` onto `master` with a rebase. + +## Community + +If you need help, please reach out to Aragon core contributors and community members in our [Discord](https://discord.gg/aragon-672466989217873929). We'd love to hear from you and know what you're working on! From 4a7505ea3917c1cc22ea5972b65d731b43f735f4 Mon Sep 17 00:00:00 2001 From: jordaniza Date: Fri, 15 Mar 2024 17:27:37 +0400 Subject: [PATCH 10/17] docs: added a line to PR template and changed contribution urls --- .github/pull_request_template.md | 1 + CONTRIBUTIONS.md | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index acfd1510..82266ba3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -25,3 +25,4 @@ See the framework lifecylce in `packages/contracts/docs/framework-lifecycle` to - [ ] I have updated the `CHANGELOG.md` file in the root folder. - [ ] I have updated the `DEPLOYMENT_CHECKLIST` file in the root folder. - [ ] I have updated the `UPDATE_CHECKLIST` file in the root folder. +- [ ] I have updated the Subgraph and added a QA URL to the description of this PR. diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index 1ca43637..c46e84a7 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -18,7 +18,7 @@ All community members are expected to adhere to our community guidelines. Please Unsure where to begin contributing to Aragon? -You can start with a [Good First Issue](https://github.com/aragon/osx/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). +You can start with a [Good First Issue](https://github.com/aragon/multisig-plugin/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). > Good first issues are usually for small features, additional tests, spelling / grammar fixes, formatting changes, or other clean up. @@ -30,7 +30,7 @@ Otherwise, you can also contribute through improving our documentation. You can ## How to file an issue or report a bug -If you see a problem, you can report it in our [issue tracker](https://github.com/aragon/osx/issues) (or [here](https://aragonteam.typeform.com/to/QJyKtESU)). +If you see a problem, you can report it in our [issue tracker](https://github.com/aragon/multisig-plugin/issues) (or [here](https://aragonteam.typeform.com/to/QJyKtESU)). Please take a quick look to see if the issue doesn't already exist before filing yours. @@ -55,7 +55,7 @@ If the answer to either of those two questions are "yes", then you're probably d ## Fixing issues -1. [Find an issue](https://github.com/aragon/osx/issues) that you are interested in. +1. [Find an issue](https://github.com/aragon/multisig-plugin/issues) that you are interested in. - You may want to ask on the GitHub issue or in our [developer channel](https://discord.com/channels/672466989217873929/742442842474938478) if anyone has already started working on the issue. 2. Fork and clone a local copy of the repository. 3. Make the appropriate changes for the issue you are trying to address or the feature that you want to add. @@ -120,7 +120,7 @@ Due to the unconventional release process of smart contracts, this repo utilizes The main challenge is that smart contracts should be heavily scrutinized before a release, making the process cumbersome and unlike the release process for "normal" dependencies or apps. [See here](https://forum.aragon.org/t/git-branch-convention-for-aragon-repos/298/3) for a more detailed explanation. -Thus, we use the following convention: any change that can be release immediately, base it on the [develop branch](https://github.com/aragon/osx/tree/develop). +Thus, we use the following convention: any change that can be release immediately, base it on the [develop branch](https://github.com/aragon/multisig-plugin/tree/develop). As `next` becomes ready, merge `next` onto `master` with a rebase. From 1ada3d526709c1e07c43f5347a8a5dc3831ec5f9 Mon Sep 17 00:00:00 2001 From: Michael Heuer <20623991+Michael-A-Heuer@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:02:57 +0100 Subject: [PATCH 11/17] feature: moved subgraph from OSx (#4) * wip: moved multisig subgraph from OSx * chore: maintained changelog * style: fix constness linter errors * fix: failing tests * refactor: comments and IDs * refactor: constants * doc: comments * doc: remove comment * doc: added comment * style: add empty line at EOF Co-authored-by: Rekard0 <5880388+Rekard0@users.noreply.github.com> * refactor: move ids into separate file * style: EOF * test: descriptions * test: descriptions * style: prettier * fix: missing import * refactor: variable name * refactor: rename dao plugin * fix: remove merge conflict artifact * fix: correct entities * chore: put deployment addresses * fix: use pluginEntityId (the plugin address as a hex string) * style: fix linter error * fix: changed plugin Id generation and added assemblyscript types * fix: wrong manifest names * fix: iplugin schema * fix: second schema fix * fix: initialize proposalCount with zero * fix: improve manifests * fix: adapt deploy script --------- Co-authored-by: Rekard0 <5880388+Rekard0@users.noreply.github.com> Co-authored-by: jordaniza --- packages/subgraph/CHANGELOG.md | 12 + packages/subgraph/manifest/data/arbitrum.json | 10 +- .../manifest/data/arbitrumGoerli.json | 18 - .../manifest/data/arbitrumSepolia.json | 10 +- .../subgraph/manifest/data/baseGoerli.json | 18 - .../subgraph/manifest/data/baseMainnet.json | 10 +- .../subgraph/manifest/data/baseSepolia.json | 10 +- packages/subgraph/manifest/data/goerli.json | 18 - .../subgraph/manifest/data/localhost.json | 12 +- packages/subgraph/manifest/data/mainnet.json | 10 +- packages/subgraph/manifest/data/mumbai.json | 10 +- packages/subgraph/manifest/data/polygon.json | 10 +- packages/subgraph/manifest/data/sepolia.json | 10 +- .../manifest/subgraph.placeholder.yaml | 29 +- packages/subgraph/schema.graphql | 88 ++- packages/subgraph/scripts/deploy-subgraph.sh | 2 +- .../subgraph/scripts/import-plugin-repo.ts | 2 +- .../subgraph/src/osx/pluginSetupProcessor.ts | 36 +- packages/subgraph/src/plugin/id.ts | 19 + packages/subgraph/src/plugin/plugin.ts | 210 ++++++- .../tests/osx/pluginSetupProcessor.test.ts | 20 +- packages/subgraph/tests/plugin/plugin.test.ts | 538 ++++++++++++++++-- .../subgraph/tests/utils/events/plugin.ts | 336 ++++++++++- packages/subgraph/tsconfig.json | 3 +- packages/subgraph/types/ASM.d.ts | 14 + 25 files changed, 1238 insertions(+), 217 deletions(-) create mode 100644 packages/subgraph/CHANGELOG.md delete mode 100644 packages/subgraph/manifest/data/arbitrumGoerli.json delete mode 100644 packages/subgraph/manifest/data/baseGoerli.json delete mode 100644 packages/subgraph/manifest/data/goerli.json create mode 100644 packages/subgraph/src/plugin/id.ts create mode 100644 packages/subgraph/types/ASM.d.ts diff --git a/packages/subgraph/CHANGELOG.md b/packages/subgraph/CHANGELOG.md new file mode 100644 index 00000000..ee9f278f --- /dev/null +++ b/packages/subgraph/CHANGELOG.md @@ -0,0 +1,12 @@ +# Aragon OSx Subgraph + +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). + +## 1.0.0 + +### Added + +- Copied files from [aragon/osx commit 19f4c4](https://github.com/aragon/osx/commit/19f4c42b5ae86b10b3a9cbbcaca6af504b69b0d2) diff --git a/packages/subgraph/manifest/data/arbitrum.json b/packages/subgraph/manifest/data/arbitrum.json index 0428ea2c..fcb806bb 100644 --- a/packages/subgraph/manifest/data/arbitrum.json +++ b/packages/subgraph/manifest/data/arbitrum.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "arbitrum-one", + "PluginRepo": { + "address": "0x7553E6Fb020c5740768cF289e603770AA09b7aE2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", "startBlock": 145462184 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/arbitrumGoerli.json b/packages/subgraph/manifest/data/arbitrumGoerli.json deleted file mode 100644 index ab3a4f37..00000000 --- a/packages/subgraph/manifest/data/arbitrumGoerli.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "arbitrum-goerli", - "dataSources": { - "PluginSetupProcessors": [ - { - "name": "PluginSetupProcessor", - "address": "0x308a1DC5020c4B5d992F5543a7236c465997fecB", - "startBlock": 51930460 - } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } - } -} diff --git a/packages/subgraph/manifest/data/arbitrumSepolia.json b/packages/subgraph/manifest/data/arbitrumSepolia.json index a05a2973..86a08943 100644 --- a/packages/subgraph/manifest/data/arbitrumSepolia.json +++ b/packages/subgraph/manifest/data/arbitrumSepolia.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "arbitrum-sepolia", + "PluginRepo": { + "address": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", "startBlock": 2827173 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/baseGoerli.json b/packages/subgraph/manifest/data/baseGoerli.json deleted file mode 100644 index 63f49b34..00000000 --- a/packages/subgraph/manifest/data/baseGoerli.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "base-testnet", - "dataSources": { - "PluginSetupProcessors": [ - { - "name": "PluginSetupProcessor", - "address": "0x6095b5004c59301f8Bb98768Bd395d0bc637C893", - "startBlock": 7890981 - } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } - } -} diff --git a/packages/subgraph/manifest/data/baseMainnet.json b/packages/subgraph/manifest/data/baseMainnet.json index 833132c5..c3a8835d 100644 --- a/packages/subgraph/manifest/data/baseMainnet.json +++ b/packages/subgraph/manifest/data/baseMainnet.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "base", + "PluginRepo": { + "address": "0xcDC4b0BC63AEfFf3a7826A19D101406C6322A585" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x91a851E9Ed7F2c6d41b15F76e4a88f5A37067cC9", "startBlock": 2094737 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/baseSepolia.json b/packages/subgraph/manifest/data/baseSepolia.json index 474f7c3a..e1f711cd 100644 --- a/packages/subgraph/manifest/data/baseSepolia.json +++ b/packages/subgraph/manifest/data/baseSepolia.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "base-sepolia", + "PluginRepo": { + "address": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", "startBlock": 3654401 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/goerli.json b/packages/subgraph/manifest/data/goerli.json deleted file mode 100644 index 19a0dbb6..00000000 --- a/packages/subgraph/manifest/data/goerli.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "goerli", - "dataSources": { - "PluginSetupProcessors": [ - { - "name": "PluginSetupProcessor", - "address": "0xE8B5d8D66a02CD1b9Bd32a4064D7ABa45F51305e", - "startBlock": 8548226 - } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } - } -} diff --git a/packages/subgraph/manifest/data/localhost.json b/packages/subgraph/manifest/data/localhost.json index 0f778a14..8f071725 100644 --- a/packages/subgraph/manifest/data/localhost.json +++ b/packages/subgraph/manifest/data/localhost.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", - "network": "rinkeby", + "network": "sepolia", + "PluginRepo": { + "address": null + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x2B8C4DD137104d1E869105cd0106e7D9EF955BfE", "startBlock": 7727664 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/mainnet.json b/packages/subgraph/manifest/data/mainnet.json index 81ffcdeb..529b8c55 100644 --- a/packages/subgraph/manifest/data/mainnet.json +++ b/packages/subgraph/manifest/data/mainnet.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "mainnet", + "PluginRepo": { + "address": "0x8c278e37D0817210E18A7958524b7D0a1fAA6F7b" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xE978942c691e43f65c1B7c7F8f1dc8cDF061B13f", "startBlock": 16721812 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/mumbai.json b/packages/subgraph/manifest/data/mumbai.json index a6e30681..592f9ba3 100644 --- a/packages/subgraph/manifest/data/mumbai.json +++ b/packages/subgraph/manifest/data/mumbai.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "mumbai", + "PluginRepo": { + "address": "0x2c4690b8be39adAd4F15A69340d5035aC6E53eEF" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x9227b311C5cecB416707F1C8B7Ca1b52649AabEc", "startBlock": 33514164 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/polygon.json b/packages/subgraph/manifest/data/polygon.json index c831362b..3bf8dc75 100644 --- a/packages/subgraph/manifest/data/polygon.json +++ b/packages/subgraph/manifest/data/polygon.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "matic", + "PluginRepo": { + "address": "0x5A5035E7E8aeff220540F383a9cf8c35929bcF31" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0x879D9dfe3F36d7684BeC1a2bB4Aa8E8871A7245B", "startBlock": 40817440 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/data/sepolia.json b/packages/subgraph/manifest/data/sepolia.json index 87c67210..8f321ac4 100644 --- a/packages/subgraph/manifest/data/sepolia.json +++ b/packages/subgraph/manifest/data/sepolia.json @@ -1,6 +1,9 @@ { "info": "# Do not edit subgraph.yaml,this is a generated file. \n# Instead, edit subgraph.placeholder.yaml and run: yarn manifest", "network": "sepolia", + "PluginRepo": { + "address": "0x9e7956C8758470dE159481e5DD0d08F8B59217A2" + }, "dataSources": { "PluginSetupProcessors": [ { @@ -8,11 +11,6 @@ "address": "0xC24188a73dc09aA7C721f96Ad8857B469C01dC9f", "startBlock": 4415294 } - ], - "Plugin": { - "name": "MyPlugin", - "address": null, - "startBlock": 7727664 - } + ] } } diff --git a/packages/subgraph/manifest/subgraph.placeholder.yaml b/packages/subgraph/manifest/subgraph.placeholder.yaml index 2de5289f..931951b1 100644 --- a/packages/subgraph/manifest/subgraph.placeholder.yaml +++ b/packages/subgraph/manifest/subgraph.placeholder.yaml @@ -19,9 +19,7 @@ dataSources: apiVersion: 0.0.5 language: wasm/assemblyscript entities: - - PluginInstallation - - PluginPreparation - - PluginPermission + - MultisigPlugin abis: - name: PluginSetupProcessor file: ./imported/PluginSetupProcessor.json @@ -32,7 +30,7 @@ dataSources: {{/dataSources.PluginSetupProcessors}} # templates templates: - # Plugin (package) + # Plugin - name: Plugin kind: ethereum/contract network: {{network}} @@ -43,12 +41,25 @@ templates: apiVersion: 0.0.5 language: wasm/assemblyscript entities: - - Dao + - Action + - MultisigPlugin + - MultisigApprover + - MultisigProposal + - MultisigProposalApprover abis: - name: Plugin - file: $PLUGIN_MODULE/contracts/artifacts/src/MyPlugin.sol/MyPlugin.json + file: $PLUGIN_MODULE/contracts/artifacts/src/Multisig.sol/Multisig.json eventHandlers: - - event: NumberStored(uint256) - handler: handleNumberStored + - event: MembersAdded(address[]) + handler: handleMembersAdded + - event: MembersRemoved(address[]) + handler: handleMembersRemoved + - event: Approved(indexed uint256,indexed address) + handler: handleApproved + - event: ProposalCreated(indexed uint256,indexed address,uint64,uint64,bytes,(address,uint256,bytes)[],uint256) + handler: handleProposalCreated + - event: ProposalExecuted(indexed uint256) + handler: handleProposalExecuted + - event: MultisigSettingsUpdated(bool,indexed uint16) + handler: handleMultisigSettingsUpdated file: ./src/plugin/plugin.ts - diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index bb175125..81f412cc 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -1,10 +1,86 @@ +interface IPlugin { + id: ID! # PluginEntityID + daoAddress: Bytes! + pluginAddress: Bytes! +} + # Types -type DaoPlugin @entity { - "OSX related data" - id: ID! # psp installationId - dao: Bytes! +type MultisigPlugin implements IPlugin @entity { + id: ID! # PluginEntityID + daoAddress: Bytes! pluginAddress: Bytes! - "Set plugin specific related data below:" - number: BigInt + # Plugin specific data" + proposalCount: BigInt + proposals: [MultisigProposal!]! @derivedFrom(field: "plugin") + members: [MultisigApprover!]! @derivedFrom(field: "plugin") + + minApprovals: Int + onlyListed: Boolean +} + +interface IProposal { + id: ID! # ProposalEntityId + daoAddress: Bytes! + creator: Bytes! + metadata: String + actions: [Action!]! @derivedFrom(field: "proposal") + allowFailureMap: BigInt! + failureMap: BigInt + executed: Boolean! + createdAt: BigInt! + startDate: BigInt! + endDate: BigInt! + executionTxHash: Bytes +} + +type Action @entity { + id: ID! # ActionEntityId + to: Bytes! + value: BigInt! + data: Bytes! + daoAddress: Bytes! + proposal: IProposal! + execResult: Bytes +} + +type MultisigApprover @entity { + id: ID! # plugin_address + member_address + address: String # address as string to facilitate filtering by address on the UI + proposals: [MultisigProposalApprover!]! @derivedFrom(field: "approver") + plugin: MultisigPlugin! +} + +type MultisigProposalApprover @entity(immutable: true) { + "ApproverProposal for Many-to-Many" + id: ID! # approver + proposal + approver: MultisigApprover! + proposal: MultisigProposal! + createdAt: BigInt! +} + +type MultisigProposal implements IProposal @entity { + id: ID! # plugin + proposalId + daoAddress: Bytes! + actions: [Action!]! @derivedFrom(field: "proposal") + allowFailureMap: BigInt! + failureMap: BigInt + plugin: MultisigPlugin! + pluginProposalId: BigInt! + creator: Bytes! + metadata: String + createdAt: BigInt! + startDate: BigInt! + endDate: BigInt! + creationBlockNumber: BigInt! + snapshotBlock: BigInt! + minApprovals: Int! + approvals: Int + approvalReached: Boolean! + isSignaling: Boolean! + executed: Boolean! + executionDate: BigInt + executionBlockNumber: BigInt + executionTxHash: Bytes + approvers: [MultisigProposalApprover!]! @derivedFrom(field: "proposal") } diff --git a/packages/subgraph/scripts/deploy-subgraph.sh b/packages/subgraph/scripts/deploy-subgraph.sh index 69c94067..3f7a9db3 100755 --- a/packages/subgraph/scripts/deploy-subgraph.sh +++ b/packages/subgraph/scripts/deploy-subgraph.sh @@ -41,7 +41,7 @@ echo '> Subgraph version: '$SUBGRAPH_VERSION # check if the repo address is null or zero address FILE=manifest/data/$SUBGRAPH_NETWORK_NAME'.json' -address=$(jq -r '.dataSources.Plugin.address' "$FILE") +address=$(jq -r '.PluginRepo.address' "$FILE") if [ "$address" = "null" ] || [ "$address" = "0x0000000000000000000000000000000000000000" ]; then diff --git a/packages/subgraph/scripts/import-plugin-repo.ts b/packages/subgraph/scripts/import-plugin-repo.ts index 59bad356..b24590fe 100644 --- a/packages/subgraph/scripts/import-plugin-repo.ts +++ b/packages/subgraph/scripts/import-plugin-repo.ts @@ -27,7 +27,7 @@ function extractAndWriteAddressToTS(): void { const networkManifestPath = path.join(manifestsPath, `${network}.json`); let networkRepoAddress = JSON.parse( fs.readFileSync(networkManifestPath, 'utf8') - ).dataSources.Plugin.address; + ).PluginRepo.address; // check if address is null and throw warning and continue with zero address if (!networkRepoAddress) { diff --git a/packages/subgraph/src/osx/pluginSetupProcessor.ts b/packages/subgraph/src/osx/pluginSetupProcessor.ts index ac0aabdf..07aa8319 100644 --- a/packages/subgraph/src/osx/pluginSetupProcessor.ts +++ b/packages/subgraph/src/osx/pluginSetupProcessor.ts @@ -1,9 +1,9 @@ import {InstallationPrepared} from '../../generated/PluginSetupProcessor/PluginSetupProcessor'; -import {DaoPlugin} from '../../generated/schema'; +import {MultisigPlugin} from '../../generated/schema'; import {Plugin as PluginTemplate} from '../../generated/templates'; import {PLUGIN_REPO_ADDRESS} from '../../imported/repo-address'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, DataSourceContext, log} from '@graphprotocol/graph-ts'; +import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; export function handleInstallationPrepared(event: InstallationPrepared): void { const pluginRepo = event.params.pluginSetupRepo; @@ -16,35 +16,27 @@ export function handleInstallationPrepared(event: InstallationPrepared): void { return; } - const dao = event.params.dao; - const plugin = event.params.plugin; - - // Generate a unique ID for the plugin installation. - const installationId = generatePluginInstallationEntityId(dao, plugin); - // Log an error and exit if unable to generate the installation ID. - if (!installationId) { - log.error('Failed to generate installationId', [ - dao.toHexString(), - plugin.toHexString(), - ]); - return; - } + const daoAddress = event.params.dao; + const pluginAddress = event.params.plugin; + const pluginId = generatePluginEntityId(pluginAddress); + // Load or create a new entry for the this plugin using the generated installation ID. - let pluginEntity = DaoPlugin.load(installationId!); + let pluginEntity = MultisigPlugin.load(pluginId); if (!pluginEntity) { - pluginEntity = new DaoPlugin(installationId!); + pluginEntity = new MultisigPlugin(pluginId); } // Set the DAO and plugin address for the plugin entity. - pluginEntity.dao = dao; - pluginEntity.pluginAddress = plugin; + pluginEntity.daoAddress = daoAddress; + pluginEntity.pluginAddress = pluginAddress; + pluginEntity.proposalCount = BigInt.zero(); // Initialize a context for the plugin data source to enable indexing from the moment of preparation. const context = new DataSourceContext(); // Include the DAO address in the context for future reference. - context.setString('daoAddress', dao.toHexString()); + context.setString('daoAddress', daoAddress.toHexString()); // Deploy a template for the plugin to facilitate individual contract indexing. - PluginTemplate.createWithContext(plugin, context); + PluginTemplate.createWithContext(pluginAddress, context); pluginEntity.save(); } diff --git a/packages/subgraph/src/plugin/id.ts b/packages/subgraph/src/plugin/id.ts new file mode 100644 index 00000000..6f22a779 --- /dev/null +++ b/packages/subgraph/src/plugin/id.ts @@ -0,0 +1,19 @@ +import {generateEntityIdFromAddress} from '@aragon/osx-commons-subgraph'; +import {Address} from '@graphprotocol/graph-ts'; + +export function generateMemberEntityId( + pluginAddress: Address, + memberAddress: Address +): string { + return [ + generateEntityIdFromAddress(pluginAddress), + generateEntityIdFromAddress(memberAddress), + ].join('_'); +} + +export function generateVoterEntityId( + memberEntityId: string, + proposalId: string +): string { + return [memberEntityId, proposalId].join('_'); +} diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index aa004d71..776bee4f 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -1,24 +1,206 @@ -import {DaoPlugin} from '../../generated/schema'; -import {NumberStored} from '../../generated/templates/Plugin/Plugin'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, dataSource} from '@graphprotocol/graph-ts'; +import { + Action, + MultisigPlugin, + MultisigProposal, + MultisigApprover, + MultisigProposalApprover, +} from '../../generated/schema'; +import { + ProposalCreated, + ProposalExecuted, + MembersAdded, + MembersRemoved, + Plugin, + Approved, + MultisigSettingsUpdated, +} from '../../generated/templates/Plugin/Plugin'; +import {generateMemberEntityId, generateVoterEntityId} from './id'; +import { + generateActionEntityId, + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, dataSource, store} from '@graphprotocol/graph-ts'; -export function handleNumberStored(event: NumberStored): void { +export function handleProposalCreated(event: ProposalCreated): void { + const pluginProposalId = event.params.proposalId; const pluginAddress = event.address; + const proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId + ); + const pluginEntityId = generatePluginEntityId(pluginAddress); + + const proposalEntity = new MultisigProposal(proposalEntityId); const context = dataSource.context(); - const daoId = context.getString('daoAddress'); + const daoAddr = Address.fromHexString(context.getString('daoAddress')); - const installationId = generatePluginInstallationEntityId( - Address.fromString(daoId), - pluginAddress - ); + proposalEntity.daoAddress = daoAddr; + proposalEntity.plugin = pluginEntityId; + proposalEntity.pluginProposalId = pluginProposalId; + proposalEntity.creator = event.params.creator; + proposalEntity.metadata = event.params.metadata.toString(); + proposalEntity.createdAt = event.block.timestamp; + proposalEntity.creationBlockNumber = event.block.number; + proposalEntity.startDate = event.params.startDate; + proposalEntity.endDate = event.params.endDate; + proposalEntity.allowFailureMap = event.params.allowFailureMap; + + const contract = Plugin.bind(pluginAddress); + + const proposal = contract.try_getProposal(pluginProposalId); + if (!proposal.reverted) { + proposalEntity.executed = proposal.value.value0; + proposalEntity.approvals = proposal.value.value1; + + // ProposalParameters + const parameters = proposal.value.value2; + proposalEntity.minApprovals = parameters.minApprovals; + proposalEntity.snapshotBlock = parameters.snapshotBlock; + proposalEntity.approvalReached = false; + + // Actions + const actions = proposal.value.value3; + for (let index = 0; index < actions.length; index++) { + const action = actions[index]; + + const actionId = generateActionEntityId(proposalEntityId, index); + + const actionEntity = new Action(actionId); + actionEntity.to = action.to; + actionEntity.value = action.value; + actionEntity.data = action.data; + actionEntity.daoAddress = daoAddr; + actionEntity.proposal = proposalEntityId; + actionEntity.save(); + } + proposalEntity.isSignaling = actions.length == 0; + } + + proposalEntity.save(); - if (installationId) { - const pluginEntity = DaoPlugin.load(installationId); - if (pluginEntity) { - pluginEntity.number = event.params.number; + // update vote length + const pluginEntity = MultisigPlugin.load(pluginEntityId); + if (pluginEntity) { + const voteLength = contract.try_proposalCount(); + if (!voteLength.reverted) { + pluginEntity.proposalCount = voteLength.value; pluginEntity.save(); } } } + +export function handleApproved(event: Approved): void { + const memberAddress = event.params.approver; + const pluginAddress = event.address; + const memberEntityId = generateMemberEntityId(pluginAddress, memberAddress); + const pluginProposalId = event.params.proposalId; + const proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); + const approverProposalId = generateVoterEntityId( + memberEntityId, + proposalEntityId + ); + + let approverProposalEntity = + MultisigProposalApprover.load(approverProposalId); + if (!approverProposalEntity) { + approverProposalEntity = new MultisigProposalApprover(approverProposalId); + approverProposalEntity.approver = memberEntityId; + approverProposalEntity.proposal = proposalEntityId; + } + approverProposalEntity.createdAt = event.block.timestamp; + approverProposalEntity.save(); + + // update count + const proposalEntity = MultisigProposal.load(proposalEntityId); + if (proposalEntity) { + const contract = Plugin.bind(pluginAddress); + const proposal = contract.try_getProposal(pluginProposalId); + + if (!proposal.reverted) { + const approvals = proposal.value.value1; + proposalEntity.approvals = approvals; + + // calculate if proposal is executable + const minApprovalsStruct = proposal.value.value2; + + if ( + approvals >= minApprovalsStruct.minApprovals && + !proposalEntity.approvalReached + ) { + proposalEntity.approvalReached = true; + } + + proposalEntity.save(); + } + } +} + +export function handleProposalExecuted(event: ProposalExecuted): void { + const pluginProposalId = event.params.proposalId; + const proposalEntityId = generateProposalEntityId( + event.address, + pluginProposalId + ); + + const proposalEntity = MultisigProposal.load(proposalEntityId); + if (proposalEntity) { + proposalEntity.approvalReached = false; + proposalEntity.executed = true; + proposalEntity.executionDate = event.block.timestamp; + proposalEntity.executionBlockNumber = event.block.number; + proposalEntity.executionTxHash = event.transaction.hash; + proposalEntity.save(); + } +} + +export function handleMembersAdded(event: MembersAdded): void { + const memberAddresses = event.params.members; + for (let index = 0; index < memberAddresses.length; index++) { + const pluginEntityId = generatePluginEntityId(event.address); + const memberEntityId = generateMemberEntityId( + event.address, + memberAddresses[index] + ); + + let approverEntity = MultisigApprover.load(memberEntityId); + if (!approverEntity) { + approverEntity = new MultisigApprover(memberEntityId); + approverEntity.address = memberAddresses[index].toHexString(); + approverEntity.plugin = pluginEntityId; + approverEntity.save(); + } + } +} + +export function handleMembersRemoved(event: MembersRemoved): void { + const memberAddresses = event.params.members; + for (let index = 0; index < memberAddresses.length; index++) { + const memberEntityId = generateMemberEntityId( + event.address, + memberAddresses[index] + ); + + const approverEntity = MultisigApprover.load(memberEntityId); + if (approverEntity) { + store.remove('MultisigApprover', memberEntityId); + } + } +} + +export function handleMultisigSettingsUpdated( + event: MultisigSettingsUpdated +): void { + const pluginEntity = MultisigPlugin.load( + generatePluginEntityId(event.address) + ); + if (pluginEntity) { + pluginEntity.onlyListed = event.params.onlyListed; + pluginEntity.minApprovals = event.params.minApprovals; + pluginEntity.save(); + } +} diff --git a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts index 920203aa..4a69b394 100644 --- a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts +++ b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts @@ -12,7 +12,7 @@ import { PLUGIN_SETUP_ID, } from '../utils/constants'; import {createInstallationPreparedEvent} from '../utils/events'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; +import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; import {assert, afterEach, clearStore, test, describe} from 'matchstick-as'; @@ -27,12 +27,11 @@ describe('OSx', () => { // Create event const daoAddress = DAO_ADDRESS; const pluginAddress = CONTRACT_ADDRESS; - const installationId = generatePluginInstallationEntityId( - Address.fromString(daoAddress), + const pluginEntityId = generatePluginEntityId( Address.fromString(pluginAddress) ); - if (!installationId) { - throw new Error('Failed to get installationId'); + if (!pluginEntityId) { + throw new Error('Failed to get pluginEntityId'); } const setupId = PLUGIN_SETUP_ID; const versionTuple = new ethereum.Tuple(); @@ -76,10 +75,13 @@ describe('OSx', () => { handleInstallationPrepared(event1); - assert.notInStore('DaoPlugin', installationId!); - assert.entityCount('DaoPlugin', 0); + assert.notInStore('MultisigPlugin', pluginEntityId!); + assert.entityCount('MultisigPlugin', 0); const thisPluginRepoAddress = PLUGIN_REPO_ADDRESS; + const pluginId = generatePluginEntityId( + Address.fromString(pluginAddress) + ); const event2 = createInstallationPreparedEvent( ADDRESS_THREE, @@ -95,8 +97,8 @@ describe('OSx', () => { handleInstallationPrepared(event2); - assert.entityCount('DaoPlugin', 1); - assert.fieldEquals('DaoPlugin', installationId!, 'id', installationId!); + assert.entityCount('MultisigPlugin', 1); + assert.fieldEquals('MultisigPlugin', pluginId, 'id', pluginId); }); }); }); diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 14db4cc4..25ab5e7d 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -1,18 +1,69 @@ -import {DaoPlugin} from '../../generated/schema'; -import {handleNumberStored} from '../../src/plugin/plugin'; -import {CONTRACT_ADDRESS, DAO_ADDRESS} from '../utils/constants'; -import {createNewNumberStoredEvent} from '../utils/events'; -import {generatePluginInstallationEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, DataSourceContext} from '@graphprotocol/graph-ts'; +import {MultisigApprover} from '../../generated/schema'; +import { + generateMemberEntityId, + generateVoterEntityId, +} from '../../src/plugin/id'; +import { + handleMembersAdded, + handleApproved, + handleProposalExecuted, + handleMembersRemoved, + handleProposalCreated, + handleMultisigSettingsUpdated, +} from '../../src/plugin/plugin'; +import { + ADDRESS_ONE, + ADDRESS_TWO, + ADDRESS_THREE, + CONTRACT_ADDRESS, + DAO_ADDRESS, +} from '../utils/constants'; +import { + createNewMembersAddedEvent, + createNewApprovedEvent, + createNewProposalExecutedEvent, + createNewMembersRemovedEvent, + createNewProposalCreatedEvent, + getProposalCountCall, + createMultisigProposalEntityState, + createGetProposalCall, + createNewMultisigSettingsUpdatedEvent, + createMultisigPluginState, + PLUGIN_PROPOSAL_ID, + SNAPSHOT_BLOCK, + ONE, + TWO, + START_DATE, + END_DATE, + ALLOW_FAILURE_MAP, +} from '../utils/events'; +import { + generatePluginEntityId, + generateProposalEntityId, + createDummyAction, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; import { - assert, afterEach, + assert, beforeEach, clearStore, - test, - describe, dataSourceMock, -} from 'matchstick-as'; + describe, + test, +} from 'matchstick-as/assembly/index'; + +let actions = [createDummyAction(ADDRESS_THREE, '0', '0x00000000')]; + +const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginEntityId = generatePluginEntityId(pluginAddress); +const pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID); +const proposalEntityId = generateProposalEntityId( + pluginAddress, + pluginProposalId +); + +export const METADATA = 'Some String Data ...'; describe('Plugin', () => { beforeEach(function () { @@ -25,36 +76,459 @@ describe('Plugin', () => { clearStore(); }); - describe('NumberStored Event', () => { - test('it should store the correct number emitted from the event', () => { - const daoAddress = Address.fromString(DAO_ADDRESS); - const pluginAddress = Address.fromString(CONTRACT_ADDRESS); + describe('handleProposalCreated', () => { + test('handles the event', () => { + // create state + createMultisigPluginState(); + + // create calls + getProposalCountCall(CONTRACT_ADDRESS, '1'); + createGetProposalCall( + CONTRACT_ADDRESS, + PLUGIN_PROPOSAL_ID, + false, + + // ProposalParameters + START_DATE, + END_DATE, + ONE, + SNAPSHOT_BLOCK, + + // approvals + ONE, + + actions, + + ALLOW_FAILURE_MAP + ); + + // create event + let event = createNewProposalCreatedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_ONE, + START_DATE, + END_DATE, + METADATA, + actions, + ALLOW_FAILURE_MAP, + CONTRACT_ADDRESS + ); + + // handle event + handleProposalCreated(event); + + // checks + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'id', + proposalEntityId + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'daoAddress', + DAO_ADDRESS + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'plugin', + pluginEntityId + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'pluginProposalId', + PLUGIN_PROPOSAL_ID + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'creator', + ADDRESS_ONE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'startDate', + START_DATE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'endDate', + END_DATE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'metadata', + METADATA + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'createdAt', + event.block.timestamp.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'creationBlockNumber', + event.block.number.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'snapshotBlock', + SNAPSHOT_BLOCK + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'minApprovals', + ONE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'approvals', + ONE + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executed', + 'false' + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'allowFailureMap', + ALLOW_FAILURE_MAP + ); + + // check MultisigPlugin + assert.fieldEquals( + 'MultisigPlugin', + Address.fromString(CONTRACT_ADDRESS).toHexString(), + 'proposalCount', + '1' + ); + }); + }); + + describe('handleApproved', () => { + test('handles the event', () => { + // create state + let proposal = createMultisigProposalEntityState( + proposalEntityId, + DAO_ADDRESS, + CONTRACT_ADDRESS, + ADDRESS_ONE + ); + + // create calls + createGetProposalCall( + CONTRACT_ADDRESS, + PLUGIN_PROPOSAL_ID, + false, + + // ProposalParameters + START_DATE, + END_DATE, + TWO, // minApprovals + SNAPSHOT_BLOCK, + + // approvals + ONE, + + actions, + ALLOW_FAILURE_MAP + ); + + // create event + let event = createNewApprovedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_ONE, + CONTRACT_ADDRESS + ); + + handleApproved(event); + + // checks + const memberAddress = Address.fromString(ADDRESS_ONE); + + const memberEntityId = generateMemberEntityId( + pluginAddress, + memberAddress + ); + + const voterEntityId = generateVoterEntityId(memberEntityId, proposal.id); + // check proposalVoter + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'id', + voterEntityId + ); + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'approver', + memberEntityId + ); + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'proposal', + proposal.id + ); + assert.fieldEquals( + 'MultisigProposalApprover', + voterEntityId, + 'createdAt', + event.block.timestamp.toString() + ); + + // check proposal + assert.fieldEquals('MultisigProposal', proposal.id, 'approvals', ONE); + assert.fieldEquals( + 'MultisigProposal', + proposal.id, + 'approvalReached', + 'false' + ); + + // create 2nd approve, to test approvals + // create calls + createGetProposalCall( + CONTRACT_ADDRESS, + PLUGIN_PROPOSAL_ID, + false, + + // ProposalParameters + START_DATE, + END_DATE, + TWO, // minApprovals + SNAPSHOT_BLOCK, + + // approvals + TWO, + + actions, + ALLOW_FAILURE_MAP + ); + + // create event + let event2 = createNewApprovedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_TWO, + CONTRACT_ADDRESS + ); + + handleApproved(event2); + + // Check + assert.fieldEquals('MultisigProposal', proposal.id, 'approvals', TWO); + assert.fieldEquals( + 'MultisigProposal', + proposal.id, + 'approvalReached', + 'true' + ); + }); + }); + + describe('handleProposalExecuted', () => { + test('handles the event', () => { + // create state + createMultisigProposalEntityState( + proposalEntityId, + DAO_ADDRESS, + CONTRACT_ADDRESS, + ADDRESS_ONE + ); + + // create event + let event = createNewProposalExecutedEvent('0', CONTRACT_ADDRESS); + + // handle event + handleProposalExecuted(event); - const installationId = generatePluginInstallationEntityId( - daoAddress, - pluginAddress + // checks + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'id', + proposalEntityId ); - if (!installationId) { - throw new Error('Failed to get installationId'); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executed', + 'true' + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executionDate', + event.block.timestamp.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executionBlockNumber', + event.block.number.toString() + ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'executionTxHash', + event.transaction.hash.toHexString() + ); + }); + }); + + describe('handleMembersAdded', () => { + test('handles the event', () => { + let userArray = [ + Address.fromString(ADDRESS_ONE), + Address.fromString(ADDRESS_TWO), + ]; + + // create event + let event = createNewMembersAddedEvent(userArray, CONTRACT_ADDRESS); + + // handle event + handleMembersAdded(event); + + // checks + let memberId = + Address.fromString(CONTRACT_ADDRESS).toHexString() + + '_' + + userArray[0].toHexString(); + + assert.fieldEquals('MultisigApprover', memberId, 'id', memberId); + assert.fieldEquals( + 'MultisigApprover', + memberId, + 'address', + userArray[0].toHexString() + ); + assert.fieldEquals( + 'MultisigApprover', + memberId, + 'plugin', + Address.fromString(CONTRACT_ADDRESS).toHexString() + ); + }); + }); + + describe('handleMembersRemoved', () => { + test('handles the event', () => { + // create state + let memberAddresses = [ + Address.fromString(ADDRESS_ONE), + Address.fromString(ADDRESS_TWO), + ]; + + for (let index = 0; index < memberAddresses.length; index++) { + const user = memberAddresses[index].toHexString(); + const pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); + let memberId = pluginId + '_' + user; + let userEntity = new MultisigApprover(memberId); + userEntity.plugin = Address.fromString(CONTRACT_ADDRESS).toHexString(); + userEntity.save(); } - // Create state - let daoPlugin = new DaoPlugin(installationId!); - daoPlugin.dao = daoAddress; - daoPlugin.pluginAddress = pluginAddress; - daoPlugin.save(); - const number = '5'; + // checks + let memberId1 = + Address.fromString(CONTRACT_ADDRESS).toHexString() + + '_' + + memberAddresses[0].toHexString(); + let memberId2 = + Address.fromString(CONTRACT_ADDRESS).toHexString() + + '_' + + memberAddresses[1].toHexString(); + + assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); + assert.fieldEquals('MultisigApprover', memberId2, 'id', memberId2); + + // create event + let event = createNewMembersRemovedEvent( + [memberAddresses[1]], + CONTRACT_ADDRESS + ); + + // handle event + handleMembersRemoved(event); - const event = createNewNumberStoredEvent( - number, - pluginAddress.toHexString() + // checks + assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); + assert.notInStore('MultisigApprover', memberId2); + }); + }); + + describe('handleMultisigSettingsUpdated', () => { + test('handles the event', () => { + // create state + let entityID = createMultisigPluginState().id; + + // create event + let onlyListed = true; + let minApproval = '5'; + + let event = createNewMultisigSettingsUpdatedEvent( + onlyListed, + minApproval, + CONTRACT_ADDRESS + ); + + // handle event + handleMultisigSettingsUpdated(event); + + // checks + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'onlyListed', + `${onlyListed}` + ); + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'minApprovals', + minApproval ); - handleNumberStored(event); + // create event + onlyListed = false; + minApproval = '4'; + + event = createNewMultisigSettingsUpdatedEvent( + onlyListed, + minApproval, + CONTRACT_ADDRESS + ); - assert.fieldEquals('DaoPlugin', installationId!, 'id', installationId!); - assert.fieldEquals('DaoPlugin', installationId!, 'number', number); - assert.entityCount('DaoPlugin', 1); + // handle event + handleMultisigSettingsUpdated(event); + + // checks + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'onlyListed', + `${onlyListed}` + ); + assert.fieldEquals( + 'MultisigPlugin', + entityID, + 'minApprovals', + minApproval + ); }); }); }); diff --git a/packages/subgraph/tests/utils/events/plugin.ts b/packages/subgraph/tests/utils/events/plugin.ts index 58df89f7..620798df 100644 --- a/packages/subgraph/tests/utils/events/plugin.ts +++ b/packages/subgraph/tests/utils/events/plugin.ts @@ -1,22 +1,332 @@ -import {NumberStored} from '../../../generated/templates/Plugin/Plugin'; -import {Address, BigInt, ethereum} from '@graphprotocol/graph-ts'; -import {newMockEvent} from 'matchstick-as'; +import {MultisigPlugin, MultisigProposal} from '../../../generated/schema'; +import { + ProposalCreated, + Approved, + ProposalExecuted, + MembersAdded, + MembersRemoved, + MultisigSettingsUpdated, +} from '../../../generated/templates/Plugin/Plugin'; +import {ADDRESS_ONE, DAO_ADDRESS, CONTRACT_ADDRESS} from '../constants'; +import { + generatePluginEntityId, + generateProposalEntityId, +} from '@aragon/osx-commons-subgraph'; +import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {createMockedFunction, newMockEvent} from 'matchstick-as'; -export function createNewNumberStoredEvent( - number: string, +// test data + +export const ZERO = '0'; +export const ONE = '1'; +export const TWO = '2'; +export const THREE = '3'; + +export const PLUGIN_PROPOSAL_ID = ZERO; +export const MIN_PROPOSER_VOTING_POWER = ZERO; +export const START_DATE = '1644851000'; +export const END_DATE = '1644852000'; +export const CREATED_AT = ONE; +export const SNAPSHOT_BLOCK = '100'; +export const ALLOW_FAILURE_MAP = '1'; + +// events + +export function createNewProposalCreatedEvent( + proposalId: string, + creator: string, + startDate: string, + endDate: string, + metadata: string, + actions: ethereum.Tuple[], + allowFailureMap: string, + contractAddress: string +): ProposalCreated { + let createProposalCreatedEvent = changetype(newMockEvent()); + + createProposalCreatedEvent.address = Address.fromString(contractAddress); + createProposalCreatedEvent.parameters = []; + + let proposalIdParam = new ethereum.EventParam( + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) + ); + let creatorParam = new ethereum.EventParam( + 'creator', + ethereum.Value.fromAddress(Address.fromString(creator)) + ); + let startDateParam = new ethereum.EventParam( + 'startDate', + ethereum.Value.fromSignedBigInt(BigInt.fromString(startDate)) + ); + let endDateParam = new ethereum.EventParam( + 'endDate', + ethereum.Value.fromSignedBigInt(BigInt.fromString(endDate)) + ); + let metadataParam = new ethereum.EventParam( + 'metadata', + ethereum.Value.fromBytes(Bytes.fromUTF8(metadata)) + ); + let actionsParam = new ethereum.EventParam( + 'actions', + ethereum.Value.fromTupleArray(actions) + ); + let allowFailureMapParam = new ethereum.EventParam( + 'allowFailureMap', + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)) + ); + + createProposalCreatedEvent.parameters.push(proposalIdParam); + createProposalCreatedEvent.parameters.push(creatorParam); + createProposalCreatedEvent.parameters.push(startDateParam); + createProposalCreatedEvent.parameters.push(endDateParam); + createProposalCreatedEvent.parameters.push(metadataParam); + createProposalCreatedEvent.parameters.push(actionsParam); + createProposalCreatedEvent.parameters.push(allowFailureMapParam); + + return createProposalCreatedEvent; +} + +export function createNewApprovedEvent( + proposalId: string, + approver: string, contractAddress: string -): NumberStored { - let createNumberStoredEvent = changetype(newMockEvent()); +): Approved { + let createApprovedEvent = changetype(newMockEvent()); - createNumberStoredEvent.address = Address.fromString(contractAddress); - createNumberStoredEvent.parameters = []; + createApprovedEvent.address = Address.fromString(contractAddress); + createApprovedEvent.parameters = []; let proposalIdParam = new ethereum.EventParam( - 'number', - ethereum.Value.fromSignedBigInt(BigInt.fromString(number)) + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) + ); + let approverParam = new ethereum.EventParam( + 'approver', + ethereum.Value.fromAddress(Address.fromString(approver)) ); - createNumberStoredEvent.parameters.push(proposalIdParam); + createApprovedEvent.parameters.push(proposalIdParam); + createApprovedEvent.parameters.push(approverParam); + + return createApprovedEvent; +} + +export function createNewProposalExecutedEvent( + proposalId: string, + contractAddress: string +): ProposalExecuted { + let createProposalExecutedEvent = changetype( + newMockEvent() + ); + + createProposalExecutedEvent.address = Address.fromString(contractAddress); + createProposalExecutedEvent.parameters = []; + + let proposalIdParam = new ethereum.EventParam( + 'proposalId', + ethereum.Value.fromSignedBigInt(BigInt.fromString(proposalId)) + ); + + createProposalExecutedEvent.parameters.push(proposalIdParam); + + return createProposalExecutedEvent; +} + +export function createNewMembersAddedEvent( + addresses: Address[], + contractAddress: string +): MembersAdded { + let newMembersAddedEvent = changetype(newMockEvent()); + + newMembersAddedEvent.address = Address.fromString(contractAddress); + newMembersAddedEvent.parameters = []; + + let usersParam = new ethereum.EventParam( + 'users', + ethereum.Value.fromAddressArray(addresses) + ); + + newMembersAddedEvent.parameters.push(usersParam); + + return newMembersAddedEvent; +} + +export function createNewMembersRemovedEvent( + addresses: Address[], + contractAddress: string +): MembersRemoved { + let newMembersRemovedEvent = changetype(newMockEvent()); + + newMembersRemovedEvent.address = Address.fromString(contractAddress); + newMembersRemovedEvent.parameters = []; + + let usersParam = new ethereum.EventParam( + 'users', + ethereum.Value.fromAddressArray(addresses) + ); + + newMembersRemovedEvent.parameters.push(usersParam); + + return newMembersRemovedEvent; +} + +export function createNewMultisigSettingsUpdatedEvent( + onlyListed: boolean, + minApprovals: string, + contractAddress: string +): MultisigSettingsUpdated { + let newProposalSettingsUpdatedEvent = changetype( + newMockEvent() + ); + + newProposalSettingsUpdatedEvent.address = Address.fromString(contractAddress); + newProposalSettingsUpdatedEvent.parameters = []; + + let onlyListedParam = new ethereum.EventParam( + 'onlyListed', + ethereum.Value.fromBoolean(onlyListed) + ); + + let minApprovalsParam = new ethereum.EventParam( + 'minApprovals', + ethereum.Value.fromSignedBigInt(BigInt.fromString(minApprovals)) + ); + + newProposalSettingsUpdatedEvent.parameters.push(onlyListedParam); + newProposalSettingsUpdatedEvent.parameters.push(minApprovalsParam); + + return newProposalSettingsUpdatedEvent; +} + +// calls + +export function getProposalCountCall( + contractAddress: string, + returns: string +): void { + createMockedFunction( + Address.fromString(contractAddress), + 'proposalCount', + 'proposalCount():(uint256)' + ) + .withArgs([]) + .returns([ethereum.Value.fromSignedBigInt(BigInt.fromString(returns))]); +} + +export function createGetProposalCall( + contractAddress: string, + proposalId: string, + executed: boolean, + + startDate: string, + endDate: string, + minApprovals: string, + snapshotBlock: string, + + approvals: string, + + actions: ethereum.Tuple[], + allowFailureMap: string +): void { + let parameters = new ethereum.Tuple(); + + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(minApprovals)) + ); + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(snapshotBlock)) + ); + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(startDate)) + ); + parameters.push( + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(endDate)) + ); + + createMockedFunction( + Address.fromString(contractAddress), + 'getProposal', + 'getProposal(uint256):(bool,uint16,(uint16,uint64,uint64,uint64),(address,uint256,bytes)[],uint256)' + ) + .withArgs([ + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(proposalId)), + ]) + .returns([ + ethereum.Value.fromBoolean(executed), + + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(approvals)), + + // ProposalParameters + ethereum.Value.fromTuple(parameters), + + ethereum.Value.fromTupleArray(actions), + + ethereum.Value.fromUnsignedBigInt(BigInt.fromString(allowFailureMap)), + ]); +} + +// state + +export function createMultisigPluginState( + entityID: string = generatePluginEntityId( + Address.fromString(CONTRACT_ADDRESS) + ), + dao: string = DAO_ADDRESS, + pluginAddress: string = CONTRACT_ADDRESS, + proposalCount: string = ZERO, + minApprovals: string = THREE, + onlyListed: boolean = false +): MultisigPlugin { + let multisigPlugin = new MultisigPlugin(entityID); + multisigPlugin.daoAddress = Bytes.fromHexString(dao); + multisigPlugin.pluginAddress = Bytes.fromHexString(pluginAddress); + multisigPlugin.proposalCount = BigInt.fromString(proposalCount); + multisigPlugin.minApprovals = parseInt(minApprovals) as i32; + multisigPlugin.onlyListed = onlyListed; + multisigPlugin.save(); + + return multisigPlugin; +} + +export function createMultisigProposalEntityState( + entityID: string = generateProposalEntityId( + Address.fromString(CONTRACT_ADDRESS), + BigInt.fromString(PLUGIN_PROPOSAL_ID) + ), + dao: string = DAO_ADDRESS, + plugin: string = CONTRACT_ADDRESS, + creator: string = ADDRESS_ONE, + pluginProposalId: string = PLUGIN_PROPOSAL_ID, + minApprovals: string = TWO, + startDate: string = START_DATE, + endDate: string = END_DATE, + executable: boolean = false, + executed: boolean = false, + allowFailureMap: string = ALLOW_FAILURE_MAP, + + snapshotBlock: string = SNAPSHOT_BLOCK, + + createdAt: string = CREATED_AT, + creationBlockNumber: BigInt = new BigInt(0) +): MultisigProposal { + let multisigProposal = new MultisigProposal(entityID); + multisigProposal.daoAddress = Address.fromHexString(dao); + multisigProposal.plugin = Address.fromString(plugin).toHexString(); + multisigProposal.pluginProposalId = BigInt.fromString(pluginProposalId); + multisigProposal.creator = Address.fromString(creator); + multisigProposal.startDate = BigInt.fromString(startDate); + multisigProposal.endDate = BigInt.fromString(endDate); + multisigProposal.approvalReached = executable; + multisigProposal.isSignaling = false; + multisigProposal.executed = executed; + multisigProposal.snapshotBlock = BigInt.fromString(snapshotBlock); + multisigProposal.minApprovals = BigInt.fromString(minApprovals).toI32(); + multisigProposal.allowFailureMap = BigInt.fromString(allowFailureMap); + multisigProposal.createdAt = BigInt.fromString(createdAt); + multisigProposal.creationBlockNumber = creationBlockNumber; + + multisigProposal.save(); - return createNumberStoredEvent; + return multisigProposal; } diff --git a/packages/subgraph/tsconfig.json b/packages/subgraph/tsconfig.json index 192bbfb5..c3f13e2e 100644 --- a/packages/subgraph/tsconfig.json +++ b/packages/subgraph/tsconfig.json @@ -24,6 +24,7 @@ "tests/**/*", "utils/**/*", "plugin-settings.ts", - "commons/**/*" + "commons/**/*", + "types/**/*" ] } diff --git a/packages/subgraph/types/ASM.d.ts b/packages/subgraph/types/ASM.d.ts new file mode 100644 index 00000000..279fcb44 --- /dev/null +++ b/packages/subgraph/types/ASM.d.ts @@ -0,0 +1,14 @@ +/// AssemblyScript types to remove false errors from the compiler +interface ethereum { + event: import('@graphprotocol/graph-ts/chain/ethereum').ethereum.Event; + value: import('@graphprotocol/graph-ts/chain/ethereum').ethereum.Value; +} + +// type casting through generics is a bit tricky so just add overloads here as you need them +declare function changetype(input: ethereum['event']): T & ethereum['event']; +declare function changetype( + input: ethereum['value'][] +): T & ethereum['value']; + +declare type i32 = number; +declare type bool = boolean; From d014f5f8f502a255c5bdb1959a05d32fec42f2cf Mon Sep 17 00:00:00 2001 From: jordaniza Date: Wed, 27 Mar 2024 15:09:07 +0000 Subject: [PATCH 12/17] feat: removed execution details from plugin actions --- packages/subgraph/schema.graphql | 15 +++- packages/subgraph/src/plugin/plugin.ts | 9 ++- packages/subgraph/tests/plugin/plugin.test.ts | 81 ++++++++++++++++++- packages/subgraph/tests/utils/ids.ts | 30 +++++++ 4 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 packages/subgraph/tests/utils/ids.ts diff --git a/packages/subgraph/schema.graphql b/packages/subgraph/schema.graphql index 81f412cc..816e89f3 100644 --- a/packages/subgraph/schema.graphql +++ b/packages/subgraph/schema.graphql @@ -4,6 +4,14 @@ interface IPlugin { pluginAddress: Bytes! } +interface IAction { + id: ID! # ActionEntityId + to: Bytes! + value: BigInt! + data: Bytes! + daoAddress: Bytes! +} + # Types type MultisigPlugin implements IPlugin @entity { id: ID! # PluginEntityID @@ -26,7 +34,6 @@ interface IProposal { metadata: String actions: [Action!]! @derivedFrom(field: "proposal") allowFailureMap: BigInt! - failureMap: BigInt executed: Boolean! createdAt: BigInt! startDate: BigInt! @@ -34,14 +41,15 @@ interface IProposal { executionTxHash: Bytes } -type Action @entity { +type Action implements IAction @entity(immutable: true) { id: ID! # ActionEntityId to: Bytes! value: BigInt! data: Bytes! daoAddress: Bytes! + + # plugin specific data proposal: IProposal! - execResult: Bytes } type MultisigApprover @entity { @@ -64,7 +72,6 @@ type MultisigProposal implements IProposal @entity { daoAddress: Bytes! actions: [Action!]! @derivedFrom(field: "proposal") allowFailureMap: BigInt! - failureMap: BigInt plugin: MultisigPlugin! pluginProposalId: BigInt! creator: Bytes! diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index 776bee4f..a93cedf1 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -14,9 +14,9 @@ import { Approved, MultisigSettingsUpdated, } from '../../generated/templates/Plugin/Plugin'; +import {generateActionEntityId} from '../../tests/utils/ids'; import {generateMemberEntityId, generateVoterEntityId} from './id'; import { - generateActionEntityId, generatePluginEntityId, generateProposalEntityId, } from '@aragon/osx-commons-subgraph'; @@ -65,7 +65,12 @@ export function handleProposalCreated(event: ProposalCreated): void { for (let index = 0; index < actions.length; index++) { const action = actions[index]; - const actionId = generateActionEntityId(proposalEntityId, index); + const actionId = generateActionEntityId( + pluginAddress, + Address.fromBytes(daoAddr), + pluginProposalId.toString(), + index + ); const actionEntity = new Action(actionId); actionEntity.to = action.to; diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 25ab5e7d..1983fb5d 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -1,4 +1,5 @@ import {MultisigApprover} from '../../generated/schema'; +import {ProposalCreated} from '../../generated/templates/Plugin/Plugin'; import { generateMemberEntityId, generateVoterEntityId, @@ -37,12 +38,18 @@ import { END_DATE, ALLOW_FAILURE_MAP, } from '../utils/events'; +import {generateActionEntityId} from '../utils/ids'; import { generatePluginEntityId, generateProposalEntityId, createDummyAction, } from '@aragon/osx-commons-subgraph'; -import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; +import { + Address, + BigInt, + DataSourceContext, + ethereum, +} from '@graphprotocol/graph-ts'; import { afterEach, assert, @@ -50,10 +57,17 @@ import { clearStore, dataSourceMock, describe, + newMockEvent, test, } from 'matchstick-as/assembly/index'; -let actions = [createDummyAction(ADDRESS_THREE, '0', '0x00000000')]; +let dummyActionTo = ADDRESS_THREE; +let dummyActionValue = '0'; +let dummyActionData = '0x00000000'; + +let actions = [ + createDummyAction(dummyActionTo, dummyActionValue, dummyActionData), +]; const pluginAddress = Address.fromString(CONTRACT_ADDRESS); const pluginEntityId = generatePluginEntityId(pluginAddress); @@ -531,4 +545,67 @@ describe('Plugin', () => { ); }); }); + + describe('Testing Actions', () => { + test('A new proposal action is registered during the proposal creation', () => { + // manual re-write so this approach can be ported to other plugins + assert.entityCount('Action', 0); + assert.entityCount('MultisigProposal', 0); + // create state + createMultisigPluginState(); + + // create calls + getProposalCountCall(CONTRACT_ADDRESS, '1'); + createGetProposalCall( + CONTRACT_ADDRESS, + PLUGIN_PROPOSAL_ID, + false, + + // ProposalParameters + START_DATE, + END_DATE, + ONE, + SNAPSHOT_BLOCK, + + // approvals + ONE, + + actions, + + ALLOW_FAILURE_MAP + ); + + // create event + let event = createNewProposalCreatedEvent( + PLUGIN_PROPOSAL_ID, + ADDRESS_ONE, + START_DATE, + END_DATE, + METADATA, + actions, + ALLOW_FAILURE_MAP, + CONTRACT_ADDRESS + ); + + // handle event + handleProposalCreated(event); + + // step 3: check that the proposal action was created + assert.entityCount('Action', 1); + assert.entityCount('MultisigProposal', 1); + + // step 3.1: check that the action has the correct fields + const actionID = generateActionEntityId( + Address.fromString(CONTRACT_ADDRESS), + Address.fromString(DAO_ADDRESS), + PLUGIN_PROPOSAL_ID.toString(), + 0 + ); + assert.fieldEquals('Action', actionID, 'to', dummyActionTo.toLowerCase()); + assert.fieldEquals('Action', actionID, 'value', dummyActionValue); + assert.fieldEquals('Action', actionID, 'data', dummyActionData); + assert.fieldEquals('Action', actionID, 'daoAddress', DAO_ADDRESS); + assert.fieldEquals('Action', actionID, 'proposal', proposalEntityId); + }); + }); }); diff --git a/packages/subgraph/tests/utils/ids.ts b/packages/subgraph/tests/utils/ids.ts new file mode 100644 index 00000000..2c6a9503 --- /dev/null +++ b/packages/subgraph/tests/utils/ids.ts @@ -0,0 +1,30 @@ +import {generateEntityIdFromAddress} from '@aragon/osx-commons-subgraph'; +import {Address} from '@graphprotocol/graph-ts'; + +/** + * @dev TODO: move this to OSx commons subgraph - tested there + * + * @param caller the user/plugin that will invoke the execute function on the DAO + * @param daoAddress the DAO address + * @param callId the callID determined by the user or plugin + * @param index the index # of the action in the batch + * + * @returns a deterministic Action ID for an action on the DAO. + * This implementation only relies on data that can be fetched + * from the event logs of the `Executed` event, so can be used + * by client applications to query both the OSx core and the plugin + * subgraphs. + */ +export function generateActionEntityId( + caller: Address, + daoAddress: Address, + callId: string, + index: i32 +): string { + return [ + generateEntityIdFromAddress(caller), + generateEntityIdFromAddress(daoAddress), + callId, + index.toString(), + ].join('_'); +} From b9ba6c0107a9995441b932501912095315b7d66f Mon Sep 17 00:00:00 2001 From: jordaniza Date: Tue, 2 Apr 2024 16:44:48 +0100 Subject: [PATCH 13/17] lint: removed dead imports --- packages/subgraph/tests/plugin/plugin.test.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 1983fb5d..285adea1 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -1,5 +1,4 @@ import {MultisigApprover} from '../../generated/schema'; -import {ProposalCreated} from '../../generated/templates/Plugin/Plugin'; import { generateMemberEntityId, generateVoterEntityId, @@ -44,12 +43,7 @@ import { generateProposalEntityId, createDummyAction, } from '@aragon/osx-commons-subgraph'; -import { - Address, - BigInt, - DataSourceContext, - ethereum, -} from '@graphprotocol/graph-ts'; +import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; import { afterEach, assert, @@ -57,7 +51,6 @@ import { clearStore, dataSourceMock, describe, - newMockEvent, test, } from 'matchstick-as/assembly/index'; From d35c4b8e98069ccab82bf4236442a6dbfc4f4009 Mon Sep 17 00:00:00 2001 From: jordaniza Date: Tue, 2 Apr 2024 16:50:22 +0100 Subject: [PATCH 14/17] chore: commons 0.0.5 and generateActionEntityID import --- packages/subgraph/package.json | 2 +- packages/subgraph/src/plugin/plugin.ts | 2 +- packages/subgraph/tests/plugin/plugin.test.ts | 2 +- packages/subgraph/tests/utils/ids.ts | 30 ------------------- packages/subgraph/yarn.lock | 8 ++--- 5 files changed, 7 insertions(+), 37 deletions(-) delete mode 100644 packages/subgraph/tests/utils/ids.ts diff --git a/packages/subgraph/package.json b/packages/subgraph/package.json index c173357d..e3d72ef6 100644 --- a/packages/subgraph/package.json +++ b/packages/subgraph/package.json @@ -29,6 +29,6 @@ "typescript": "^5.2.2" }, "dependencies": { - "@aragon/osx-commons-subgraph": "^0.0.4" + "@aragon/osx-commons-subgraph": "^0.0.5" } } diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index a93cedf1..a45c4ad9 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -14,9 +14,9 @@ import { Approved, MultisigSettingsUpdated, } from '../../generated/templates/Plugin/Plugin'; -import {generateActionEntityId} from '../../tests/utils/ids'; import {generateMemberEntityId, generateVoterEntityId} from './id'; import { + generateActionEntityId, generatePluginEntityId, generateProposalEntityId, } from '@aragon/osx-commons-subgraph'; diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 285adea1..dd33ada8 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -37,10 +37,10 @@ import { END_DATE, ALLOW_FAILURE_MAP, } from '../utils/events'; -import {generateActionEntityId} from '../utils/ids'; import { generatePluginEntityId, generateProposalEntityId, + generateActionEntityId, createDummyAction, } from '@aragon/osx-commons-subgraph'; import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; diff --git a/packages/subgraph/tests/utils/ids.ts b/packages/subgraph/tests/utils/ids.ts deleted file mode 100644 index 2c6a9503..00000000 --- a/packages/subgraph/tests/utils/ids.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {generateEntityIdFromAddress} from '@aragon/osx-commons-subgraph'; -import {Address} from '@graphprotocol/graph-ts'; - -/** - * @dev TODO: move this to OSx commons subgraph - tested there - * - * @param caller the user/plugin that will invoke the execute function on the DAO - * @param daoAddress the DAO address - * @param callId the callID determined by the user or plugin - * @param index the index # of the action in the batch - * - * @returns a deterministic Action ID for an action on the DAO. - * This implementation only relies on data that can be fetched - * from the event logs of the `Executed` event, so can be used - * by client applications to query both the OSx core and the plugin - * subgraphs. - */ -export function generateActionEntityId( - caller: Address, - daoAddress: Address, - callId: string, - index: i32 -): string { - return [ - generateEntityIdFromAddress(caller), - generateEntityIdFromAddress(daoAddress), - callId, - index.toString(), - ].join('_'); -} diff --git a/packages/subgraph/yarn.lock b/packages/subgraph/yarn.lock index 7d11039e..53f8341f 100644 --- a/packages/subgraph/yarn.lock +++ b/packages/subgraph/yarn.lock @@ -9,10 +9,10 @@ dependencies: tslib "^2.6.2" -"@aragon/osx-commons-subgraph@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@aragon/osx-commons-subgraph/-/osx-commons-subgraph-0.0.4.tgz#2aa52f3089d21189c9152d2f3d14c0d7c66d129f" - integrity sha512-cqhusJ3HNvMx+t9lXfN+Hy/5ipefNs1Tdxe+y0GvD4qgBMVU4tCbsxOpB9U2JEJNBCzFQj4E/872FFLpIErB4w== +"@aragon/osx-commons-subgraph@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@aragon/osx-commons-subgraph/-/osx-commons-subgraph-0.0.5.tgz#7e0c0f854e4ca52de1d937595c9bb6ef0370f840" + integrity sha512-M5edVTYyHbkcDLr2H8ySCbOpLA+5pUdN7tCYCif0pDP99Wb+/njgO23G2B2FjB4Q3hB0fCkLkQwNp9QplJjqGA== dependencies: "@graphprotocol/graph-ts" "0.31.0" From 5efefe958001e86421f3b23e6b5f7500524a9696 Mon Sep 17 00:00:00 2001 From: Carles <75954325+banasa44@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:41:51 +0200 Subject: [PATCH 15/17] F/improve subgraph tests (#8) * ci: clean up tests and add/modify constants * ci: split handleProposalCreated as in Admin Plugin * ci: add missing assertions, entitycount and fieldequals * fix: unconsistent use of Address.fromHexString vs Address.fromString * fix: remove unused imports --- packages/subgraph/src/plugin/plugin.ts | 22 +- .../tests/osx/pluginSetupProcessor.test.ts | 52 +++-- packages/subgraph/tests/plugin/plugin.test.ts | 197 ++++++++++++------ packages/subgraph/tests/utils/constants.ts | 33 ++- .../subgraph/tests/utils/events/plugin.ts | 23 +- 5 files changed, 204 insertions(+), 123 deletions(-) diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index a45c4ad9..5985cdb0 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -23,6 +23,17 @@ import { import {Address, dataSource, store} from '@graphprotocol/graph-ts'; export function handleProposalCreated(event: ProposalCreated): void { + const context = dataSource.context(); + const daoAddress = context.getString('daoAddress'); + const metadata = event.params.metadata.toString(); + _handleProposalCreated(event, daoAddress, metadata); +} + +export function _handleProposalCreated( + event: ProposalCreated, + daoAddress: string, + metadata: string +): void { const pluginProposalId = event.params.proposalId; const pluginAddress = event.address; const proposalEntityId = generateProposalEntityId( @@ -33,14 +44,11 @@ export function handleProposalCreated(event: ProposalCreated): void { const proposalEntity = new MultisigProposal(proposalEntityId); - const context = dataSource.context(); - const daoAddr = Address.fromHexString(context.getString('daoAddress')); - - proposalEntity.daoAddress = daoAddr; + proposalEntity.daoAddress = Address.fromHexString(daoAddress); proposalEntity.plugin = pluginEntityId; proposalEntity.pluginProposalId = pluginProposalId; proposalEntity.creator = event.params.creator; - proposalEntity.metadata = event.params.metadata.toString(); + proposalEntity.metadata = metadata; proposalEntity.createdAt = event.block.timestamp; proposalEntity.creationBlockNumber = event.block.number; proposalEntity.startDate = event.params.startDate; @@ -67,7 +75,7 @@ export function handleProposalCreated(event: ProposalCreated): void { const actionId = generateActionEntityId( pluginAddress, - Address.fromBytes(daoAddr), + Address.fromString(daoAddress), pluginProposalId.toString(), index ); @@ -76,7 +84,7 @@ export function handleProposalCreated(event: ProposalCreated): void { actionEntity.to = action.to; actionEntity.value = action.value; actionEntity.data = action.data; - actionEntity.daoAddress = daoAddr; + actionEntity.daoAddress = Address.fromString(daoAddress); actionEntity.proposal = proposalEntityId; actionEntity.save(); } diff --git a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts index 4a69b394..8f0d2754 100644 --- a/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts +++ b/packages/subgraph/tests/osx/pluginSetupProcessor.test.ts @@ -1,19 +1,20 @@ import {PLUGIN_REPO_ADDRESS} from '../../imported/repo-address'; import {handleInstallationPrepared} from '../../src/osx/pluginSetupProcessor'; import { - ADDRESS_FIVE, - ADDRESS_FOUR, - ADDRESS_SIX, - ADDRESS_THREE, - ADDRESS_TWO, ADDRESS_ZERO, + ADDRESS_SIX, CONTRACT_ADDRESS, DAO_ADDRESS, + DAO_ADDRESS_STRING, + ADDRESS_TWO_STRING, + ADDRESS_THREE_STRING, PLUGIN_SETUP_ID, + ADDRESS_FOUR_STRING, + ADDRESS_FIVE_STRING, } from '../utils/constants'; import {createInstallationPreparedEvent} from '../utils/events'; import {generatePluginEntityId} from '@aragon/osx-commons-subgraph'; -import {Address, BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; +import {BigInt, Bytes, ethereum} from '@graphprotocol/graph-ts'; import {assert, afterEach, clearStore, test, describe} from 'matchstick-as'; describe('OSx', () => { @@ -25,11 +26,8 @@ describe('OSx', () => { describe('InstallationPrepared event', () => { test('it should store one plugin', () => { // Create event - const daoAddress = DAO_ADDRESS; const pluginAddress = CONTRACT_ADDRESS; - const pluginEntityId = generatePluginEntityId( - Address.fromString(pluginAddress) - ); + const pluginEntityId = generatePluginEntityId(CONTRACT_ADDRESS); if (!pluginEntityId) { throw new Error('Failed to get pluginEntityId'); } @@ -44,32 +42,32 @@ describe('OSx', () => { let permissions = [ [ ethereum.Value.fromSignedBigInt(BigInt.fromString('0')), - ethereum.Value.fromAddress(Address.fromString(daoAddress)), - ethereum.Value.fromAddress(Address.fromString(pluginAddress)), - ethereum.Value.fromAddress(Address.fromString(ADDRESS_ZERO)), + ethereum.Value.fromAddress(DAO_ADDRESS), + ethereum.Value.fromAddress(pluginAddress), + ethereum.Value.fromAddress(ADDRESS_ZERO), ethereum.Value.fromBytes(Bytes.fromHexString('0x1234')), ], [ ethereum.Value.fromSignedBigInt(BigInt.fromString('2')), - ethereum.Value.fromAddress(Address.fromString(daoAddress)), - ethereum.Value.fromAddress(Address.fromString(pluginAddress)), - ethereum.Value.fromAddress(Address.fromString(ADDRESS_SIX)), + ethereum.Value.fromAddress(DAO_ADDRESS), + ethereum.Value.fromAddress(pluginAddress), + ethereum.Value.fromAddress(ADDRESS_SIX), ethereum.Value.fromBytes(Bytes.fromHexString('0x5678')), ], ]; - const otherPluginSetupRepo = ADDRESS_TWO; + const otherPluginSetupRepo = ADDRESS_TWO_STRING; const event1 = createInstallationPreparedEvent( - ADDRESS_THREE, // sender - daoAddress, - pluginAddress, + ADDRESS_THREE_STRING, // sender + DAO_ADDRESS_STRING, + pluginAddress.toHexString(), Bytes.fromHexString(setupId), otherPluginSetupRepo, versionTuple, Bytes.fromHexString('0x00'), - [ADDRESS_FOUR, ADDRESS_FIVE], + [ADDRESS_FOUR_STRING, ADDRESS_FIVE_STRING], permissions ); @@ -79,19 +77,17 @@ describe('OSx', () => { assert.entityCount('MultisigPlugin', 0); const thisPluginRepoAddress = PLUGIN_REPO_ADDRESS; - const pluginId = generatePluginEntityId( - Address.fromString(pluginAddress) - ); + const pluginId = generatePluginEntityId(pluginAddress); const event2 = createInstallationPreparedEvent( - ADDRESS_THREE, - daoAddress, - pluginAddress, + ADDRESS_THREE_STRING, + DAO_ADDRESS_STRING, + pluginAddress.toHexString(), Bytes.fromHexString(setupId), thisPluginRepoAddress, versionTuple, Bytes.fromHexString('0x00'), - [ADDRESS_FOUR, ADDRESS_FIVE], + [ADDRESS_FOUR_STRING, ADDRESS_FIVE_STRING], permissions ); diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index dd33ada8..2acab6af 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -1,4 +1,3 @@ -import {MultisigApprover} from '../../generated/schema'; import { generateMemberEntityId, generateVoterEntityId, @@ -14,9 +13,13 @@ import { import { ADDRESS_ONE, ADDRESS_TWO, - ADDRESS_THREE, CONTRACT_ADDRESS, DAO_ADDRESS, + ADDRESS_ONE_STRING, + ADDRESS_TWO_STRING, + ADDRESS_THREE_STRING, + CONTRACT_ADDRESS_STRING, + DAO_ADDRESS_STRING, } from '../utils/constants'; import { createNewMembersAddedEvent, @@ -43,7 +46,7 @@ import { generateActionEntityId, createDummyAction, } from '@aragon/osx-commons-subgraph'; -import {Address, BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; +import {BigInt, DataSourceContext} from '@graphprotocol/graph-ts'; import { afterEach, assert, @@ -54,7 +57,7 @@ import { test, } from 'matchstick-as/assembly/index'; -let dummyActionTo = ADDRESS_THREE; +let dummyActionTo = ADDRESS_THREE_STRING; let dummyActionValue = '0'; let dummyActionData = '0x00000000'; @@ -62,7 +65,7 @@ let actions = [ createDummyAction(dummyActionTo, dummyActionValue, dummyActionData), ]; -const pluginAddress = Address.fromString(CONTRACT_ADDRESS); +const pluginAddress = CONTRACT_ADDRESS; const pluginEntityId = generatePluginEntityId(pluginAddress); const pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID); const proposalEntityId = generateProposalEntityId( @@ -75,7 +78,7 @@ export const METADATA = 'Some String Data ...'; describe('Plugin', () => { beforeEach(function () { let context = new DataSourceContext(); - context.setString('daoAddress', DAO_ADDRESS); + context.setString('daoAddress', DAO_ADDRESS_STRING); dataSourceMock.setContext(context); }); @@ -85,13 +88,18 @@ describe('Plugin', () => { describe('handleProposalCreated', () => { test('handles the event', () => { + // check the entities are not in the store + assert.entityCount('MultisigProposal', 0); + assert.entityCount('Action', 0); + assert.entityCount('MultisigPlugin', 0); + // create state createMultisigPluginState(); // create calls - getProposalCountCall(CONTRACT_ADDRESS, '1'); + getProposalCountCall(CONTRACT_ADDRESS_STRING, '1'); createGetProposalCall( - CONTRACT_ADDRESS, + CONTRACT_ADDRESS_STRING, PLUGIN_PROPOSAL_ID, false, @@ -112,18 +120,23 @@ describe('Plugin', () => { // create event let event = createNewProposalCreatedEvent( PLUGIN_PROPOSAL_ID, - ADDRESS_ONE, + ADDRESS_ONE_STRING, START_DATE, END_DATE, METADATA, actions, ALLOW_FAILURE_MAP, - CONTRACT_ADDRESS + CONTRACT_ADDRESS_STRING ); // handle event handleProposalCreated(event); + // check that the proposal action was created + assert.entityCount('Action', 1); + assert.entityCount('MultisigProposal', 1); + assert.entityCount('MultisigPlugin', 1); + // checks assert.fieldEquals( 'MultisigProposal', @@ -135,7 +148,7 @@ describe('Plugin', () => { 'MultisigProposal', proposalEntityId, 'daoAddress', - DAO_ADDRESS + DAO_ADDRESS_STRING ); assert.fieldEquals( 'MultisigProposal', @@ -153,7 +166,7 @@ describe('Plugin', () => { 'MultisigProposal', proposalEntityId, 'creator', - ADDRESS_ONE + ADDRESS_ONE_STRING ); assert.fieldEquals( 'MultisigProposal', @@ -219,26 +232,42 @@ describe('Plugin', () => { // check MultisigPlugin assert.fieldEquals( 'MultisigPlugin', - Address.fromString(CONTRACT_ADDRESS).toHexString(), + CONTRACT_ADDRESS.toHexString(), 'proposalCount', '1' ); + + const actionID = generateActionEntityId( + CONTRACT_ADDRESS, + DAO_ADDRESS, + PLUGIN_PROPOSAL_ID.toString(), + 0 + ); + assert.fieldEquals('Action', actionID, 'to', dummyActionTo.toLowerCase()); + assert.fieldEquals('Action', actionID, 'value', dummyActionValue); + assert.fieldEquals('Action', actionID, 'data', dummyActionData); + assert.fieldEquals('Action', actionID, 'daoAddress', DAO_ADDRESS_STRING); + assert.fieldEquals('Action', actionID, 'proposal', proposalEntityId); }); }); describe('handleApproved', () => { test('handles the event', () => { + // check the entities are not in the store + assert.entityCount('MultisigProposalApprover', 0); + assert.entityCount('MultisigProposal', 0); + // create state let proposal = createMultisigProposalEntityState( proposalEntityId, - DAO_ADDRESS, - CONTRACT_ADDRESS, - ADDRESS_ONE + DAO_ADDRESS_STRING, + CONTRACT_ADDRESS_STRING, + ADDRESS_ONE_STRING ); // create calls createGetProposalCall( - CONTRACT_ADDRESS, + CONTRACT_ADDRESS_STRING, PLUGIN_PROPOSAL_ID, false, @@ -258,14 +287,18 @@ describe('Plugin', () => { // create event let event = createNewApprovedEvent( PLUGIN_PROPOSAL_ID, - ADDRESS_ONE, - CONTRACT_ADDRESS + ADDRESS_ONE_STRING, + CONTRACT_ADDRESS_STRING ); handleApproved(event); + // check if the proposal was approved + assert.entityCount('MultisigProposal', 1); + assert.entityCount('MultisigProposalApprover', 1); + // checks - const memberAddress = Address.fromString(ADDRESS_ONE); + const memberAddress = ADDRESS_ONE; const memberEntityId = generateMemberEntityId( pluginAddress, @@ -311,7 +344,7 @@ describe('Plugin', () => { // create 2nd approve, to test approvals // create calls createGetProposalCall( - CONTRACT_ADDRESS, + CONTRACT_ADDRESS_STRING, PLUGIN_PROPOSAL_ID, false, @@ -331,12 +364,16 @@ describe('Plugin', () => { // create event let event2 = createNewApprovedEvent( PLUGIN_PROPOSAL_ID, - ADDRESS_TWO, - CONTRACT_ADDRESS + ADDRESS_TWO_STRING, + CONTRACT_ADDRESS_STRING ); handleApproved(event2); + // check if the proposal was approved + assert.entityCount('MultisigProposal', 1); + assert.entityCount('MultisigProposalApprover', 2); + // Check assert.fieldEquals('MultisigProposal', proposal.id, 'approvals', TWO); assert.fieldEquals( @@ -350,20 +387,26 @@ describe('Plugin', () => { describe('handleProposalExecuted', () => { test('handles the event', () => { + // check the entities are not in the store + assert.entityCount('MultisigProposal', 0); + // create state createMultisigProposalEntityState( proposalEntityId, - DAO_ADDRESS, - CONTRACT_ADDRESS, - ADDRESS_ONE + DAO_ADDRESS_STRING, + CONTRACT_ADDRESS_STRING, + ADDRESS_ONE_STRING ); // create event - let event = createNewProposalExecutedEvent('0', CONTRACT_ADDRESS); + let event = createNewProposalExecutedEvent('0', CONTRACT_ADDRESS_STRING); // handle event handleProposalExecuted(event); + // check the proposal was executed + assert.entityCount('MultisigProposal', 1); + // checks assert.fieldEquals( 'MultisigProposal', @@ -371,6 +414,12 @@ describe('Plugin', () => { 'id', proposalEntityId ); + assert.fieldEquals( + 'MultisigProposal', + proposalEntityId, + 'approvalReached', + 'false' + ); assert.fieldEquals( 'MultisigProposal', proposalEntityId, @@ -400,77 +449,83 @@ describe('Plugin', () => { describe('handleMembersAdded', () => { test('handles the event', () => { - let userArray = [ - Address.fromString(ADDRESS_ONE), - Address.fromString(ADDRESS_TWO), - ]; + // check the entities are not in the store + assert.entityCount('MultisigApprover', 0); + + let memberAddresses = [ADDRESS_ONE, ADDRESS_TWO]; // create event - let event = createNewMembersAddedEvent(userArray, CONTRACT_ADDRESS); + let event = createNewMembersAddedEvent( + memberAddresses, + CONTRACT_ADDRESS_STRING + ); // handle event handleMembersAdded(event); + // check members were added + assert.entityCount('MultisigApprover', 2); + // checks let memberId = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - userArray[0].toHexString(); + CONTRACT_ADDRESS.toHexString() + '_' + memberAddresses[0].toHexString(); assert.fieldEquals('MultisigApprover', memberId, 'id', memberId); assert.fieldEquals( 'MultisigApprover', memberId, 'address', - userArray[0].toHexString() + memberAddresses[0].toHexString() ); assert.fieldEquals( 'MultisigApprover', memberId, 'plugin', - Address.fromString(CONTRACT_ADDRESS).toHexString() + CONTRACT_ADDRESS.toHexString() ); }); }); describe('handleMembersRemoved', () => { test('handles the event', () => { + // check the entities are not in the store + assert.entityCount('MultisigProposal', 0); + // create state - let memberAddresses = [ - Address.fromString(ADDRESS_ONE), - Address.fromString(ADDRESS_TWO), - ]; - - for (let index = 0; index < memberAddresses.length; index++) { - const user = memberAddresses[index].toHexString(); - const pluginId = Address.fromString(CONTRACT_ADDRESS).toHexString(); - let memberId = pluginId + '_' + user; - let userEntity = new MultisigApprover(memberId); - userEntity.plugin = Address.fromString(CONTRACT_ADDRESS).toHexString(); - userEntity.save(); - } + let memberAddresses = [ADDRESS_ONE, ADDRESS_TWO]; + + // create event + let addEvent = createNewMembersAddedEvent( + memberAddresses, + CONTRACT_ADDRESS_STRING + ); + + // handle event + handleMembersAdded(addEvent); + + // check members were added + assert.entityCount('MultisigApprover', 2); // checks let memberId1 = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - memberAddresses[0].toHexString(); + CONTRACT_ADDRESS.toHexString() + '_' + memberAddresses[0].toHexString(); let memberId2 = - Address.fromString(CONTRACT_ADDRESS).toHexString() + - '_' + - memberAddresses[1].toHexString(); + CONTRACT_ADDRESS.toHexString() + '_' + memberAddresses[1].toHexString(); assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); assert.fieldEquals('MultisigApprover', memberId2, 'id', memberId2); // create event - let event = createNewMembersRemovedEvent( + let removeEvent = createNewMembersRemovedEvent( [memberAddresses[1]], - CONTRACT_ADDRESS + CONTRACT_ADDRESS_STRING ); // handle event - handleMembersRemoved(event); + handleMembersRemoved(removeEvent); + + // check members were removed + assert.entityCount('MultisigApprover', 1); // checks assert.fieldEquals('MultisigApprover', memberId1, 'id', memberId1); @@ -480,6 +535,9 @@ describe('Plugin', () => { describe('handleMultisigSettingsUpdated', () => { test('handles the event', () => { + // check the entities are not in the store + assert.entityCount('MultisigPlugin', 0); + // create state let entityID = createMultisigPluginState().id; @@ -490,12 +548,15 @@ describe('Plugin', () => { let event = createNewMultisigSettingsUpdatedEvent( onlyListed, minApproval, - CONTRACT_ADDRESS + CONTRACT_ADDRESS_STRING ); // handle event handleMultisigSettingsUpdated(event); + // check the entities are updated + assert.entityCount('MultisigPlugin', 1); + // checks assert.fieldEquals( 'MultisigPlugin', @@ -517,7 +578,7 @@ describe('Plugin', () => { event = createNewMultisigSettingsUpdatedEvent( onlyListed, minApproval, - CONTRACT_ADDRESS + CONTRACT_ADDRESS_STRING ); // handle event @@ -548,9 +609,9 @@ describe('Plugin', () => { createMultisigPluginState(); // create calls - getProposalCountCall(CONTRACT_ADDRESS, '1'); + getProposalCountCall(CONTRACT_ADDRESS_STRING, '1'); createGetProposalCall( - CONTRACT_ADDRESS, + CONTRACT_ADDRESS_STRING, PLUGIN_PROPOSAL_ID, false, @@ -571,13 +632,13 @@ describe('Plugin', () => { // create event let event = createNewProposalCreatedEvent( PLUGIN_PROPOSAL_ID, - ADDRESS_ONE, + ADDRESS_ONE_STRING, START_DATE, END_DATE, METADATA, actions, ALLOW_FAILURE_MAP, - CONTRACT_ADDRESS + CONTRACT_ADDRESS_STRING ); // handle event @@ -589,15 +650,15 @@ describe('Plugin', () => { // step 3.1: check that the action has the correct fields const actionID = generateActionEntityId( - Address.fromString(CONTRACT_ADDRESS), - Address.fromString(DAO_ADDRESS), + CONTRACT_ADDRESS, + DAO_ADDRESS, PLUGIN_PROPOSAL_ID.toString(), 0 ); assert.fieldEquals('Action', actionID, 'to', dummyActionTo.toLowerCase()); assert.fieldEquals('Action', actionID, 'value', dummyActionValue); assert.fieldEquals('Action', actionID, 'data', dummyActionData); - assert.fieldEquals('Action', actionID, 'daoAddress', DAO_ADDRESS); + assert.fieldEquals('Action', actionID, 'daoAddress', DAO_ADDRESS_STRING); assert.fieldEquals('Action', actionID, 'proposal', proposalEntityId); }); }); diff --git a/packages/subgraph/tests/utils/constants.ts b/packages/subgraph/tests/utils/constants.ts index f0ea51a9..65f3e94b 100644 --- a/packages/subgraph/tests/utils/constants.ts +++ b/packages/subgraph/tests/utils/constants.ts @@ -1,16 +1,29 @@ -// Constants for test scenarios: +import {Address} from '@graphprotocol/graph-ts'; + +// String and Address type constants for test scenarios: // ADDRESS_ZERO to ADDRESS_SIX: Dummy Ethereum addresses for various test cases. // DAO_ADDRESS: A placeholder address for a DAO instance. // CONTRACT_ADDRESS: A placeholder address for a contract instance. // PLUGIN_SETUP_ID: A mock identifier for a plugin setup in test simulations. -export const ADDRESS_ZERO = '0x0000000000000000000000000000000000000000'; -export const ADDRESS_ONE = '0x0000000000000000000000000000000000000001'; -export const ADDRESS_TWO = '0x0000000000000000000000000000000000000002'; -export const ADDRESS_THREE = '0x0000000000000000000000000000000000000003'; -export const ADDRESS_FOUR = '0x0000000000000000000000000000000000000004'; -export const ADDRESS_FIVE = '0x0000000000000000000000000000000000000005'; -export const ADDRESS_SIX = '0x0000000000000000000000000000000000000006'; -export const DAO_ADDRESS = '0x00000000000000000000000000000000000000da'; -export const CONTRACT_ADDRESS = '0x00000000000000000000000000000000000000Ad'; +export const ADDRESS_ZERO_STRING = '0x0000000000000000000000000000000000000000'; +export const ADDRESS_ZERO = Address.fromString(ADDRESS_ZERO_STRING); +export const ADDRESS_ONE_STRING = '0x0000000000000000000000000000000000000001'; +export const ADDRESS_ONE = Address.fromString(ADDRESS_ONE_STRING); +export const ADDRESS_TWO_STRING = '0x0000000000000000000000000000000000000002'; +export const ADDRESS_TWO = Address.fromString(ADDRESS_TWO_STRING); +export const ADDRESS_THREE_STRING = + '0x0000000000000000000000000000000000000003'; +export const ADDRESS_THREE = Address.fromString(ADDRESS_THREE_STRING); +export const ADDRESS_FOUR_STRING = '0x0000000000000000000000000000000000000004'; +export const ADDRESS_FOUR = Address.fromString(ADDRESS_FOUR_STRING); +export const ADDRESS_FIVE_STRING = '0x0000000000000000000000000000000000000005'; +export const ADDRESS_FIVE = Address.fromString(ADDRESS_FIVE_STRING); +export const ADDRESS_SIX_STRING = '0x0000000000000000000000000000000000000006'; +export const ADDRESS_SIX = Address.fromString(ADDRESS_SIX_STRING); +export const DAO_ADDRESS_STRING = '0x00000000000000000000000000000000000000da'; +export const DAO_ADDRESS = Address.fromString(DAO_ADDRESS_STRING); +export const CONTRACT_ADDRESS_STRING = + '0x00000000000000000000000000000000000000Ad'; +export const CONTRACT_ADDRESS = Address.fromString(CONTRACT_ADDRESS_STRING); export const PLUGIN_SETUP_ID = '0xfb3fd2c4cd4e19944dd3f8437e67476240cd9e3efb2294ebd10c59c8f1d6817c'; diff --git a/packages/subgraph/tests/utils/events/plugin.ts b/packages/subgraph/tests/utils/events/plugin.ts index 620798df..31edcbc5 100644 --- a/packages/subgraph/tests/utils/events/plugin.ts +++ b/packages/subgraph/tests/utils/events/plugin.ts @@ -7,7 +7,12 @@ import { MembersRemoved, MultisigSettingsUpdated, } from '../../../generated/templates/Plugin/Plugin'; -import {ADDRESS_ONE, DAO_ADDRESS, CONTRACT_ADDRESS} from '../constants'; +import { + ADDRESS_ONE_STRING, + DAO_ADDRESS_STRING, + CONTRACT_ADDRESS, + CONTRACT_ADDRESS_STRING, +} from '../constants'; import { generatePluginEntityId, generateProposalEntityId, @@ -269,11 +274,9 @@ export function createGetProposalCall( // state export function createMultisigPluginState( - entityID: string = generatePluginEntityId( - Address.fromString(CONTRACT_ADDRESS) - ), - dao: string = DAO_ADDRESS, - pluginAddress: string = CONTRACT_ADDRESS, + entityID: string = generatePluginEntityId(CONTRACT_ADDRESS), + dao: string = DAO_ADDRESS_STRING, + pluginAddress: string = CONTRACT_ADDRESS_STRING, proposalCount: string = ZERO, minApprovals: string = THREE, onlyListed: boolean = false @@ -291,12 +294,12 @@ export function createMultisigPluginState( export function createMultisigProposalEntityState( entityID: string = generateProposalEntityId( - Address.fromString(CONTRACT_ADDRESS), + CONTRACT_ADDRESS, BigInt.fromString(PLUGIN_PROPOSAL_ID) ), - dao: string = DAO_ADDRESS, - plugin: string = CONTRACT_ADDRESS, - creator: string = ADDRESS_ONE, + dao: string = DAO_ADDRESS_STRING, + plugin: string = CONTRACT_ADDRESS_STRING, + creator: string = ADDRESS_ONE_STRING, pluginProposalId: string = PLUGIN_PROPOSAL_ID, minApprovals: string = TWO, startDate: string = START_DATE, From b9b465ff22700e83e26307c85de1e83086ad9895 Mon Sep 17 00:00:00 2001 From: Carles <75954325+banasa44@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:54:46 +0200 Subject: [PATCH 16/17] Improve casting subgraph tests (#9) * ci: clean up tests and add/modify constants * ci: split handleProposalCreated as in Admin Plugin * ci: add missing assertions, entitycount and fieldequals * fix: unconsistent use of Address.fromHexString vs Address.fromString * fix: remove unused imports * fix: improve casting --- packages/subgraph/src/plugin/plugin.ts | 11 ++++++----- packages/subgraph/tests/plugin/plugin.test.ts | 4 ++-- packages/subgraph/tests/utils/events/plugin.ts | 14 +++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/subgraph/src/plugin/plugin.ts b/packages/subgraph/src/plugin/plugin.ts index 5985cdb0..e744b662 100644 --- a/packages/subgraph/src/plugin/plugin.ts +++ b/packages/subgraph/src/plugin/plugin.ts @@ -24,14 +24,15 @@ import {Address, dataSource, store} from '@graphprotocol/graph-ts'; export function handleProposalCreated(event: ProposalCreated): void { const context = dataSource.context(); - const daoAddress = context.getString('daoAddress'); + const daoAddressString = context.getString('daoAddress'); + const daoAddress = Address.fromString(daoAddressString); const metadata = event.params.metadata.toString(); _handleProposalCreated(event, daoAddress, metadata); } export function _handleProposalCreated( event: ProposalCreated, - daoAddress: string, + daoAddress: Address, metadata: string ): void { const pluginProposalId = event.params.proposalId; @@ -44,7 +45,7 @@ export function _handleProposalCreated( const proposalEntity = new MultisigProposal(proposalEntityId); - proposalEntity.daoAddress = Address.fromHexString(daoAddress); + proposalEntity.daoAddress = daoAddress; proposalEntity.plugin = pluginEntityId; proposalEntity.pluginProposalId = pluginProposalId; proposalEntity.creator = event.params.creator; @@ -75,7 +76,7 @@ export function _handleProposalCreated( const actionId = generateActionEntityId( pluginAddress, - Address.fromString(daoAddress), + daoAddress, pluginProposalId.toString(), index ); @@ -84,7 +85,7 @@ export function _handleProposalCreated( actionEntity.to = action.to; actionEntity.value = action.value; actionEntity.data = action.data; - actionEntity.daoAddress = Address.fromString(daoAddress); + actionEntity.daoAddress = daoAddress; actionEntity.proposal = proposalEntityId; actionEntity.save(); } diff --git a/packages/subgraph/tests/plugin/plugin.test.ts b/packages/subgraph/tests/plugin/plugin.test.ts index 2acab6af..82ab2169 100644 --- a/packages/subgraph/tests/plugin/plugin.test.ts +++ b/packages/subgraph/tests/plugin/plugin.test.ts @@ -260,7 +260,7 @@ describe('Plugin', () => { // create state let proposal = createMultisigProposalEntityState( proposalEntityId, - DAO_ADDRESS_STRING, + DAO_ADDRESS, CONTRACT_ADDRESS_STRING, ADDRESS_ONE_STRING ); @@ -393,7 +393,7 @@ describe('Plugin', () => { // create state createMultisigProposalEntityState( proposalEntityId, - DAO_ADDRESS_STRING, + DAO_ADDRESS, CONTRACT_ADDRESS_STRING, ADDRESS_ONE_STRING ); diff --git a/packages/subgraph/tests/utils/events/plugin.ts b/packages/subgraph/tests/utils/events/plugin.ts index 31edcbc5..8cbe3e4c 100644 --- a/packages/subgraph/tests/utils/events/plugin.ts +++ b/packages/subgraph/tests/utils/events/plugin.ts @@ -9,7 +9,7 @@ import { } from '../../../generated/templates/Plugin/Plugin'; import { ADDRESS_ONE_STRING, - DAO_ADDRESS_STRING, + DAO_ADDRESS, CONTRACT_ADDRESS, CONTRACT_ADDRESS_STRING, } from '../constants'; @@ -275,15 +275,15 @@ export function createGetProposalCall( export function createMultisigPluginState( entityID: string = generatePluginEntityId(CONTRACT_ADDRESS), - dao: string = DAO_ADDRESS_STRING, - pluginAddress: string = CONTRACT_ADDRESS_STRING, + dao: Address = DAO_ADDRESS, + pluginAddress: Address = CONTRACT_ADDRESS, proposalCount: string = ZERO, minApprovals: string = THREE, onlyListed: boolean = false ): MultisigPlugin { let multisigPlugin = new MultisigPlugin(entityID); - multisigPlugin.daoAddress = Bytes.fromHexString(dao); - multisigPlugin.pluginAddress = Bytes.fromHexString(pluginAddress); + multisigPlugin.daoAddress = dao; + multisigPlugin.pluginAddress = pluginAddress; multisigPlugin.proposalCount = BigInt.fromString(proposalCount); multisigPlugin.minApprovals = parseInt(minApprovals) as i32; multisigPlugin.onlyListed = onlyListed; @@ -297,7 +297,7 @@ export function createMultisigProposalEntityState( CONTRACT_ADDRESS, BigInt.fromString(PLUGIN_PROPOSAL_ID) ), - dao: string = DAO_ADDRESS_STRING, + dao: Address = DAO_ADDRESS, plugin: string = CONTRACT_ADDRESS_STRING, creator: string = ADDRESS_ONE_STRING, pluginProposalId: string = PLUGIN_PROPOSAL_ID, @@ -314,7 +314,7 @@ export function createMultisigProposalEntityState( creationBlockNumber: BigInt = new BigInt(0) ): MultisigProposal { let multisigProposal = new MultisigProposal(entityID); - multisigProposal.daoAddress = Address.fromHexString(dao); + multisigProposal.daoAddress = dao; multisigProposal.plugin = Address.fromString(plugin).toHexString(); multisigProposal.pluginProposalId = BigInt.fromString(pluginProposalId); multisigProposal.creator = Address.fromString(creator); From 2b2875695390c7bbdd89845da853844eb5c99cdf Mon Sep 17 00:00:00 2001 From: banasa44 Date: Mon, 8 Apr 2024 17:17:59 +0200 Subject: [PATCH 17/17] fix: update outdated GH link --- packages/subgraph/manifest/subgraph.placeholder.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/subgraph/manifest/subgraph.placeholder.yaml b/packages/subgraph/manifest/subgraph.placeholder.yaml index 931951b1..68d1b39b 100644 --- a/packages/subgraph/manifest/subgraph.placeholder.yaml +++ b/packages/subgraph/manifest/subgraph.placeholder.yaml @@ -1,7 +1,7 @@ {{info}} specVersion: 0.0.2 -description: A template for Plugin subgraphs -repository: https://github.com/aragon/osx-plugin-subgraph +description: The schema for Multisig Plugin subgraph +repository: https://github.com/aragon/multisig-plugin schema: file: ./schema.graphql dataSources: